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 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;
};
}
}
if max_dist == 0 {
panic!("there is no valid move!")
}
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) {
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())
}

View File

@ -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;
}

View File

@ -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
}
}

View File

@ -108,6 +108,11 @@ impl TreeElement {
}
}
pub struct PlayerAction {
pub symb: Symb,
pub pos: usize,
}
impl TreeElement {
pub fn evaluate(&self) -> Option<f32> {
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

View File

@ -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<Board> {
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!(