diff --git a/crates/game-solver/src/game.rs b/crates/game-solver/src/game.rs index a55649b..82c40de 100644 --- a/crates/game-solver/src/game.rs +++ b/crates/game-solver/src/game.rs @@ -14,21 +14,6 @@ pub enum GameState { Win(P), } -/// Defines the 'partiality' of the game; whether a game's possible moves -/// are affected by the current player moving. -pub enum Partiality { - /// If a game is impartial, a game's moves - /// must not be affected by its player. - /// - /// Learn more: - Impartial, - /// If a game is partizan, a game's moves - /// must be affected by its player. - /// - /// Learn more: - Partizan -} - /// Defines the 'state' the game is in. /// /// Generally used by a game solver for better optimizations. @@ -84,7 +69,6 @@ pub trait Game: Clone { type Player: Player; - const PARTIALITY: Partiality; const STATE_TYPE: Option; /// Returns the amount of moves that have been played diff --git a/crates/game-solver/src/lib.rs b/crates/game-solver/src/lib.rs index 0bfd469..e8b4b96 100644 --- a/crates/game-solver/src/lib.rs +++ b/crates/game-solver/src/lib.rs @@ -23,7 +23,7 @@ use std::hash::Hash; /// Runs the two-player minimax variant on a zero-sum game. /// Since it uses alpha-beta pruning, you can specify an alpha beta window. -fn negamax + Eq + Hash>( +fn negamax + Eq + Hash>( game: &T, transposition_table: &mut dyn TranspositionTable, mut alpha: isize, @@ -108,7 +108,7 @@ fn negamax + Eq + Hash>( /// In 2 player games, if a score > 0, then the player whose turn it is has a winning strategy. /// If a score < 0, then the player whose turn it is has a losing strategy. /// Else, the game is a draw (score = 0). -pub fn solve + Eq + Hash>( +pub fn solve + Eq + Hash>( game: &T, transposition_table: &mut dyn TranspositionTable, ) -> Result { @@ -140,7 +140,7 @@ pub fn solve + Eq + Hash>( /// # Returns /// /// An iterator of tuples of the form `(move, score)`. -pub fn move_scores<'a, T: Game + Eq + Hash>( +pub fn move_scores<'a, T: Game + Eq + Hash>( game: &'a T, transposition_table: &'a mut dyn TranspositionTable, ) -> impl Iterator> + 'a { @@ -165,9 +165,8 @@ type CollectedMoves = Vec::Move, isize), ::Mov /// /// A vector of tuples of the form `(move, score)`. #[cfg(feature = "rayon")] -pub fn par_move_scores_with_hasher(game: &T) -> CollectedMoves +pub fn par_move_scores_with_hasher + Eq + Hash + Sync + Send + 'static, S>(game: &T) -> CollectedMoves where - T: Game + Eq + Hash + Sync + Send + 'static, T::Move: Sync + Send, T::MoveError: Sync + Send, S: BuildHasher + Default + Sync + Send + Clone + 'static, @@ -204,9 +203,8 @@ where /// /// A vector of tuples of the form `(move, score)`. #[cfg(feature = "rayon")] -pub fn par_move_scores(game: &T) -> CollectedMoves +pub fn par_move_scores + Eq + Hash + Sync + Send + 'static>(game: &T) -> CollectedMoves where - T: Game + Eq + Hash + Sync + Send + 'static, T::Move: Sync + Send, T::MoveError: Sync + Send, { diff --git a/crates/game-solver/src/player.rs b/crates/game-solver/src/player.rs index 343135e..15984be 100644 --- a/crates/game-solver/src/player.rs +++ b/crates/game-solver/src/player.rs @@ -23,7 +23,10 @@ pub trait TwoPlayer: Player { } } -/// Represents a player in a partizan game. +/// Represents a player in a zero-sum (2-player) game, +/// where the game is partizan. That is, +/// a player can affect the `Game::possible_moves` function, +/// or players have different winning outcomes. #[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)] pub enum PartizanPlayer { /// The first player. @@ -60,7 +63,7 @@ impl TwoPlayer for PartizanPlayer {} /// Represents a player in a zero-sum (2-player) game, /// where the game is impartial. That is, -/// a player does not affect the `Game::possible_moves` function. +/// the only difference between players is who goes first. #[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)] pub enum ImpartialPlayer { /// The player that will play on the current game state, diff --git a/crates/games/src/chomp/mod.rs b/crates/games/src/chomp/mod.rs index 9241a4d..d7171d3 100644 --- a/crates/games/src/chomp/mod.rs +++ b/crates/games/src/chomp/mod.rs @@ -5,7 +5,7 @@ pub mod gui; use anyhow::Error; use array2d::Array2D; use clap::Args; -use game_solver::{game::{Game, GameState, Partiality, PartizanGame, StateType}, player::ZeroSumPlayer}; +use game_solver::{game::{Game, GameState, StateType}, player::ImpartialPlayer}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -77,10 +77,9 @@ pub type ChompMove = NaturalMove<2>; impl Game for Chomp { type Move = ChompMove; type Iter<'a> = std::vec::IntoIter; - type Player = ZeroSumPlayer; + type Player = ImpartialPlayer; type MoveError = ChompMoveError; - const PARTIALITY: Partiality = Partiality::Impartial; const STATE_TYPE: Option = Some(StateType::Misere); fn max_moves(&self) -> Option { @@ -118,11 +117,7 @@ impl Game for Chomp { } fn player(&self) -> Self::Player { - if self.move_count % 2 == 0 { - ZeroSumPlayer::Left - } else { - ZeroSumPlayer::Right - } + ImpartialPlayer::Next } fn state(&self) -> GameState { diff --git a/crates/games/src/domineering/mod.rs b/crates/games/src/domineering/mod.rs index 7404421..08feed1 100644 --- a/crates/games/src/domineering/mod.rs +++ b/crates/games/src/domineering/mod.rs @@ -5,7 +5,7 @@ pub mod gui; use anyhow::Error; use array2d::Array2D; use clap::Args; -use game_solver::{game::{Game, GameState, PartizanGame}, player::{Player, ZeroSumPlayer}}; +use game_solver::{game::{Game, GameState, StateType}, player::{PartizanPlayer, Player}}; use serde::{Deserialize, Serialize}; use std::{ fmt::{Debug, Display, Formatter}, @@ -63,9 +63,9 @@ impl Domineering { #[derive(Error, Debug, Clone)] pub enum DomineeringMoveError { #[error("While no domino is present at {0}, player {1:?} can not move at {0} because a domino is in way of placement.")] - BlockingAdjacent(DomineeringMove, ZeroSumPlayer), + BlockingAdjacent(DomineeringMove, PartizanPlayer), #[error("Player {1:?} can not move at {0} because a domino is already at {0}.")] - BlockingCurrent(DomineeringMove, ZeroSumPlayer), + BlockingCurrent(DomineeringMove, PartizanPlayer), } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -106,22 +106,14 @@ impl Domineering { } } -impl PartizanGame for Domineering { - fn player(&self) -> Self::Player { - if self.move_count % 2 == 0 { - ZeroSumPlayer::Left - } else { - ZeroSumPlayer::Right - } - } -} - impl Game for Domineering { type Move = DomineeringMove; type Iter<'a> = std::vec::IntoIter; - type Player = ZeroSumPlayer; + type Player = PartizanPlayer; type MoveError = DomineeringMoveError; + const STATE_TYPE: Option = Some(StateType::Normal); + fn max_moves(&self) -> Option { Some(WIDTH * HEIGHT) } @@ -132,7 +124,7 @@ impl Game for Domineering Result<(), Self::MoveError> { if *self.board.get(m.0, m.1).unwrap() { - self.place(m, if self.player() == ZeroSumPlayer::Left { + self.place(m, if self.player() == PartizanPlayer::Left { self.primary_orientation } else { self.primary_orientation.turn() @@ -150,7 +142,7 @@ impl Game for Domineering Self::Iter<'_> { let mut moves = Vec::new(); - let orientation = if self.player() == ZeroSumPlayer::Left { + let orientation = if self.player() == PartizanPlayer::Left { self.primary_orientation } else { self.primary_orientation.turn() @@ -187,6 +179,14 @@ impl Game for Domineering Self::Player { + if self.move_count % 2 == 0 { + PartizanPlayer::Left + } else { + PartizanPlayer::Right + } + } } impl Display for Domineering { @@ -244,7 +244,7 @@ mod tests { use super::*; /// Get the winner of a generic configuration of domineering - fn winner(orientation: Orientation) -> Option { + fn winner(orientation: Orientation) -> Option { let game = Domineering::::new_orientation(orientation); let mut move_scores = move_scores(&game, &mut HashMap::new()) .collect::, DomineeringMoveError>>() @@ -256,20 +256,20 @@ mod tests { move_scores.sort_by_key(|m| m.1); move_scores.reverse(); if move_scores[0].1 > 0 { - Some(ZeroSumPlayer::Left) + Some(PartizanPlayer::Left) } else { - Some(ZeroSumPlayer::Right) + Some(PartizanPlayer::Right) } } } #[test] fn test_wins() { - assert_eq!(winner::<5, 5>(Orientation::Horizontal), Some(ZeroSumPlayer::Right)); - assert_eq!(winner::<4, 4>(Orientation::Horizontal), Some(ZeroSumPlayer::Left)); - assert_eq!(winner::<3, 3>(Orientation::Horizontal), Some(ZeroSumPlayer::Left)); - assert_eq!(winner::<13, 2>(Orientation::Horizontal), Some(ZeroSumPlayer::Right)); - assert_eq!(winner::<11, 2>(Orientation::Horizontal), Some(ZeroSumPlayer::Left)); + assert_eq!(winner::<5, 5>(Orientation::Horizontal), Some(PartizanPlayer::Right)); + assert_eq!(winner::<4, 4>(Orientation::Horizontal), Some(PartizanPlayer::Left)); + assert_eq!(winner::<3, 3>(Orientation::Horizontal), Some(PartizanPlayer::Left)); + assert_eq!(winner::<13, 2>(Orientation::Horizontal), Some(PartizanPlayer::Right)); + assert_eq!(winner::<11, 2>(Orientation::Horizontal), Some(PartizanPlayer::Left)); } #[test] diff --git a/crates/games/src/nim/mod.rs b/crates/games/src/nim/mod.rs index 4d3a381..6bb5e2a 100644 --- a/crates/games/src/nim/mod.rs +++ b/crates/games/src/nim/mod.rs @@ -4,7 +4,7 @@ pub mod gui; use anyhow::Error; use clap::Args; -use game_solver::{game::{Game, GameState}, player::ImpartialPlayer}; +use game_solver::{game::{Game, GameState, StateType}, player::ImpartialPlayer}; use serde::{Deserialize, Serialize}; use std::{fmt::Display, hash::Hash}; use thiserror::Error; @@ -53,6 +53,8 @@ impl Game for Nim { type Player = ImpartialPlayer; type MoveError = NimMoveError; + const STATE_TYPE: Option = Some(StateType::Normal); + fn max_moves(&self) -> Option { Some(self.max_score) } @@ -99,11 +101,11 @@ impl Game for Nim { } fn state(&self) -> GameState { - if self.possible_moves().len() == 0 { - GameState::Win(ImpartialPlayer::Previous) - } else { - GameState::Playable - } + Self::STATE_TYPE.unwrap().state(self) + } + + fn player(&self) -> Self::Player { + ImpartialPlayer::Next } } diff --git a/crates/games/src/order_and_chaos/mod.rs b/crates/games/src/order_and_chaos/mod.rs index 57ac279..17d7016 100644 --- a/crates/games/src/order_and_chaos/mod.rs +++ b/crates/games/src/order_and_chaos/mod.rs @@ -5,7 +5,7 @@ pub mod gui; use anyhow::{anyhow, Error}; use array2d::Array2D; use clap::Args; -use game_solver::{game::{Game, GameState, PartizanGame}, player::ZeroSumPlayer}; +use game_solver::{game::{Game, GameState, StateType}, player::PartizanPlayer}; use serde::{Deserialize, Serialize}; use std::{ fmt::{Display, Formatter}, @@ -88,9 +88,11 @@ impl Game for OrderAndChaos { type Move = OrderAndChaosMove; type Iter<'a> = std::vec::IntoIter; /// Define Nimbers as a zero-sum game - type Player = ZeroSumPlayer; + type Player = PartizanPlayer; type MoveError = OrderAndChaosMoveError; + const STATE_TYPE: Option = None; + fn max_moves(&self) -> Option { Some(WIDTH * HEIGHT) } @@ -139,58 +141,18 @@ impl Game for OrderAndChaos { // a move is winning if the next player // has no possible moves to make (normal play for Nim) - fn next_state(&self, m: &Self::Move) -> Result, Self::MoveError> { - let mut board = self.clone(); - board.make_move(m)?; - let found = 'found: { - let ((row, column), square) = m.0; - - // check for horizontal win - let mut count = 0; - let mut mistakes = 0; - 'horiz: for i in 0..WIDTH { - if board.board[(row, i)] == Some(square) { - count += 1; - if count == WIN_LENGTH { - break 'found true; - } - } else { - count = 0; - mistakes += 1; - if mistakes > WIDTH - WIN_LENGTH { - break 'horiz; - } - } - } - - // check for vertical win - let mut count = 0; - let mut mistakes = 0; - 'vert: for i in 0..HEIGHT { - if board.board[(i, column)] == Some(square) { - count += 1; - if count == WIN_LENGTH { - break 'found true; - } - } else { - count = 0; - mistakes += 1; - if mistakes > HEIGHT - WIN_LENGTH { - break 'vert; - } - } - } - - // check for diagonal win - top left to bottom right - let mut count = 0; - let mut mistakes = 0; - let origins = [(0, 0), (1, 0), (0, 1)]; - - 'diag: for (row, column) in &origins { - let mut row = *row; - let mut column = *column; - while row < HEIGHT && column < WIDTH { - if board.board[(row, column)] == Some(square) { + fn find_immediately_resolvable_game(&self) -> Result, Self::MoveError> { + for m in &mut self.possible_moves() { + let mut board = self.clone(); + board.make_move(&m)?; + let found = 'found: { + let ((row, column), square) = m.0; + + // check for horizontal win + let mut count = 0; + let mut mistakes = 0; + 'horiz: for i in 0..WIDTH { + if board.board[(row, i)] == Some(square) { count += 1; if count == WIN_LENGTH { break 'found true; @@ -198,25 +160,17 @@ impl Game for OrderAndChaos { } else { count = 0; mistakes += 1; - if mistakes > HEIGHT - WIN_LENGTH { - break 'diag; + if mistakes > WIDTH - WIN_LENGTH { + break 'horiz; } } - row += 1; - column += 1; } - } - - // check for diagonal win - top right to bottom left - let mut count = 0; - let mut mistakes = 0; - let origins = [(0, WIDTH - 1), (1, WIDTH - 1), (0, WIDTH - 2)]; - 'diag: for (row, column) in &origins { - let mut row = *row; - let mut column = *column; - while row < HEIGHT { - if board.board[(row, column)] == Some(square) { + // check for vertical win + let mut count = 0; + let mut mistakes = 0; + 'vert: for i in 0..HEIGHT { + if board.board[(i, column)] == Some(square) { count += 1; if count == WIN_LENGTH { break 'found true; @@ -225,40 +179,88 @@ impl Game for OrderAndChaos { count = 0; mistakes += 1; if mistakes > HEIGHT - WIN_LENGTH { - break 'diag; + break 'vert; } } - row += 1; - if column == 0 { - break; + } + + // check for diagonal win - top left to bottom right + let mut count = 0; + let mut mistakes = 0; + let origins = [(0, 0), (1, 0), (0, 1)]; + + 'diag: for (row, column) in &origins { + let mut row = *row; + let mut column = *column; + while row < HEIGHT && column < WIDTH { + if board.board[(row, column)] == Some(square) { + count += 1; + if count == WIN_LENGTH { + break 'found true; + } + } else { + count = 0; + mistakes += 1; + if mistakes > HEIGHT - WIN_LENGTH { + break 'diag; + } + } + row += 1; + column += 1; + } + } + + // check for diagonal win - top right to bottom left + let mut count = 0; + let mut mistakes = 0; + let origins = [(0, WIDTH - 1), (1, WIDTH - 1), (0, WIDTH - 2)]; + + 'diag: for (row, column) in &origins { + let mut row = *row; + let mut column = *column; + while row < HEIGHT { + if board.board[(row, column)] == Some(square) { + count += 1; + if count == WIN_LENGTH { + break 'found true; + } + } else { + count = 0; + mistakes += 1; + if mistakes > HEIGHT - WIN_LENGTH { + break 'diag; + } + } + row += 1; + if column == 0 { + break; + } + column -= 1; } - column -= 1; } - } - false - }; + false + }; - Ok(if found { - GameState::Win(ZeroSumPlayer::Left) - } else if board.possible_moves().next().is_none() { - GameState::Win(ZeroSumPlayer::Right) - } else { - GameState::Playable - }) + if found { + return Ok(Some(board)); + } else if board.possible_moves().next().is_none() { + return Ok(Some(board)); + } + } + + Ok(None) } fn state(&self) -> GameState { unimplemented!() } -} -impl PartizanGame for OrderAndChaos { - fn player(&self) -> ZeroSumPlayer { + fn player(&self) -> PartizanPlayer { if self.move_count % 2 == 0 { - ZeroSumPlayer::Left + PartizanPlayer::Left } else { - ZeroSumPlayer::Right + PartizanPlayer::Right } } } diff --git a/crates/games/src/reversi/mod.rs b/crates/games/src/reversi/mod.rs index 244837e..f39e075 100644 --- a/crates/games/src/reversi/mod.rs +++ b/crates/games/src/reversi/mod.rs @@ -6,7 +6,7 @@ pub mod gui; use anyhow::Error; use array2d::Array2D; use clap::Args; -use game_solver::{game::{Game, GameState, PartizanGame}, player::{Player, ZeroSumPlayer}}; +use game_solver::{game::{Game, GameState, StateType}, player::{PartizanPlayer, Player}}; use serde::{Deserialize, Serialize}; use std::fmt; use std::hash::Hash; @@ -21,7 +21,7 @@ pub type ReversiMove = NaturalMove<2>; #[derive(Clone, Hash, Eq, PartialEq)] pub struct Reversi { /// None if empty, Some(Player) if occupied - board: Array2D>, + board: Array2D>, move_count: usize, } @@ -31,16 +31,16 @@ impl Reversi { // set middle squares to occupied: board - .set(WIDTH / 2 - 1, HEIGHT / 2 - 1, Some(ZeroSumPlayer::Left)) + .set(WIDTH / 2 - 1, HEIGHT / 2 - 1, Some(PartizanPlayer::Left)) .unwrap(); board - .set(WIDTH / 2, HEIGHT / 2, Some(ZeroSumPlayer::Left)) + .set(WIDTH / 2, HEIGHT / 2, Some(PartizanPlayer::Left)) .unwrap(); board - .set(WIDTH / 2 - 1, HEIGHT / 2, Some(ZeroSumPlayer::Right)) + .set(WIDTH / 2 - 1, HEIGHT / 2, Some(PartizanPlayer::Right)) .unwrap(); board - .set(WIDTH / 2, HEIGHT / 2 - 1, Some(ZeroSumPlayer::Right)) + .set(WIDTH / 2, HEIGHT / 2 - 1, Some(PartizanPlayer::Right)) .unwrap(); Self { @@ -127,9 +127,11 @@ impl Reversi { impl Game for Reversi { type Move = ReversiMove; type Iter<'a> = std::vec::IntoIter; - type Player = ZeroSumPlayer; + type Player = PartizanPlayer; type MoveError = array2d::Error; + const STATE_TYPE: Option = None; + fn max_moves(&self) -> Option { Some(WIDTH * HEIGHT) } @@ -175,35 +177,33 @@ impl Game for Reversi { for x in 0..WIDTH { for y in 0..HEIGHT { match *self.board.get(x, y).unwrap() { - Some(ZeroSumPlayer::Left) => player_one_count += 1, - Some(ZeroSumPlayer::Right) => player_two_count += 1, + Some(PartizanPlayer::Left) => player_one_count += 1, + Some(PartizanPlayer::Right) => player_two_count += 1, None => (), } } } match player_one_count.cmp(&player_two_count) { - std::cmp::Ordering::Greater => GameState::Win(ZeroSumPlayer::Left), - std::cmp::Ordering::Less => GameState::Win(ZeroSumPlayer::Right), + std::cmp::Ordering::Greater => GameState::Win(PartizanPlayer::Left), + std::cmp::Ordering::Less => GameState::Win(PartizanPlayer::Right), std::cmp::Ordering::Equal => GameState::Tie, } } -} -impl PartizanGame for Reversi { - fn player(&self) -> ZeroSumPlayer { + fn player(&self) -> PartizanPlayer { if self.move_count % 2 == 0 { - ZeroSumPlayer::Left + PartizanPlayer::Left } else { - ZeroSumPlayer::Right + PartizanPlayer::Right } } } -fn player_to_char(player: Option) -> char { +fn player_to_char(player: Option) -> char { match player { - Some(ZeroSumPlayer::Left) => 'X', - Some(ZeroSumPlayer::Right) => 'O', + Some(PartizanPlayer::Left) => 'X', + Some(PartizanPlayer::Right) => 'O', None => '-', } } diff --git a/crates/games/src/tic_tac_toe/mod.rs b/crates/games/src/tic_tac_toe/mod.rs index a472a5d..a30704a 100644 --- a/crates/games/src/tic_tac_toe/mod.rs +++ b/crates/games/src/tic_tac_toe/mod.rs @@ -4,7 +4,7 @@ pub mod gui; use anyhow::{anyhow, Error}; use clap::Args; -use game_solver::{game::{Game, GameState, PartizanGame}, player::{Player, ZeroSumPlayer}}; +use game_solver::{game::{Game, GameState, StateType}, player::{PartizanPlayer, Player}}; use itertools::Itertools; use ndarray::{iter::IndexedIter, ArrayD, Dim, Dimension, IntoDimension, IxDyn, IxDynImpl}; use serde::{Deserialize, Serialize}; @@ -160,9 +160,11 @@ impl Game for TicTacToe { IndexedIter<'a, Square, Dim>, fn((Dim, &Square)) -> Option, >; - type Player = ZeroSumPlayer; + type Player = PartizanPlayer; type MoveError = TicTacToeMoveError; + const STATE_TYPE: Option = None; + fn max_moves(&self) -> Option { Some(self.size.pow(self.dim as u32)) } @@ -196,7 +198,7 @@ impl Game for TicTacToe { fn make_move(&mut self, m: &Self::Move) -> Result<(), Self::MoveError> { if *self.board.get(m.0.clone()).unwrap() == Square::Empty { - let square = if self.player() == ZeroSumPlayer::Left { + let square = if self.player() == PartizanPlayer::Left { Square::X } else { Square::O @@ -222,25 +224,29 @@ impl Game for TicTacToe { }) } - fn next_state(&self, m: &Self::Move) -> Result, Self::MoveError> { + fn find_immediately_resolvable_game(&self) -> Result, Self::MoveError> { // check if the amount of moves is less than (size * 2) - 1 // if it is, then it's impossible to win if self.move_count + 1 < self.size * 2 - 1 { - return Ok(GameState::Playable); + return Ok(None); } - let mut board = self.clone(); - board.make_move(m)?; - Ok(board.state()) + for m in &mut self.possible_moves() { + let mut new_self = self.clone(); + new_self.make_move(&m)?; + if let GameState::Win(_) = new_self.state() { + return Ok(Some(new_self)); + } + } + + Ok(None) } -} -impl PartizanGame for TicTacToe { fn player(&self) -> Self::Player { if self.move_count % 2 == 0 { - ZeroSumPlayer::Left + PartizanPlayer::Left } else { - ZeroSumPlayer::Right + PartizanPlayer::Right } } } @@ -337,7 +343,7 @@ mod tests { game.make_move(&TicTacToeMove(vec![2, 0].into_dimension())) .unwrap(); // X - assert!(game.state() == GameState::Win(ZeroSumPlayer::Left)); + assert!(game.state() == GameState::Win(PartizanPlayer::Left)); } #[test] @@ -375,7 +381,7 @@ mod tests { game.make_move(&TicTacToeMove(vec![0, 2, 0].into_dimension())) .unwrap(); // X - assert!(game.state() == GameState::Win(ZeroSumPlayer::Left)); + assert!(game.state() == GameState::Win(PartizanPlayer::Left)); } #[test] diff --git a/crates/games/src/util/cli/mod.rs b/crates/games/src/util/cli/mod.rs index 737a763..6d49b5a 100644 --- a/crates/games/src/util/cli/mod.rs +++ b/crates/games/src/util/cli/mod.rs @@ -2,14 +2,12 @@ use anyhow::{anyhow, Result}; use core::hash::Hash; use game_solver::{ game::{Game, GameState}, - par_move_scores, player::{Player, ZeroSumPlayer}, + par_move_scores, player::TwoPlayer, }; -use std::{any::Any, fmt::{Debug, Display}}; +use std::fmt::{Debug, Display}; -pub fn play(game: T) +pub fn play + Eq + Hash + Sync + Send + Display + 'static>(game: T) where - P: Player, - T: Game + Eq + Hash + Sync + Send + Display + 'static, T::Move: Sync + Send + Display, T::MoveError: Sync + Send + Debug, { @@ -18,9 +16,7 @@ where match game.state() { GameState::Playable => { - let game_any = &game as &dyn Any; - - if let Some(partizan) + println!("Player {:?} to move", game.player()); let move_scores = par_move_scores(&game); let mut move_scores = move_scores diff --git a/rust-toolchain b/rust-toolchain index fe60ec1..414f779 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,5 +5,5 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.76" +channel = "1.81" components = [ "rustfmt", "clippy" ]