diff --git a/src/agents/diffuse.rs b/src/agents/diffuse.rs index a4fc81f..7a4e4a8 100644 --- a/src/agents/diffuse.rs +++ b/src/agents/diffuse.rs @@ -1,7 +1,8 @@ -use super::PlayerAgent; -use crate::util::Symb; -use rand::Rng; -use std::num::NonZeroU8; +use super::{MinimizerAgent, RandomAgent}; +use crate::{ + board::{Board, PlayerAction}, + util::Symb, +}; /// A simple "operator diffusion" MINIMIZER agent. /// @@ -11,7 +12,7 @@ pub struct DiffuseAgent {} impl DiffuseAgent { /// Place a symbol 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) { 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(); - 'outer: loop { - for i in 0..11 { - if dist[i] >= max_dist { - if board.play(i, symb) { - break 'outer; + loop { + for pos in 0..11 { + if dist[pos] >= max_dist { + let action = PlayerAction { symb, pos }; + if board.can_play(&action) { + return action; }; } } - 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()) + if max_dist == 0 { + panic!("there is no valid move!") } - }; - 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()) - } - }; + max_dist -= 1; } } } -impl PlayerAgent for DiffuseAgent { - fn step(&mut self, board: &mut crate::board::Board) { +impl MinimizerAgent for DiffuseAgent { + fn step_min(&mut self, board: &Board) -> PlayerAction { let symb = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div] .iter() .filter(|x| !board.contains(**x)) .next(); - // No symbols available, play a random number if symb.is_none() { - self.step_number(board) + // No symbols available, play a random number + RandomAgent {}.step_min(board) } else { self.step_symb(board, *symb.unwrap()) } diff --git a/src/agents/mod.rs b/src/agents/mod.rs index 39bceeb..6dc3951 100644 --- a/src/agents/mod.rs +++ b/src/agents/mod.rs @@ -1,11 +1,19 @@ mod diffuse; +mod minmaxtree; mod random; pub use diffuse::DiffuseAgent; +pub use minmaxtree::MinMaxTree; pub use random::RandomAgent; -use crate::board::Board; +use crate::board::{Board, PlayerAction}; -pub trait PlayerAgent { - fn step(&mut self, board: &mut Board); +/// An agent that tries to minimize the value of a 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; } diff --git a/src/agents/random.rs b/src/agents/random.rs index 5052a52..949161f 100644 --- a/src/agents/random.rs +++ b/src/agents/random.rs @@ -1,17 +1,21 @@ -use super::PlayerAgent; -use crate::util::Symb; +use crate::{ + board::{Board, PlayerAction}, + util::Symb, +}; use rand::Rng; use std::num::NonZeroU8; +use super::{MaximizerAgent, MinimizerAgent}; + pub struct RandomAgent {} -impl PlayerAgent for RandomAgent { - fn step(&mut self, board: &mut crate::board::Board) { +impl RandomAgent { + fn random_action(&self, board: &Board) -> PlayerAction { let mut rng = rand::thread_rng(); let n = board.size(); - let mut c = rng.gen_range(0..n); - let mut s = match rng.gen_range(0..4) { + let pos = rng.gen_range(0..n); + let symb = match rng.gen_range(0..4) { 0 => { let n = rng.gen_range(0..=9); if n == 0 { @@ -27,23 +31,26 @@ impl PlayerAgent for RandomAgent { _ => unreachable!(), }; - while !board.play(c, s) { - c = rng.gen_range(0..n); - s = match rng.gen_range(0..4) { - 0 => { - let n = rng.gen_range(0..=9); - if n == 0 { - Symb::Zero - } else { - Symb::Number(NonZeroU8::new(n).unwrap()) - } - } - 1 => Symb::Div, - 2 => Symb::Minus, - 3 => Symb::Plus, - 4 => Symb::Times, - _ => unreachable!(), - }; - } + PlayerAction { symb, pos } + } +} + +impl MinimizerAgent for RandomAgent { + fn step_min(&mut self, board: &Board) -> PlayerAction { + let mut action = self.random_action(board); + while !board.can_play(&action) { + action = self.random_action(board); + } + action + } +} + +impl MaximizerAgent for RandomAgent { + 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 } } diff --git a/src/board.rs b/src/board.rs index 2d6042e..d533ba0 100644 --- a/src/board.rs +++ b/src/board.rs @@ -108,6 +108,11 @@ impl TreeElement { } } +pub struct PlayerAction { + pub symb: Symb, + pub pos: usize, +} + impl TreeElement { pub fn evaluate(&self) -> Option { match self { @@ -230,33 +235,32 @@ impl Board { false } - // Place the marked symbol at the given position. - // Returns true for valid moves and false otherwise. - pub fn play(&mut self, cursor: usize, symb: Symb) -> bool { - match &self.board[cursor] { + /// Is the given action valid? + pub fn can_play(&self, action: &PlayerAction) -> bool { + match &self.board[action.pos] { Some(_) => return false, None => { // Check for duplicate symbols - if self.contains(symb) { + if self.contains(action.symb) { return false; } // Check syntax - match symb { + match action.symb { Symb::Minus => { - if cursor == self.board.len() - 1 { + if action.pos == self.board.len() - 1 { 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()) { return false; } } Symb::Zero => { - if cursor != 0 { - let l = &self.board[cursor - 1].map(|x| x.0); + if action.pos != 0 { + let l = &self.board[action.pos - 1].map(|x| x.0); if l == &Some(Symb::Div) { return false; } @@ -264,14 +268,14 @@ impl Board { } 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; } - let l = &self.board[cursor - 1].map(|x| x.0); - let r = &self.board[cursor + 1].map(|x| x.0); + let l = &self.board[action.pos - 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; } @@ -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.free_spots -= 1; true diff --git a/src/main.rs b/src/main.rs index 1ba3493..5e506e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,10 +14,12 @@ mod util; use board::Board; use util::{Player, Symb}; +use crate::board::PlayerAction; + fn play( stdout: &mut StdoutLock, player_max: bool, - computer: &mut dyn agents::PlayerAgent, + computer: &mut dyn agents::MinimizerAgent, ) -> Result { let mut cursor = 0usize; let cursor_offset = 10usize - 1; @@ -41,7 +43,7 @@ fn play( 'outer: loop { // Computer turn if board.current_player() == Player::Computer && !board.is_done() { - computer.step(&mut board); + computer.step_min(&mut board); } let min_color = if !player_max { @@ -146,17 +148,19 @@ fn play( false } Key::Char('\n') => { - let s = Symb::from_char(&symbols[selected_symbol]); - if let Some(s) = s { - board.play(cursor, s) + let symb = Symb::from_char(&symbols[selected_symbol]); + if let Some(symb) = symb { + let action = PlayerAction { symb, pos: cursor }; + board.play(action) } else { false } } Key::Char(c) => { - let s = Symb::from_char(&c); - if let Some(s) = s { - board.play(cursor, s) + let symb = Symb::from_char(&c); + if let Some(symb) = symb { + let action = PlayerAction { symb, pos: cursor }; + board.play(action) } else { false } @@ -174,7 +178,7 @@ fn main() -> Result<()> { let stdout = HideCursor::from(stdout().into_raw_mode().unwrap()); let mut stdout = stdout.lock(); - let mut agent = agents::MinMaxTree {}; + let mut agent = agents::DiffuseAgent {}; let a = play(&mut stdout, true, &mut agent)?; if a.is_done() { println!(