UI cleanup

This commit is contained in:
2024-03-05 11:43:23 -08:00
parent 76a1bd423c
commit 7394e9db0b
6 changed files with 455 additions and 107 deletions

View File

@@ -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
View 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()
}
}

View File

@@ -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),
);
}
}
}

View File

@@ -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}")
}