API cleanup

master
Mark 2024-03-04 17:03:28 -08:00
parent 207665dda4
commit 17a42356be
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
5 changed files with 103 additions and 92 deletions

View File

@ -1,7 +1,8 @@
use super::PlayerAgent; use super::{MinimizerAgent, RandomAgent};
use crate::util::Symb; use crate::{
use rand::Rng; board::{Board, PlayerAction},
use std::num::NonZeroU8; util::Symb,
};
/// A simple "operator diffusion" MINIMIZER agent. /// A simple "operator diffusion" MINIMIZER agent.
/// ///
@ -11,7 +12,7 @@ pub struct DiffuseAgent {}
impl DiffuseAgent { impl DiffuseAgent {
/// Place a symbol on the board. /// Place a symbol on the board.
/// Assumes `symb` is not already on the board /// Assumes `symb` is not already on the board
fn step_symb(&self, board: &mut crate::board::Board, symb: Symb) { fn step_symb(&self, board: &Board, symb: Symb) -> PlayerAction {
if board.contains(symb) { if board.contains(symb) {
panic!("Called `step_symb` with a symbol that's already on the board!") panic!("Called `step_symb` with a symbol that's already on the board!")
} }
@ -46,57 +47,35 @@ impl DiffuseAgent {
} }
let mut max_dist = *dist.iter().max().unwrap(); let mut max_dist = *dist.iter().max().unwrap();
'outer: loop { loop {
for i in 0..11 { for pos in 0..11 {
if dist[i] >= max_dist { if dist[pos] >= max_dist {
if board.play(i, symb) { let action = PlayerAction { symb, pos };
break 'outer; if board.can_play(&action) {
return action;
}; };
} }
} }
if max_dist == 0 {
panic!("there is no valid move!")
}
max_dist -= 1; max_dist -= 1;
} }
} }
/// Called when we're out of symbols
fn step_number(&self, board: &mut crate::board::Board) {
let mut rng = rand::thread_rng();
let n = board.size();
let mut c = rng.gen_range(0..n);
let mut s = {
let n = rng.gen_range(0..=9);
if n == 0 {
Symb::Zero
} else {
Symb::Number(NonZeroU8::new(n).unwrap())
}
};
while !board.play(c, s) {
c = rng.gen_range(0..n);
s = {
let n = rng.gen_range(0..=9);
if n == 0 {
Symb::Zero
} else {
Symb::Number(NonZeroU8::new(n).unwrap())
}
};
}
}
} }
impl PlayerAgent for DiffuseAgent { impl MinimizerAgent for DiffuseAgent {
fn step(&mut self, board: &mut crate::board::Board) { fn step_min(&mut self, board: &Board) -> PlayerAction {
let symb = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div] let symb = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div]
.iter() .iter()
.filter(|x| !board.contains(**x)) .filter(|x| !board.contains(**x))
.next(); .next();
// No symbols available, play a random number
if symb.is_none() { if symb.is_none() {
self.step_number(board) // No symbols available, play a random number
RandomAgent {}.step_min(board)
} else { } else {
self.step_symb(board, *symb.unwrap()) self.step_symb(board, *symb.unwrap())
} }

View File

@ -1,11 +1,19 @@
mod diffuse; mod diffuse;
mod minmaxtree;
mod random; mod random;
pub use diffuse::DiffuseAgent; pub use diffuse::DiffuseAgent;
pub use minmaxtree::MinMaxTree;
pub use random::RandomAgent; pub use random::RandomAgent;
use crate::board::Board; use crate::board::{Board, PlayerAction};
pub trait PlayerAgent { /// An agent that tries to minimize the value of a board.
fn step(&mut self, board: &mut Board); pub trait MinimizerAgent {
fn step_min(&mut self, board: &Board) -> PlayerAction;
}
/// An agent that tries to maximize the value of a board.
pub trait MaximizerAgent {
fn step_max(&mut self, board: &Board) -> PlayerAction;
} }

View File

@ -1,17 +1,21 @@
use super::PlayerAgent; use crate::{
use crate::util::Symb; board::{Board, PlayerAction},
util::Symb,
};
use rand::Rng; use rand::Rng;
use std::num::NonZeroU8; use std::num::NonZeroU8;
use super::{MaximizerAgent, MinimizerAgent};
pub struct RandomAgent {} pub struct RandomAgent {}
impl PlayerAgent for RandomAgent { impl RandomAgent {
fn step(&mut self, board: &mut crate::board::Board) { fn random_action(&self, board: &Board) -> PlayerAction {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let n = board.size(); let n = board.size();
let mut c = rng.gen_range(0..n); let pos = rng.gen_range(0..n);
let mut s = match rng.gen_range(0..4) { let symb = match rng.gen_range(0..4) {
0 => { 0 => {
let n = rng.gen_range(0..=9); let n = rng.gen_range(0..=9);
if n == 0 { if n == 0 {
@ -27,23 +31,26 @@ impl PlayerAgent for RandomAgent {
_ => unreachable!(), _ => unreachable!(),
}; };
while !board.play(c, s) { PlayerAction { symb, pos }
c = rng.gen_range(0..n); }
s = match rng.gen_range(0..4) { }
0 => {
let n = rng.gen_range(0..=9); impl MinimizerAgent for RandomAgent {
if n == 0 { fn step_min(&mut self, board: &Board) -> PlayerAction {
Symb::Zero let mut action = self.random_action(board);
} else { while !board.can_play(&action) {
Symb::Number(NonZeroU8::new(n).unwrap()) action = self.random_action(board);
} }
} action
1 => Symb::Div, }
2 => Symb::Minus, }
3 => Symb::Plus,
4 => Symb::Times, impl MaximizerAgent for RandomAgent {
_ => unreachable!(), fn step_max(&mut self, board: &Board) -> PlayerAction {
}; let mut action = self.random_action(board);
} while !board.can_play(&action) {
action = self.random_action(board);
}
action
} }
} }

View File

@ -108,6 +108,11 @@ impl TreeElement {
} }
} }
pub struct PlayerAction {
pub symb: Symb,
pub pos: usize,
}
impl TreeElement { impl TreeElement {
pub fn evaluate(&self) -> Option<f32> { pub fn evaluate(&self) -> Option<f32> {
match self { match self {
@ -230,33 +235,32 @@ impl Board {
false false
} }
// Place the marked symbol at the given position. /// Is the given action valid?
// Returns true for valid moves and false otherwise. pub fn can_play(&self, action: &PlayerAction) -> bool {
pub fn play(&mut self, cursor: usize, symb: Symb) -> bool { match &self.board[action.pos] {
match &self.board[cursor] {
Some(_) => return false, Some(_) => return false,
None => { None => {
// Check for duplicate symbols // Check for duplicate symbols
if self.contains(symb) { if self.contains(action.symb) {
return false; return false;
} }
// Check syntax // Check syntax
match symb { match action.symb {
Symb::Minus => { Symb::Minus => {
if cursor == self.board.len() - 1 { if action.pos == self.board.len() - 1 {
return false; return false;
} }
let r = &self.board[cursor + 1]; let r = &self.board[action.pos + 1];
if r.is_some_and(|(s, _)| s.is_op() && !s.is_minus()) { if r.is_some_and(|(s, _)| s.is_op() && !s.is_minus()) {
return false; return false;
} }
} }
Symb::Zero => { Symb::Zero => {
if cursor != 0 { if action.pos != 0 {
let l = &self.board[cursor - 1].map(|x| x.0); let l = &self.board[action.pos - 1].map(|x| x.0);
if l == &Some(Symb::Div) { if l == &Some(Symb::Div) {
return false; return false;
} }
@ -264,14 +268,14 @@ impl Board {
} }
Symb::Div | Symb::Plus | Symb::Times => { Symb::Div | Symb::Plus | Symb::Times => {
if cursor == 0 || cursor == self.board.len() - 1 { if action.pos == 0 || action.pos == self.board.len() - 1 {
return false; return false;
} }
let l = &self.board[cursor - 1].map(|x| x.0); let l = &self.board[action.pos - 1].map(|x| x.0);
let r = &self.board[cursor + 1].map(|x| x.0); let r = &self.board[action.pos + 1].map(|x| x.0);
if symb == Symb::Div && r == &Some(Symb::Zero) { if action.symb == Symb::Div && r == &Some(Symb::Zero) {
return false; return false;
} }
@ -283,11 +287,20 @@ impl Board {
} }
_ => {} _ => {}
} }
self.board[cursor] = Some((symb, self.current_player));
} }
} }
true
}
/// Place the marked symbol at the given position.
/// Returns true for valid moves and false otherwise.
pub fn play(&mut self, action: PlayerAction) -> bool {
if !self.can_play(&action) {
return false;
}
self.board[action.pos] = Some((action.symb, self.current_player));
self.current_player.invert(); self.current_player.invert();
self.free_spots -= 1; self.free_spots -= 1;
true true

View File

@ -14,10 +14,12 @@ mod util;
use board::Board; use board::Board;
use util::{Player, Symb}; use util::{Player, Symb};
use crate::board::PlayerAction;
fn play( fn play(
stdout: &mut StdoutLock, stdout: &mut StdoutLock,
player_max: bool, player_max: bool,
computer: &mut dyn agents::PlayerAgent, computer: &mut dyn agents::MinimizerAgent,
) -> Result<Board> { ) -> Result<Board> {
let mut cursor = 0usize; let mut cursor = 0usize;
let cursor_offset = 10usize - 1; let cursor_offset = 10usize - 1;
@ -41,7 +43,7 @@ fn play(
'outer: loop { 'outer: loop {
// Computer turn // Computer turn
if board.current_player() == Player::Computer && !board.is_done() { if board.current_player() == Player::Computer && !board.is_done() {
computer.step(&mut board); computer.step_min(&mut board);
} }
let min_color = if !player_max { let min_color = if !player_max {
@ -146,17 +148,19 @@ fn play(
false false
} }
Key::Char('\n') => { Key::Char('\n') => {
let s = Symb::from_char(&symbols[selected_symbol]); let symb = Symb::from_char(&symbols[selected_symbol]);
if let Some(s) = s { if let Some(symb) = symb {
board.play(cursor, s) let action = PlayerAction { symb, pos: cursor };
board.play(action)
} else { } else {
false false
} }
} }
Key::Char(c) => { Key::Char(c) => {
let s = Symb::from_char(&c); let symb = Symb::from_char(&c);
if let Some(s) = s { if let Some(symb) = symb {
board.play(cursor, s) let action = PlayerAction { symb, pos: cursor };
board.play(action)
} else { } else {
false false
} }
@ -174,7 +178,7 @@ fn main() -> Result<()> {
let stdout = HideCursor::from(stdout().into_raw_mode().unwrap()); let stdout = HideCursor::from(stdout().into_raw_mode().unwrap());
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
let mut agent = agents::MinMaxTree {}; let mut agent = agents::DiffuseAgent {};
let a = play(&mut stdout, true, &mut agent)?; let a = play(&mut stdout, true, &mut agent)?;
if a.is_done() { if a.is_done() {
println!( println!(