minimax/src/agents/diffuse.rs
2024-03-06 09:56:04 -08:00

118 lines
2.6 KiB
Rust

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<PlayerAction> {
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<PlayerAction> {
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)
}
}
}