From 5c95aa753648e7b14295a02bc432fe50f8dc81df Mon Sep 17 00:00:00 2001 From: nils <48135649+Nilstrieb@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:20:46 +0100 Subject: [PATCH] move around --- src/lib.rs | 109 ++----------------------------- src/main.rs | 7 +- src/{ => tic_tac_toe}/board.rs | 0 src/{ => tic_tac_toe}/game.rs | 8 ++- src/tic_tac_toe/mod.rs | 54 +++++++++++++++ src/{ => tic_tac_toe}/perfect.rs | 6 +- src/tic_tac_toe/player.rs | 70 ++++++++++++++++++++ 7 files changed, 144 insertions(+), 110 deletions(-) rename src/{ => tic_tac_toe}/board.rs (100%) rename src/{ => tic_tac_toe}/game.rs (68%) create mode 100644 src/tic_tac_toe/mod.rs rename src/{ => tic_tac_toe}/perfect.rs (95%) create mode 100644 src/tic_tac_toe/player.rs diff --git a/src/lib.rs b/src/lib.rs index 2cca4c4..bd91328 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,113 +1,16 @@ #![feature(never_type, try_trait_v2)] -mod board; pub mod connect4; -mod game; -mod perfect; +pub mod tic_tac_toe; + mod player; -use std::io::Write; - -pub use board::Board; -pub use perfect::PerfectPlayer; pub use player::{Player, State}; -pub trait GamePlayer: Default { - fn next_move(&mut self, board: &mut Board, this_player: Player); +pub trait Game { + type Board; } -#[derive(Clone, Default)] -pub struct GreedyPlayer; - -impl GamePlayer for GreedyPlayer { - fn next_move(&mut self, board: &mut Board, 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; - -impl GamePlayer for HumanPlayer { - fn next_move(&mut self, board: &mut Board, this_player: Player) { - loop { - print!("{board}where to put the next {this_player}? (0-8): "); - - std::io::stdout().flush().unwrap(); - let mut buf = String::new(); - std::io::stdin().read_line(&mut buf).unwrap(); - - match buf.trim().parse() { - Ok(number) if number < 9 => match board.get(number) { - None => { - board.set(number, Some(this_player)); - return; - } - Some(_) => { - println!("Field is occupied already.") - } - }, - Ok(_) | Err(_) => { - println!("Invalid input.") - } - } - } - } -} - -#[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 Board, this_player: Player) { - loop { - let next = (fun_random() % 9) as usize; - match board.get(next) { - Some(_) => {} - None => { - board.set(next, Some(this_player)); - return; - } - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::{Board, GamePlayer, GreedyPlayer, PerfectPlayer, Player, RandomPlayer}; - - fn assert_win_ratio(runs: u64, x_win_ratio: f64) { - let mut results = [0u64, 0, 0]; - - for _ in 0..runs { - let result = Board::default_play::(); - let idx = Player::as_u8(result); - results[idx as usize] += 1; - } - - let total = results.iter().copied().sum::(); - - let ratio = (total as f64) / (results[0] as f64); - println!("{ratio} >= {x_win_ratio}"); - assert!(ratio >= x_win_ratio); - } - - #[test] - fn perfect_always_beats_greedy() { - assert_win_ratio::(20, 1.0); - } - - #[test] - fn perfect_beats_random() { - assert_win_ratio::(10, 0.95); - } +pub trait GamePlayer: Default { + fn next_move(&mut self, board: &mut G::Board, this_player: Player); } diff --git a/src/main.rs b/src/main.rs index cfa83cc..cc07429 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,10 @@ use std::time::SystemTime; -use minmax::{Board, GamePlayer, GreedyPlayer, HumanPlayer, PerfectPlayer, Player, RandomPlayer}; +use minmax::{ + tic_tac_toe::{Board, GreedyPlayer, HumanPlayer, PerfectPlayer, TicTacToe}, + GamePlayer, Player, +}; fn main() { let mut results = [0, 0, 0]; @@ -25,7 +28,7 @@ fn main() { println!("Completed in {}ms", time.as_millis()); } -fn play_round(print: bool) -> Option { +fn play_round, O: GamePlayer>(print: bool) -> Option { let mut board = Board::empty(); let result = board.play(&mut X::default(), &mut O::default()); if print { diff --git a/src/board.rs b/src/tic_tac_toe/board.rs similarity index 100% rename from src/board.rs rename to src/tic_tac_toe/board.rs diff --git a/src/game.rs b/src/tic_tac_toe/game.rs similarity index 68% rename from src/game.rs rename to src/tic_tac_toe/game.rs index c09ee64..1ce6b02 100644 --- a/src/game.rs +++ b/src/tic_tac_toe/game.rs @@ -1,11 +1,13 @@ -use crate::{Board, GamePlayer, Player, State}; +use crate::{GamePlayer, Player, State}; + +use super::{board::Board, TicTacToe}; impl Board { - pub fn default_play() -> Option { + pub fn default_play, O: GamePlayer>() -> Option { Self::empty().play(&mut X::default(), &mut O::default()) } - pub fn play(&mut self, x: &mut A, o: &mut B) -> Option { + pub fn play, B: GamePlayer>(&mut self, x: &mut A, o: &mut B) -> Option { let mut current_player = Player::X; for _ in 0..9 { diff --git a/src/tic_tac_toe/mod.rs b/src/tic_tac_toe/mod.rs new file mode 100644 index 0000000..1efddec --- /dev/null +++ b/src/tic_tac_toe/mod.rs @@ -0,0 +1,54 @@ +use crate::Game; + +mod board; +mod game; +mod perfect; +mod player; + +pub use {board::Board, perfect::PerfectPlayer, player::*}; + +pub struct TicTacToe; + +impl Game for TicTacToe { + type Board = board::Board; +} + +#[cfg(test)] +mod tests { + use crate::{tic_tac_toe::board::Board, GamePlayer, Player}; + + use super::{ + perfect::PerfectPlayer, + player::{GreedyPlayer, RandomPlayer}, + TicTacToe, + }; + + fn assert_win_ratio, O: GamePlayer>( + runs: u64, + x_win_ratio: f64, + ) { + let mut results = [0u64, 0, 0]; + + for _ in 0..runs { + let result = Board::default_play::(); + let idx = Player::as_u8(result); + results[idx as usize] += 1; + } + + let total = results.iter().copied().sum::(); + + let ratio = (total as f64) / (results[0] as f64); + println!("{ratio} >= {x_win_ratio}"); + assert!(ratio >= x_win_ratio); + } + + #[test] + fn perfect_always_beats_greedy() { + assert_win_ratio::(20, 1.0); + } + + #[test] + fn perfect_beats_random() { + assert_win_ratio::(10, 0.95); + } +} diff --git a/src/perfect.rs b/src/tic_tac_toe/perfect.rs similarity index 95% rename from src/perfect.rs rename to src/tic_tac_toe/perfect.rs index ddf0798..a93c8cf 100644 --- a/src/perfect.rs +++ b/src/tic_tac_toe/perfect.rs @@ -1,6 +1,8 @@ use std::ops::Neg; -use crate::{Board, GamePlayer, Player, State}; +use crate::{GamePlayer, Player, State}; + +use super::{board::Board, TicTacToe}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] enum Score { @@ -102,7 +104,7 @@ impl PerfectPlayer { } } -impl GamePlayer for PerfectPlayer { +impl GamePlayer for PerfectPlayer { fn next_move(&mut self, board: &mut Board, this_player: Player) { self.best_move = usize::MAX; self.minmax(board, this_player, 0); diff --git a/src/tic_tac_toe/player.rs b/src/tic_tac_toe/player.rs new file mode 100644 index 0000000..88f315b --- /dev/null +++ b/src/tic_tac_toe/player.rs @@ -0,0 +1,70 @@ +use std::io::Write; + +use crate::{GamePlayer, Player}; + +use super::{board::Board, TicTacToe}; + +#[derive(Clone, Default)] +pub struct GreedyPlayer; + +impl GamePlayer for GreedyPlayer { + fn next_move(&mut self, board: &mut Board, 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; + +impl GamePlayer for HumanPlayer { + fn next_move(&mut self, board: &mut Board, this_player: Player) { + loop { + print!("{board}where to put the next {this_player}? (0-8): "); + + std::io::stdout().flush().unwrap(); + let mut buf = String::new(); + std::io::stdin().read_line(&mut buf).unwrap(); + + match buf.trim().parse() { + Ok(number) if number < 9 => match board.get(number) { + None => { + board.set(number, Some(this_player)); + return; + } + Some(_) => { + println!("Field is occupied already.") + } + }, + Ok(_) | Err(_) => { + println!("Invalid input.") + } + } + } + } +} + +#[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 Board, this_player: Player) { + loop { + let next = (fun_random() % 9) as usize; + match board.get(next) { + Some(_) => {} + None => { + board.set(next, Some(this_player)); + return; + } + } + } + } +}