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