From 5b0c3106c079248bc5edc5253be23aeb5c8ca9e1 Mon Sep 17 00:00:00 2001 From: nils <48135649+Nilstrieb@users.noreply.github.com> Date: Mon, 5 Dec 2022 11:05:23 +0100 Subject: [PATCH] cli --- Cargo.lock | 376 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/connect4/board.rs | 4 +- src/lib.rs | 47 ++++- src/main.rs | 111 ++++++++++- src/minmax.rs | 52 ++--- src/tic_tac_toe/board.rs | 2 +- src/tic_tac_toe/game.rs | 4 - src/tic_tac_toe/mod.rs | 18 +- src/tic_tac_toe/perfect.rs | 118 ------------ src/tic_tac_toe/player.rs | 11 +- 11 files changed, 560 insertions(+), 185 deletions(-) delete mode 100644 src/tic_tac_toe/perfect.rs diff --git a/Cargo.lock b/Cargo.lock index 69d4be2..7c06bd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,382 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + [[package]] name = "minmax" version = "0.1.0" +dependencies = [ + "clap", + "rand", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustix" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index d89143c..ce58d84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.0.29", features = ["derive"] } +rand = "0.8.5" [profile.dev] diff --git a/src/connect4/board.rs b/src/connect4/board.rs index 56558a2..5c7c39f 100644 --- a/src/connect4/board.rs +++ b/src/connect4/board.rs @@ -3,7 +3,7 @@ use std::{ ops::{Index, IndexMut}, }; -use crate::{minmax::Score, Game, Player, State}; +use crate::{Game, Player, Score, State}; type Position = Option; @@ -130,7 +130,7 @@ impl IndexMut for Connect4 { impl Game for Connect4 { type Move = usize; - const REASONABLE_SEARCH_DEPTH: Option = Some(5); + const REASONABLE_SEARCH_DEPTH: Option = Some(7); fn empty() -> Self { Self::new() diff --git a/src/lib.rs b/src/lib.rs index 216bb3e..9dd3cfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,15 +12,28 @@ pub mod tic_tac_toe; mod player; -use std::fmt::Display; +use std::{fmt::Display, ops::Neg}; -use minmax::Score; +pub use self::minmax::PerfectPlayer; pub use player::{Player, State}; -pub trait GamePlayer: Default { +pub trait GamePlayer { fn next_move(&mut self, board: &mut G, this_player: Player); } +impl + ?Sized> GamePlayer for &mut P { + fn next_move(&mut self, board: &mut G, this_player: Player) { + P::next_move(self, board, this_player) + } +} + + +impl + ?Sized> GamePlayer for Box

{ + fn next_move(&mut self, board: &mut G, this_player: Player) { + P::next_move(self, board, this_player) + } +} + pub trait Game: Display { type Move: Copy; @@ -65,3 +78,31 @@ pub trait Game: Display { } } } + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Score(i32); + +impl Score { + const MIN: Self = Self(i32::MIN); + const LOST: Self = Self(-100); + const TIE: Self = Self(0); + const WON: Self = Self(100); + + pub fn new(int: i32) -> Self { + Self(int) + } + + fn randomize(self) -> Self { + let score = self.0 as f32; + let rand = rand::thread_rng(); + self + } +} + +impl Neg for Score { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(-self.0) + } +} diff --git a/src/main.rs b/src/main.rs index ad43ee3..aa35a45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,103 @@ -#![allow(unused_imports)] +#![feature(let_chains)] -use std::{fmt::Display, time::SystemTime}; +use std::{fmt::Display, str::FromStr, time::SystemTime}; +use clap::{Parser, ValueEnum}; use minmax::{ connect4::{self, board::Connect4}, - tic_tac_toe::{GreedyPlayer, HumanPlayer, PerfectPlayer, TicTacToe}, - Game, GamePlayer, Player, + tic_tac_toe::{self, TicTacToe}, + Game, GamePlayer, PerfectPlayer, Player, }; +#[derive(Debug, Clone)] +enum PlayerConfig { + Human, + Perfect { depth: Option }, +} + +impl FromStr for PlayerConfig { + type Err = String; + + fn from_str(s: &str) -> Result { + let mut parts = s.split(":"); + let mut player = match parts + .next() + .ok_or_else(|| "No player name provided".to_owned())? + { + "human" | "h" => Self::Human, + "perfect" | "p" | "ai" | "minmax" => Self::Perfect { depth: None }, + string => { + return Err(format!( + "Invalid player: {string}. Available players: human,perfect" + )) + } + }; + + if let Some(depth) = parts.next() + && let Self::Perfect { depth: player_depth } = &mut player + { + match depth.parse() { + Ok(depth) => *player_depth = Some(depth), + Err(err) => return Err(format!("Invalid depth: {depth}. {err}")), + } + } + + Ok(player) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] +enum GameType { + TicTacToe, + Connect4, +} + +#[derive(Debug, Parser)] +#[command(author, version, about)] +struct Args { + #[arg(short, long)] + game: GameType, + #[arg(short)] + x: PlayerConfig, + #[arg(short)] + o: PlayerConfig, +} + fn main() { - play::(true); + let args = Args::parse(); + + match args.game { + GameType::Connect4 => { + let get_player = |player| -> Box> { + match player { + PlayerConfig::Human => Box::new(connect4::HumanPlayer), + PlayerConfig::Perfect { depth } => { + Box::new(PerfectPlayer::new().with_max_depth(depth)) + } + } + }; + + let player_a = get_player(args.o); + let player_b = get_player(args.x); + + play_with_players(player_a, player_b); + } + GameType::TicTacToe => { + let get_player = |player| -> Box> { + match player { + PlayerConfig::Human => Box::new(tic_tac_toe::HumanPlayer), + PlayerConfig::Perfect { depth } => { + Box::new(PerfectPlayer::new().with_max_depth(depth)) + } + } + }; + + let player_a = get_player(args.o); + let player_b = get_player(args.x); + + play_with_players(player_a, player_b); + } + } } #[allow(dead_code)] @@ -19,7 +107,7 @@ fn tic_tac_toe_stats() { let start = SystemTime::now(); for _ in 0..100 { - let result = play::(false); + let result = play::, tic_tac_toe::GreedyPlayer, _>(false); let idx = Player::as_u8(result); results[idx as usize] += 1; } @@ -34,7 +122,16 @@ fn tic_tac_toe_stats() { println!("Completed in {}ms", time.as_millis()); } -fn play, O: GamePlayer, G: Game>(print: bool) -> Option { +fn play_with_players, O: GamePlayer>(mut x: X, mut o: O) { + let mut board = G::empty(); + let result = board.play(&mut x, &mut o); + + print_result(result, board); +} + +fn play + Default, O: GamePlayer + Default, G: Game>( + print: bool, +) -> Option { let mut board = G::empty(); let result = board.play(&mut X::default(), &mut O::default()); if print { diff --git a/src/minmax.rs b/src/minmax.rs index 95dc173..7a78d76 100644 --- a/src/minmax.rs +++ b/src/minmax.rs @@ -1,46 +1,32 @@ -use std::ops::Neg; - -use crate::{Game, GamePlayer, Player, State}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Score(i32); - -impl Score { - const LOST: Self = Self(i32::MIN); - const TIE: Self = Self(0); - const WON: Self = Self(i32::MAX); - - pub fn new(int: i32) -> Self { - Self(int) - } -} - -impl Neg for Score { - type Output = Self; - - fn neg(self) -> Self::Output { - Self(-self.0) - } -} +use crate::{Game, GamePlayer, Player, Score, State}; #[derive(Clone)] -pub struct PerfectPlayer { - best_move: Option, +pub struct PerfectPlayer { + best_move: Option, + max_depth: Option, } -impl Default for PerfectPlayer { +impl Default for PerfectPlayer { fn default() -> Self { Self::new() } } -impl PerfectPlayer { +impl PerfectPlayer { pub fn new() -> Self { - Self { best_move: None } + Self { + best_move: None, + max_depth: G::REASONABLE_SEARCH_DEPTH, + } } - fn minmax(&mut self, board: &mut B, player: Player, depth: usize) -> Score { - if let Some(max_depth) = B::REASONABLE_SEARCH_DEPTH && depth >= max_depth { + pub fn with_max_depth(mut self, max_depth: Option) -> Self { + self.max_depth = max_depth; + self + } + + fn minmax(&mut self, board: &mut G, player: Player, depth: usize) -> Score { + if let Some(max_depth) = self.max_depth && depth >= max_depth { return board.rate(player); } @@ -54,7 +40,7 @@ impl PerfectPlayer { } State::Draw => Score::TIE, State::InProgress => { - let mut max_value = Score::LOST; + let mut max_value = Score::MIN; for pos in board.possible_moves() { board.make_move(pos, player); @@ -78,6 +64,8 @@ impl PerfectPlayer { impl GamePlayer for PerfectPlayer { fn next_move(&mut self, board: &mut G, this_player: Player) { + println!("{board}"); + self.best_move = None; self.minmax(board, this_player, 0); diff --git a/src/tic_tac_toe/board.rs b/src/tic_tac_toe/board.rs index 7acaf6b..8857494 100644 --- a/src/tic_tac_toe/board.rs +++ b/src/tic_tac_toe/board.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Write}; -use crate::{minmax::Score, Player, State, Game}; +use crate::{Game, Player, Score, State}; #[derive(Clone)] pub struct TicTacToe(u32); diff --git a/src/tic_tac_toe/game.rs b/src/tic_tac_toe/game.rs index 004a9f5..6faeb88 100644 --- a/src/tic_tac_toe/game.rs +++ b/src/tic_tac_toe/game.rs @@ -3,10 +3,6 @@ use crate::{GamePlayer, Player, State}; use super::TicTacToe; impl TicTacToe { - pub fn default_play, O: GamePlayer>() -> Option { - Self::empty().play(&mut X::default(), &mut O::default()) - } - pub fn play, B: GamePlayer>( &mut self, x: &mut A, diff --git a/src/tic_tac_toe/mod.rs b/src/tic_tac_toe/mod.rs index d72e14e..ccc7bca 100644 --- a/src/tic_tac_toe/mod.rs +++ b/src/tic_tac_toe/mod.rs @@ -1,27 +1,25 @@ mod board; mod game; -mod perfect; mod player; -pub use {board::TicTacToe, perfect::PerfectPlayer, player::*}; +pub use {board::TicTacToe, player::*}; #[cfg(test)] mod tests { - use crate::{tic_tac_toe::board::TicTacToe, GamePlayer, Player}; + use crate::{minmax::PerfectPlayer, tic_tac_toe::board::TicTacToe, GamePlayer, Player}; - use super::{ - perfect::PerfectPlayer, - player::{GreedyPlayer, RandomPlayer}, - }; + use super::player::{GreedyPlayer, RandomPlayer}; fn assert_win_ratio, O: GamePlayer>( runs: u64, x_win_ratio: f64, + x: impl Fn() -> X, + o: impl Fn() -> O, ) { let mut results = [0u64, 0, 0]; for _ in 0..runs { - let result = TicTacToe::default_play::(); + let result = TicTacToe::empty().play::(&mut x(), &mut o()); let idx = Player::as_u8(result); results[idx as usize] += 1; } @@ -35,11 +33,11 @@ mod tests { #[test] fn perfect_always_beats_greedy() { - assert_win_ratio::(20, 1.0); + assert_win_ratio(20, 1.0, || PerfectPlayer::new(), || GreedyPlayer); } #[test] fn perfect_beats_random() { - assert_win_ratio::(10, 0.95); + assert_win_ratio(10, 0.95, || PerfectPlayer::new(), || RandomPlayer); } } diff --git a/src/tic_tac_toe/perfect.rs b/src/tic_tac_toe/perfect.rs deleted file mode 100644 index 738a7d6..0000000 --- a/src/tic_tac_toe/perfect.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::ops::Neg; - -use crate::{GamePlayer, Player, State}; - -use super::TicTacToe; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -enum Score { - Lost = -1, - Tie = 0, - Won = 1, -} - -impl Neg for Score { - type Output = Self; - - fn neg(self) -> Self::Output { - match self { - Self::Lost => Self::Won, - Self::Tie => Self::Tie, - Self::Won => Self::Lost, - } - } -} - -#[derive(Clone)] -pub struct PerfectPlayer { - best_move: usize, -} - -impl Default for PerfectPlayer { - fn default() -> Self { - Self::new() - } -} - -impl PerfectPlayer { - pub fn new() -> Self { - Self { - best_move: usize::MAX, - } - } - - fn minmax(&mut self, board: &mut TicTacToe, player: Player, depth: usize) -> Score { - if depth < 2 { - //print!("{board}{}| playing {player}: ", " ".repeat(depth)); - } - match board.result() { - State::Winner(winner) => { - if depth < 2 { - //println!(" a winner {winner}"); - } - if winner == player { - Score::Won - } else { - Score::Lost - } - } - State::Draw => { - if depth < 2 { - //println!("this is gonna be a draw"); - } - Score::Tie - } - State::InProgress => { - if depth < 2 { - //println!("not done yet"); - } - let mut max_value = Score::Lost; - - debug_assert!( - !board.iter().all(|x| x.is_some()), - "the board is full but state is InProgress" - ); - - for (i, pos) in board.iter().enumerate() { - if pos.is_some() { - continue; - } - - board.set(i, Some(player)); - let value = -self.minmax(board, player.opponent(), depth + 1); - - if depth < 2 { - if i == 8 { - //println!("AAA\n{board}AAAA"); - } - //println!("{}^| {i} {player} -> {:?}", " ".repeat(depth), value); - } - - board.set(i, None); - - if value > max_value { - max_value = value; - if depth == 0 { - self.best_move = i; - } - } - } - - max_value - } - } - } -} - -impl GamePlayer for PerfectPlayer { - fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) { - self.best_move = usize::MAX; - self.minmax(board, this_player, 0); - - if self.best_move == usize::MAX { - panic!("could not make a move"); - } - - board.set(self.best_move, Some(this_player)); - } -} diff --git a/src/tic_tac_toe/player.rs b/src/tic_tac_toe/player.rs index a82ed66..508c3d1 100644 --- a/src/tic_tac_toe/player.rs +++ b/src/tic_tac_toe/player.rs @@ -1,5 +1,7 @@ use std::io::Write; +use rand::Rng; + use crate::{GamePlayer, Player}; use super::TicTacToe; @@ -47,17 +49,10 @@ impl GamePlayer for HumanPlayer { #[derive(Clone, Default)] pub struct RandomPlayer; -fn fun_random() -> u64 { - use std::hash::{BuildHasher, Hasher}; - std::collections::hash_map::RandomState::new() - .build_hasher() - .finish() -} - impl GamePlayer for RandomPlayer { fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) { loop { - let next = (fun_random() % 9) as usize; + let next = rand::thread_rng().gen_range(0..9); match board.get(next) { Some(_) => {} None => {