diff --git a/minmax-rs/Cargo.toml b/minmax-rs/Cargo.toml index 9e6c8d8..3f34e8c 100644 --- a/minmax-rs/Cargo.toml +++ b/minmax-rs/Cargo.toml @@ -8,5 +8,3 @@ edition = "2021" [dependencies] clap = { version = "4.0.29", features = ["derive"] } rand = "0.8.5" - - diff --git a/minmax-rs/src/lib.rs b/minmax-rs/src/lib.rs index 3883424..e4de693 100644 --- a/minmax-rs/src/lib.rs +++ b/minmax-rs/src/lib.rs @@ -10,7 +10,7 @@ pub mod connect4; mod minmax; pub mod tic_tac_toe; -mod player; +pub mod player; use std::{fmt::Display, ops::Neg}; @@ -27,7 +27,6 @@ impl + ?Sized> GamePlayer for &mut P { } } - impl + ?Sized> GamePlayer for Box

{ fn next_move(&mut self, board: &mut G, this_player: Player) { P::next_move(self, board, this_player) diff --git a/minmax-rs/src/main.rs b/minmax-rs/src/main.rs index 7c38d22..8d6004a 100644 --- a/minmax-rs/src/main.rs +++ b/minmax-rs/src/main.rs @@ -5,6 +5,7 @@ use std::{fmt::Display, str::FromStr, time::SystemTime}; use clap::{Parser, ValueEnum}; use minmax::{ connect4::{self, board::Connect4}, + player::{GreedyPlayer, RandomPlayer}, tic_tac_toe::{self, TicTacToe}, Game, GamePlayer, PerfectPlayer, Player, }; @@ -12,6 +13,8 @@ use minmax::{ #[derive(Debug, Clone)] enum PlayerConfig { Human, + Greedy, + Random, Perfect { depth: Option }, } @@ -26,6 +29,8 @@ impl FromStr for PlayerConfig { { "human" | "h" => Self::Human, "perfect" | "p" | "ai" | "minmax" => Self::Perfect { depth: None }, + "greedy" | "g" => Self::Greedy, + "random" | "r" => Self::Random, string => { return Err(format!( "Invalid player: {string}. Available players: human,perfect" @@ -73,9 +78,12 @@ fn main() { let get_player = |player| -> Box> { match player { PlayerConfig::Human => Box::new(connect4::HumanPlayer), - PlayerConfig::Perfect { depth } => { - Box::new(PerfectPlayer::new(!args.no_print_time).with_max_depth(depth)) - } + PlayerConfig::Greedy => Box::new(GreedyPlayer), + PlayerConfig::Random => Box::new(RandomPlayer), + PlayerConfig::Perfect { depth } => Box::new( + PerfectPlayer::new(!args.no_print_time) + .with_max_depth(depth.or(Connect4::REASONABLE_SEARCH_DEPTH)), + ), } }; @@ -88,9 +96,12 @@ fn main() { let get_player = |player| -> Box> { match player { PlayerConfig::Human => Box::new(tic_tac_toe::HumanPlayer), - PlayerConfig::Perfect { depth } => { - Box::new(PerfectPlayer::new(!args.no_print_time).with_max_depth(depth)) - } + PlayerConfig::Greedy => Box::new(GreedyPlayer), + PlayerConfig::Random => Box::new(RandomPlayer), + PlayerConfig::Perfect { depth } => Box::new( + PerfectPlayer::new(!args.no_print_time) + .with_max_depth(depth.or(TicTacToe::REASONABLE_SEARCH_DEPTH)), + ), } }; @@ -109,7 +120,7 @@ fn tic_tac_toe_stats() { let start = SystemTime::now(); for _ in 0..100 { - let result = play::, tic_tac_toe::GreedyPlayer, _>(false); + let result = play::, GreedyPlayer, _>(false); let idx = Player::as_u8(result); results[idx as usize] += 1; } diff --git a/minmax-rs/src/player.rs b/minmax-rs/src/player.rs index 8f36caf..b76ddc8 100644 --- a/minmax-rs/src/player.rs +++ b/minmax-rs/src/player.rs @@ -3,6 +3,10 @@ use std::{ ops::{ControlFlow, Try}, }; +use rand::Rng; + +use crate::{Game, GamePlayer}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Player { X, @@ -84,3 +88,25 @@ impl Try for State { } } } + +#[derive(Clone, Default)] +pub struct GreedyPlayer; + +impl GamePlayer for GreedyPlayer { + fn next_move(&mut self, board: &mut G, this_player: Player) { + let first_free = board.possible_moves().next().expect("cannot make move"); + board.make_move(first_free, this_player); + } +} + +#[derive(Clone, Default)] +pub struct RandomPlayer; + +impl GamePlayer for RandomPlayer { + fn next_move(&mut self, board: &mut G, this_player: Player) { + let moves = board.possible_moves().collect::>(); + + let selected = rand::thread_rng().gen_range(0..moves.len()); + board.make_move(moves[selected], this_player); + } +} diff --git a/minmax-rs/src/tic_tac_toe/mod.rs b/minmax-rs/src/tic_tac_toe/mod.rs index e8325b9..4af6e6d 100644 --- a/minmax-rs/src/tic_tac_toe/mod.rs +++ b/minmax-rs/src/tic_tac_toe/mod.rs @@ -8,7 +8,7 @@ pub use {board::TicTacToe, player::*}; mod tests { use crate::{minmax::PerfectPlayer, tic_tac_toe::board::TicTacToe, GamePlayer, Player}; - use super::player::{GreedyPlayer, RandomPlayer}; + use crate::player::{GreedyPlayer, RandomPlayer}; fn assert_win_ratio, O: GamePlayer>( runs: u64, @@ -33,7 +33,7 @@ mod tests { #[test] fn perfect_always_beats_greedy() { - assert_win_ratio(20, 1.0, || PerfectPlayer::new(false), || GreedyPlayer); + assert_win_ratio(1, 1.0, || PerfectPlayer::new(false), || GreedyPlayer); } #[test] diff --git a/minmax-rs/src/tic_tac_toe/player.rs b/minmax-rs/src/tic_tac_toe/player.rs index 508c3d1..c85f838 100644 --- a/minmax-rs/src/tic_tac_toe/player.rs +++ b/minmax-rs/src/tic_tac_toe/player.rs @@ -1,21 +1,9 @@ use std::io::Write; -use rand::Rng; - use crate::{GamePlayer, Player}; use super::TicTacToe; -#[derive(Clone, Default)] -pub struct GreedyPlayer; - -impl GamePlayer for GreedyPlayer { - fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) { - let first_free = board.iter().position(|p| p.is_none()).unwrap(); - board.set(first_free, Some(this_player)); - } -} - #[derive(Clone, Default)] pub struct HumanPlayer; @@ -45,21 +33,3 @@ impl GamePlayer for HumanPlayer { } } } - -#[derive(Clone, Default)] -pub struct RandomPlayer; - -impl GamePlayer for RandomPlayer { - fn next_move(&mut self, board: &mut TicTacToe, this_player: Player) { - loop { - let next = rand::thread_rng().gen_range(0..9); - match board.get(next) { - Some(_) => {} - None => { - board.set(next, Some(this_player)); - return; - } - } - } - } -}