improvements

This commit is contained in:
nora 2023-01-14 16:33:20 +01:00
parent afa61289a5
commit a643d11021
6 changed files with 47 additions and 43 deletions

View file

@ -8,5 +8,3 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.0.29", features = ["derive"] } clap = { version = "4.0.29", features = ["derive"] }
rand = "0.8.5" rand = "0.8.5"

View file

@ -10,7 +10,7 @@ pub mod connect4;
mod minmax; mod minmax;
pub mod tic_tac_toe; pub mod tic_tac_toe;
mod player; pub mod player;
use std::{fmt::Display, ops::Neg}; use std::{fmt::Display, ops::Neg};
@ -27,7 +27,6 @@ impl<G: Game, P: GamePlayer<G> + ?Sized> GamePlayer<G> for &mut P {
} }
} }
impl<G: Game, P: GamePlayer<G> + ?Sized> GamePlayer<G> for Box<P> { impl<G: Game, P: GamePlayer<G> + ?Sized> GamePlayer<G> for Box<P> {
fn next_move(&mut self, board: &mut G, this_player: Player) { fn next_move(&mut self, board: &mut G, this_player: Player) {
P::next_move(self, board, this_player) P::next_move(self, board, this_player)

View file

@ -5,6 +5,7 @@ use std::{fmt::Display, str::FromStr, time::SystemTime};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use minmax::{ use minmax::{
connect4::{self, board::Connect4}, connect4::{self, board::Connect4},
player::{GreedyPlayer, RandomPlayer},
tic_tac_toe::{self, TicTacToe}, tic_tac_toe::{self, TicTacToe},
Game, GamePlayer, PerfectPlayer, Player, Game, GamePlayer, PerfectPlayer, Player,
}; };
@ -12,6 +13,8 @@ use minmax::{
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum PlayerConfig { enum PlayerConfig {
Human, Human,
Greedy,
Random,
Perfect { depth: Option<usize> }, Perfect { depth: Option<usize> },
} }
@ -26,6 +29,8 @@ impl FromStr for PlayerConfig {
{ {
"human" | "h" => Self::Human, "human" | "h" => Self::Human,
"perfect" | "p" | "ai" | "minmax" => Self::Perfect { depth: None }, "perfect" | "p" | "ai" | "minmax" => Self::Perfect { depth: None },
"greedy" | "g" => Self::Greedy,
"random" | "r" => Self::Random,
string => { string => {
return Err(format!( return Err(format!(
"Invalid player: {string}. Available players: human,perfect" "Invalid player: {string}. Available players: human,perfect"
@ -73,9 +78,12 @@ fn main() {
let get_player = |player| -> Box<dyn GamePlayer<Connect4>> { let get_player = |player| -> Box<dyn GamePlayer<Connect4>> {
match player { match player {
PlayerConfig::Human => Box::new(connect4::HumanPlayer), PlayerConfig::Human => Box::new(connect4::HumanPlayer),
PlayerConfig::Perfect { depth } => { PlayerConfig::Greedy => Box::new(GreedyPlayer),
Box::new(PerfectPlayer::new(!args.no_print_time).with_max_depth(depth)) 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<dyn GamePlayer<TicTacToe>> { let get_player = |player| -> Box<dyn GamePlayer<TicTacToe>> {
match player { match player {
PlayerConfig::Human => Box::new(tic_tac_toe::HumanPlayer), PlayerConfig::Human => Box::new(tic_tac_toe::HumanPlayer),
PlayerConfig::Perfect { depth } => { PlayerConfig::Greedy => Box::new(GreedyPlayer),
Box::new(PerfectPlayer::new(!args.no_print_time).with_max_depth(depth)) 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(); let start = SystemTime::now();
for _ in 0..100 { for _ in 0..100 {
let result = play::<PerfectPlayer<TicTacToe>, tic_tac_toe::GreedyPlayer, _>(false); let result = play::<PerfectPlayer<TicTacToe>, GreedyPlayer, _>(false);
let idx = Player::as_u8(result); let idx = Player::as_u8(result);
results[idx as usize] += 1; results[idx as usize] += 1;
} }

View file

@ -3,6 +3,10 @@ use std::{
ops::{ControlFlow, Try}, ops::{ControlFlow, Try},
}; };
use rand::Rng;
use crate::{Game, GamePlayer};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Player { pub enum Player {
X, X,
@ -84,3 +88,25 @@ impl Try for State {
} }
} }
} }
#[derive(Clone, Default)]
pub struct GreedyPlayer;
impl<G: Game> GamePlayer<G> 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<G: Game> GamePlayer<G> for RandomPlayer {
fn next_move(&mut self, board: &mut G, this_player: Player) {
let moves = board.possible_moves().collect::<Vec<_>>();
let selected = rand::thread_rng().gen_range(0..moves.len());
board.make_move(moves[selected], this_player);
}
}

View file

@ -8,7 +8,7 @@ pub use {board::TicTacToe, player::*};
mod tests { mod tests {
use crate::{minmax::PerfectPlayer, tic_tac_toe::board::TicTacToe, GamePlayer, Player}; 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<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>( fn assert_win_ratio<X: GamePlayer<TicTacToe>, O: GamePlayer<TicTacToe>>(
runs: u64, runs: u64,
@ -33,7 +33,7 @@ mod tests {
#[test] #[test]
fn perfect_always_beats_greedy() { 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] #[test]

View file

@ -1,21 +1,9 @@
use std::io::Write; use std::io::Write;
use rand::Rng;
use crate::{GamePlayer, Player}; use crate::{GamePlayer, Player};
use super::TicTacToe; use super::TicTacToe;
#[derive(Clone, Default)]
pub struct GreedyPlayer;
impl GamePlayer<TicTacToe> 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)] #[derive(Clone, Default)]
pub struct HumanPlayer; pub struct HumanPlayer;
@ -45,21 +33,3 @@ impl GamePlayer<TicTacToe> for HumanPlayer {
} }
} }
} }
#[derive(Clone, Default)]
pub struct RandomPlayer;
impl GamePlayer<TicTacToe> 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;
}
}
}
}
}