diff --git a/src/agents/diffuse.rs b/src/agents/diffuse.rs new file mode 100644 index 0000000..a656f45 --- /dev/null +++ b/src/agents/diffuse.rs @@ -0,0 +1,105 @@ +use super::PlayerAgent; +use crate::util::Symb; +use rand::Rng; +use std::num::NonZeroU8; + +/// A simple "operator diffusion" MINIMIZER agent. +/// +/// Tries to keep operators as far apart as possible, denying large numbers. +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) { + 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.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(); + println!("{:?}", dist); + + 'outer: loop { + for i in 0..11 { + if dist[i] >= max_dist { + if board.play(i, symb) { + break 'outer; + }; + } + } + 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 { + fn step(&mut self, board: &mut crate::board::Board) { + 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) + } else { + self.step_symb(board, *symb.unwrap()) + } + } +} diff --git a/src/agents/mod.rs b/src/agents/mod.rs new file mode 100644 index 0000000..39bceeb --- /dev/null +++ b/src/agents/mod.rs @@ -0,0 +1,11 @@ +mod diffuse; +mod random; + +pub use diffuse::DiffuseAgent; +pub use random::RandomAgent; + +use crate::board::Board; + +pub trait PlayerAgent { + fn step(&mut self, board: &mut Board); +} diff --git a/src/random.rs b/src/agents/random.rs similarity index 95% rename from src/random.rs rename to src/agents/random.rs index ed7ede6..5052a52 100644 --- a/src/random.rs +++ b/src/agents/random.rs @@ -1,8 +1,7 @@ -use std::num::NonZeroU8; - +use super::PlayerAgent; +use crate::util::Symb; use rand::Rng; - -use crate::{util::Symb, PlayerAgent}; +use std::num::NonZeroU8; pub struct RandomAgent {} diff --git a/src/main.rs b/src/main.rs index b1ec09c..b27f7ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,22 +11,16 @@ use termion::{ raw::IntoRawMode, }; +mod agents; mod board; -mod random; mod util; use board::Board; use util::{Player, Symb}; -use crate::random::RandomAgent; - -pub trait PlayerAgent { - fn step(&mut self, board: &mut Board); -} - fn play( stdout: &mut StdoutLock, player_max: bool, - computer: &mut dyn PlayerAgent, + computer: &mut dyn agents::PlayerAgent, ) -> Result { let mut cursor = 0usize; let cursor_offset = 10usize - 1; @@ -88,6 +82,7 @@ fn play( break; } + // Player turn let stdin = stdin(); for c in stdin.keys() { print_board = match c.unwrap() { @@ -112,10 +107,10 @@ fn play( Key::Char('8') => board.play(cursor, Symb::Number(NonZeroU8::new(8).unwrap())), Key::Char('9') => board.play(cursor, Symb::Number(NonZeroU8::new(9).unwrap())), Key::Char('0') => board.play(cursor, Symb::Zero), - Key::Char('a') => board.play(cursor, Symb::Plus), - Key::Char('s') => board.play(cursor, Symb::Minus), - Key::Char('m') => board.play(cursor, Symb::Times), - Key::Char('d') => board.play(cursor, Symb::Div), + Key::Char('+') => board.play(cursor, Symb::Plus), + Key::Char('-') => board.play(cursor, Symb::Minus), + Key::Char('*') => board.play(cursor, Symb::Times), + Key::Char('/') => board.play(cursor, Symb::Div), _ => false, }; continue 'outer; @@ -129,7 +124,7 @@ fn main() -> Result<()> { let stdout = HideCursor::from(stdout().into_raw_mode().unwrap()); let mut stdout = stdout.lock(); - let mut agent = RandomAgent {}; + let mut agent = agents::DiffuseAgent {}; let a = play(&mut stdout, true, &mut agent)?; if a.is_done() { println!( @@ -147,7 +142,7 @@ fn main() -> Result<()> { return Ok(()); } - let mut agent = RandomAgent {}; + let mut agent = agents::RandomAgent {}; let b = play(&mut stdout, false, &mut agent)?; if b.is_done() { println!( diff --git a/src/util.rs b/src/util.rs index d6f0a50..3dade5a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -59,9 +59,9 @@ impl Display for Symb { impl Symb { /// Is this symbol a plain binary operator? - pub fn is_binop(&self) -> bool { + pub fn is_op(&self) -> bool { match self { - Symb::Div | Symb::Plus | Symb::Times => true, + Symb::Div | Symb::Plus | Symb::Times | Symb::Minus => true, _ => false, } }