mirror of
https://github.com/Noratrieb/minmax.git
synced 2026-01-16 16:25:07 +01:00
improvements
This commit is contained in:
parent
afa61289a5
commit
a643d11021
6 changed files with 47 additions and 43 deletions
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue