UI cleanup
This commit is contained in:
@@ -1,23 +1,31 @@
|
||||
mod diffuse;
|
||||
mod human;
|
||||
mod minimax;
|
||||
mod player;
|
||||
mod random;
|
||||
pub mod util;
|
||||
|
||||
pub use diffuse::Diffuse;
|
||||
pub use human::Human;
|
||||
pub use minimax::SimpleMinimax;
|
||||
pub use player::PlayerAgent;
|
||||
pub use random::RandomAgent;
|
||||
pub use random::Random;
|
||||
|
||||
use crate::board::{Board, PlayerAction};
|
||||
use crate::{
|
||||
board::{Board, PlayerAction},
|
||||
util::Player,
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
||||
pub trait Agent {
|
||||
fn name(&self) -> &'static str;
|
||||
fn player(&self) -> Player;
|
||||
}
|
||||
|
||||
/// An agent that tries to minimize the value of a board.
|
||||
pub trait MinimizerAgent {
|
||||
pub trait MinimizerAgent: Agent {
|
||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction>;
|
||||
}
|
||||
|
||||
/// An agent that tries to maximize the value of a board.
|
||||
pub trait MaximizerAgent {
|
||||
pub trait MaximizerAgent: Agent {
|
||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction>;
|
||||
}
|
||||
|
||||
59
src/cli.rs
Normal file
59
src/cli.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
use crate::{
|
||||
agents::{Diffuse, Human, MaximizerAgent, MinimizerAgent, Random, SimpleMinimax},
|
||||
util::Player,
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about)]
|
||||
pub struct Cli {
|
||||
pub red: AgentSelector,
|
||||
pub blue: AgentSelector,
|
||||
|
||||
#[arg(long, short, default_value = "0")]
|
||||
pub repeat: usize,
|
||||
|
||||
#[arg(long, short, default_value = "false")]
|
||||
pub silent: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
pub enum AgentSelector {
|
||||
Random,
|
||||
Diffuse,
|
||||
Minimax,
|
||||
Human,
|
||||
}
|
||||
|
||||
impl AgentSelector {
|
||||
pub fn to_maxi(&self, player: Player) -> Box<dyn MaximizerAgent> {
|
||||
match self {
|
||||
Self::Random => Box::new(Random::new(player)),
|
||||
Self::Minimax => Box::new(SimpleMinimax::new(player)),
|
||||
Self::Diffuse => Box::new(Diffuse::new(player)),
|
||||
Self::Human => Box::new(Human::new(player)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_mini(&self, player: Player) -> Box<dyn MinimizerAgent> {
|
||||
match self {
|
||||
Self::Random => Box::new(Random::new(player)),
|
||||
Self::Minimax => Box::new(SimpleMinimax::new(player)),
|
||||
Self::Diffuse => Box::new(Diffuse::new(player)),
|
||||
Self::Human => Box::new(Human::new(player)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for AgentSelector {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::Random => "random",
|
||||
Self::Diffuse => "diffuse",
|
||||
Self::Minimax => "minimax",
|
||||
Self::Human => "human",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
235
src/main.rs
235
src/main.rs
@@ -1,19 +1,59 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use termion::color::{self};
|
||||
|
||||
mod agents;
|
||||
mod board;
|
||||
mod cli;
|
||||
mod util;
|
||||
use board::Board;
|
||||
use util::{Player, Symb};
|
||||
|
||||
fn play_silent(
|
||||
maxi: &mut dyn agents::MaximizerAgent,
|
||||
mini: &mut dyn agents::MinimizerAgent,
|
||||
) -> Result<Board> {
|
||||
let mut board = Board::new();
|
||||
let mut is_maxi_turn = true;
|
||||
|
||||
while !board.is_done() {
|
||||
// Take action
|
||||
let action = if is_maxi_turn {
|
||||
maxi.step_max(&board)?
|
||||
} else {
|
||||
mini.step_min(&board)?
|
||||
};
|
||||
|
||||
if !board.play(
|
||||
action,
|
||||
if is_maxi_turn {
|
||||
maxi.player()
|
||||
} else {
|
||||
mini.player()
|
||||
},
|
||||
) {
|
||||
bail!(
|
||||
"agent {} made an invalid move {}!",
|
||||
if is_maxi_turn {
|
||||
maxi.name()
|
||||
} else {
|
||||
mini.name()
|
||||
},
|
||||
action
|
||||
)
|
||||
}
|
||||
|
||||
is_maxi_turn = !is_maxi_turn;
|
||||
}
|
||||
|
||||
Ok(board)
|
||||
}
|
||||
|
||||
fn play(
|
||||
maxi: &mut dyn agents::MaximizerAgent,
|
||||
maxi_player: Player,
|
||||
mini: &mut dyn agents::MinimizerAgent,
|
||||
mini_player: Player,
|
||||
) -> Result<Board> {
|
||||
let mut board = Board::new();
|
||||
let mut is_first_turn = true;
|
||||
@@ -40,12 +80,20 @@ fn play(
|
||||
if !board.play(
|
||||
action,
|
||||
if is_maxi_turn {
|
||||
maxi_player
|
||||
maxi.player()
|
||||
} else {
|
||||
mini_player
|
||||
mini.player()
|
||||
},
|
||||
) {
|
||||
bail!("agent made invalid move")
|
||||
bail!(
|
||||
"agent {} made an invalid move {}!",
|
||||
if is_maxi_turn {
|
||||
maxi.name()
|
||||
} else {
|
||||
mini.name()
|
||||
},
|
||||
action
|
||||
)
|
||||
}
|
||||
|
||||
is_maxi_turn = !is_maxi_turn;
|
||||
@@ -57,75 +105,140 @@ fn play(
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(4)
|
||||
.build_global()
|
||||
.unwrap();
|
||||
let cli = cli::Cli::parse();
|
||||
|
||||
let mut maxi = agents::PlayerAgent::new(Player::Human);
|
||||
let mut mini = agents::Diffuse {};
|
||||
if cli.repeat > 1 {
|
||||
let mut red_wins = 0f32;
|
||||
let mut blue_wins = 0f32;
|
||||
|
||||
let a = play(&mut maxi, Player::Human, &mut mini, Player::Computer)?;
|
||||
if a.is_done() {
|
||||
for _ in 0..cli.repeat {
|
||||
let mut maxi = cli.red.to_maxi(Player::Red);
|
||||
let mut mini = cli.blue.to_mini(Player::Blue);
|
||||
let red_board = match if cli.silent {
|
||||
play_silent(&mut *maxi, &mut *mini)
|
||||
} else {
|
||||
play(&mut *maxi, &mut *mini)
|
||||
} {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
println!("Error: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut maxi = cli.blue.to_maxi(Player::Blue);
|
||||
let mut mini = cli.red.to_mini(Player::Red);
|
||||
let blue_board = match if cli.silent {
|
||||
play_silent(&mut *maxi, &mut *mini)
|
||||
} else {
|
||||
play(&mut *maxi, &mut *mini)
|
||||
} {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
println!("Error: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match red_board.evaluate().partial_cmp(&blue_board.evaluate()) {
|
||||
Some(Ordering::Equal) => {}
|
||||
Some(Ordering::Greater) => red_wins += 1.0,
|
||||
Some(Ordering::Less) => blue_wins += 1.0,
|
||||
None => {
|
||||
println!("Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Played {} rounds\n", cli.repeat);
|
||||
println!(
|
||||
"\r\n{}Your score:{} {:.2}\n\n",
|
||||
color::Fg(Player::Human.color()),
|
||||
color::Fg(color::Reset),
|
||||
a.evaluate().unwrap()
|
||||
"Red win rate: {:.2} ({})",
|
||||
red_wins / cli.repeat as f32,
|
||||
cli.red.to_string(),
|
||||
);
|
||||
println!(
|
||||
"Blue win rate: {:.2} ({}) ",
|
||||
blue_wins / cli.repeat as f32,
|
||||
cli.blue.to_string(),
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\r\n{}Quitting{}\r\n",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let mut maxi = cli.red.to_maxi(Player::Red);
|
||||
let mut mini = cli.blue.to_mini(Player::Blue);
|
||||
let red_board = if cli.silent {
|
||||
play_silent(&mut *maxi, &mut *mini)
|
||||
} else {
|
||||
play(&mut *maxi, &mut *mini)
|
||||
}?;
|
||||
|
||||
let mut mini = agents::PlayerAgent::new(Player::Human);
|
||||
let mut maxi = agents::Diffuse {};
|
||||
|
||||
let b = play(&mut maxi, Player::Computer, &mut mini, Player::Human)?;
|
||||
if b.is_done() {
|
||||
println!(
|
||||
"\r\n{}Computer score:{} {:.2}\n\n",
|
||||
color::Fg(Player::Human.color()),
|
||||
color::Fg(color::Reset),
|
||||
b.evaluate().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\r\n{}Quitting{}\r\n",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match a.evaluate().partial_cmp(&b.evaluate()) {
|
||||
Some(Ordering::Equal) => {
|
||||
println!("\r\nTie");
|
||||
}
|
||||
Some(Ordering::Greater) => {
|
||||
if red_board.is_done() {
|
||||
println!(
|
||||
"\r\n{}Human wins{}",
|
||||
color::Fg(Player::Human.color()),
|
||||
"\r\n{}{} score:{} {:.2}\n\n",
|
||||
color::Fg(maxi.player().color()),
|
||||
maxi.name(),
|
||||
color::Fg(color::Reset),
|
||||
red_board.evaluate().unwrap()
|
||||
);
|
||||
}
|
||||
Some(Ordering::Less) => {
|
||||
} else {
|
||||
println!(
|
||||
"\r\n{}Computer wins{}",
|
||||
color::Fg(Player::Computer.color()),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"\r\n{}Error{}",
|
||||
"\r\n{}Quitting{}\r\n",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut maxi = cli.blue.to_maxi(Player::Blue);
|
||||
let mut mini = cli.red.to_mini(Player::Red);
|
||||
let blue_board = if cli.silent {
|
||||
play_silent(&mut *maxi, &mut *mini)
|
||||
} else {
|
||||
play(&mut *maxi, &mut *mini)
|
||||
}?;
|
||||
|
||||
if blue_board.is_done() {
|
||||
println!(
|
||||
"\r\n{}{} score:{} {:.2}\n\n",
|
||||
color::Fg(maxi.player().color()),
|
||||
maxi.name(),
|
||||
color::Fg(color::Reset),
|
||||
blue_board.evaluate().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\r\n{}Quitting{}\r\n",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match red_board.evaluate().partial_cmp(&blue_board.evaluate()) {
|
||||
Some(Ordering::Equal) => {
|
||||
println!("\r\nTie");
|
||||
}
|
||||
Some(Ordering::Greater) => {
|
||||
println!(
|
||||
"\r\n{}Red ({}){} wins!",
|
||||
color::Fg(Player::Red.color()),
|
||||
cli.red.to_string(),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
}
|
||||
Some(Ordering::Less) => {
|
||||
println!(
|
||||
"\r\n{}Blue ({}){} wins!",
|
||||
color::Fg(Player::Blue.color()),
|
||||
cli.blue.to_string(),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"\r\n{}Error{}",
|
||||
color::Fg(color::Red),
|
||||
color::Fg(color::Reset),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
12
src/util.rs
12
src/util.rs
@@ -7,15 +7,15 @@ use termion::color::{self, Color};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Player {
|
||||
Human,
|
||||
Computer,
|
||||
Red,
|
||||
Blue,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn color(&self) -> &dyn Color {
|
||||
match self {
|
||||
Player::Computer => &color::LightBlack,
|
||||
Player::Human => &color::Magenta,
|
||||
Player::Red => &color::LightRed,
|
||||
Player::Blue => &color::LightBlue,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ impl Player {
|
||||
impl Display for Player {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
Self::Computer => "Max",
|
||||
Self::Human => "Min",
|
||||
Self::Red => "Red ",
|
||||
Self::Blue => "Blue",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user