use anyhow::Result; use rand::{seq::SliceRandom, thread_rng}; use super::{Agent, Chase, MaximizerAgent, MinimizerAgent, Random}; use crate::{ board::{Board, PlayerAction}, util::{Player, Symb}, }; /// A simple "operator diffusion" MINIMIZER agent. /// /// Tries to keep operators as far apart as possible, denying large numbers. /// Places numbers using the same algorithm as chase. pub struct Diffuse { player: Player, } impl Diffuse { pub fn new(player: Player) -> Self { Self { player } } /// Place a symbol on the board. /// Assumes `symb` is not already on the board 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!") } // Fill distance array with largest possible value let mut dist = [board.size() + 1; 11]; // Set up initial distances dist[0] = 1; *dist.last_mut().unwrap() = 1; for (i, o) in board.get_board().iter().enumerate() { if let Some(s) = o { if s.is_op() { dist[i] = 0 } } } let mut did_something = true; while did_something { did_something = false; for i in 1..(dist.len() - 1) { let l = dist[i - 1]; let r = dist[i + 1]; let new = (l + 1).min(r + 1); if new < dist[i] { did_something = true; dist[i] = new; } } } let mut max_dist = *dist.iter().max().unwrap(); loop { for (pos, d) in dist.iter().enumerate() { if *d >= max_dist { let action = PlayerAction { symb, pos }; if board.can_play(&action) { return action; }; } } if max_dist == 0 { return Random::new(self.player).step_max(board).unwrap(); } max_dist -= 1; } } } impl Agent for Diffuse { fn name(&self) -> &'static str { "Diffuse" } fn player(&self) -> Player { self.player } } impl MinimizerAgent for Diffuse { fn step_min(&mut self, board: &Board) -> Result { let mut x = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div]; x.shuffle(&mut thread_rng()); let symb = x.iter().find(|x| !board.contains(**x)); if let Some(symb) = symb { Ok(self.step_symb(board, *symb)) } else { // No symbols available, play a random number Chase::new(self.player).step_min(board) } } } impl MaximizerAgent for Diffuse { fn step_max(&mut self, board: &Board) -> Result { let mut x = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div]; x.shuffle(&mut thread_rng()); let symb = x.iter().find(|x| !board.contains(**x)); if let Some(symb) = symb { Ok(self.step_symb(board, *symb)) } else { // No symbols available, play a random number Chase::new(self.player).step_max(board) } } }