UI cleanup

master
Mark 2024-03-05 11:43:23 -08:00
parent 76a1bd423c
commit 7394e9db0b
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
6 changed files with 455 additions and 107 deletions

234
Cargo.lock generated
View File

@ -2,6 +2,54 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.80" version = "1.0.80"
@ -27,29 +75,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "crossbeam-deque" name = "clap"
version = "0.8.5" version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [ dependencies = [
"crossbeam-epoch", "clap_builder",
"crossbeam-utils", "clap_derive",
] ]
[[package]] [[package]]
name = "crossbeam-epoch" name = "clap_builder"
version = "0.9.18" version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [ dependencies = [
"crossbeam-utils", "anstream",
"anstyle",
"clap_lex",
"strsim",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "clap_derive"
version = "0.8.19" version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "either" name = "either"
@ -68,6 +137,12 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.12.1" version = "0.12.1"
@ -105,9 +180,9 @@ name = "ops"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap",
"itertools", "itertools",
"rand", "rand",
"rayon",
"termion", "termion",
] ]
@ -117,6 +192,24 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -147,26 +240,6 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rayon"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.4.1" version = "0.4.1"
@ -182,6 +255,23 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "syn"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "termion" name = "termion"
version = "3.0.0" version = "3.0.0"
@ -194,8 +284,86 @@ dependencies = [
"redox_termios", "redox_termios",
] ]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"

View File

@ -5,7 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.80" anyhow = "1.0.80"
clap = { version = "4.5.1", features = ["derive"] }
itertools = "0.12.1" itertools = "0.12.1"
rand = "0.8.5" rand = "0.8.5"
rayon = "1.9.0"
termion = "3.0.0" termion = "3.0.0"

View File

@ -1,23 +1,31 @@
mod diffuse; mod diffuse;
mod human;
mod minimax; mod minimax;
mod player;
mod random; mod random;
pub mod util; pub mod util;
pub use diffuse::Diffuse; pub use diffuse::Diffuse;
pub use human::Human;
pub use minimax::SimpleMinimax; pub use minimax::SimpleMinimax;
pub use player::PlayerAgent; pub use random::Random;
pub use random::RandomAgent;
use crate::board::{Board, PlayerAction}; use crate::{
board::{Board, PlayerAction},
util::Player,
};
use anyhow::Result; 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. /// 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>; fn step_min(&mut self, board: &Board) -> Result<PlayerAction>;
} }
/// An agent that tries to maximize the value of a board. /// 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>; 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 std::cmp::Ordering;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use clap::Parser;
use termion::color::{self}; use termion::color::{self};
mod agents; mod agents;
mod board; mod board;
mod cli;
mod util; mod util;
use board::Board; use board::Board;
use util::{Player, Symb}; 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( fn play(
maxi: &mut dyn agents::MaximizerAgent, maxi: &mut dyn agents::MaximizerAgent,
maxi_player: Player,
mini: &mut dyn agents::MinimizerAgent, mini: &mut dyn agents::MinimizerAgent,
mini_player: Player,
) -> Result<Board> { ) -> Result<Board> {
let mut board = Board::new(); let mut board = Board::new();
let mut is_first_turn = true; let mut is_first_turn = true;
@ -40,12 +80,20 @@ fn play(
if !board.play( if !board.play(
action, action,
if is_maxi_turn { if is_maxi_turn {
maxi_player maxi.player()
} else { } 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; is_maxi_turn = !is_maxi_turn;
@ -57,75 +105,140 @@ fn play(
} }
fn main() -> Result<()> { fn main() -> Result<()> {
rayon::ThreadPoolBuilder::new() let cli = cli::Cli::parse();
.num_threads(4)
.build_global()
.unwrap();
let mut maxi = agents::PlayerAgent::new(Player::Human); if cli.repeat > 1 {
let mut mini = agents::Diffuse {}; let mut red_wins = 0f32;
let mut blue_wins = 0f32;
let a = play(&mut maxi, Player::Human, &mut mini, Player::Computer)?; for _ in 0..cli.repeat {
if a.is_done() { 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!( println!(
"\r\n{}Your score:{} {:.2}\n\n", "Red win rate: {:.2} ({})",
color::Fg(Player::Human.color()), red_wins / cli.repeat as f32,
color::Fg(color::Reset), cli.red.to_string(),
a.evaluate().unwrap() );
println!(
"Blue win rate: {:.2} ({}) ",
blue_wins / cli.repeat as f32,
cli.blue.to_string(),
); );
} else { } else {
println!( let mut maxi = cli.red.to_maxi(Player::Red);
"\r\n{}Quitting{}\r\n", let mut mini = cli.blue.to_mini(Player::Blue);
color::Fg(color::Red), let red_board = if cli.silent {
color::Fg(color::Reset), play_silent(&mut *maxi, &mut *mini)
); } else {
return Ok(()); play(&mut *maxi, &mut *mini)
} }?;
let mut mini = agents::PlayerAgent::new(Player::Human); if red_board.is_done() {
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) => {
println!( println!(
"\r\n{}Human wins{}", "\r\n{}{} score:{} {:.2}\n\n",
color::Fg(Player::Human.color()), color::Fg(maxi.player().color()),
maxi.name(),
color::Fg(color::Reset), color::Fg(color::Reset),
red_board.evaluate().unwrap()
); );
} } else {
Some(Ordering::Less) => {
println!( println!(
"\r\n{}Computer wins{}", "\r\n{}Quitting{}\r\n",
color::Fg(Player::Computer.color()),
color::Fg(color::Reset),
);
}
None => {
println!(
"\r\n{}Error{}",
color::Fg(color::Red), color::Fg(color::Red),
color::Fg(color::Reset), 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)] #[derive(PartialEq, Eq, Clone, Copy)]
pub enum Player { pub enum Player {
Human, Red,
Computer, Blue,
} }
impl Player { impl Player {
pub fn color(&self) -> &dyn Color { pub fn color(&self) -> &dyn Color {
match self { match self {
Player::Computer => &color::LightBlack, Player::Red => &color::LightRed,
Player::Human => &color::Magenta, Player::Blue => &color::LightBlue,
} }
} }
} }
@ -23,8 +23,8 @@ impl Player {
impl Display for Player { impl Display for Player {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self { let s = match self {
Self::Computer => "Max", Self::Red => "Red ",
Self::Human => "Min", Self::Blue => "Blue",
}; };
write!(f, "{s}") write!(f, "{s}")
} }