API cleanup
parent
207665dda4
commit
17a42356be
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
PlayerAction { symb, pos }
|
||||
}
|
||||
}
|
||||
1 => Symb::Div,
|
||||
2 => Symb::Minus,
|
||||
3 => Symb::Plus,
|
||||
4 => Symb::Times,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
45
src/board.rs
45
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<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
|
||||
|
|
22
src/main.rs
22
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<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!(
|
||||
|
|
Loading…
Reference in New Issue