Internal tweaks
parent
171d8b8d6c
commit
76a1bd423c
|
@ -1,17 +1,23 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use super::{MaximizerAgent, MinimizerAgent, SimpleMinimax};
|
use super::{Agent, MaximizerAgent, MinimizerAgent, Random, SimpleMinimax};
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{Board, PlayerAction},
|
board::{Board, PlayerAction},
|
||||||
util::Symb,
|
util::{Player, Symb},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A simple "operator diffusion" MINIMIZER agent.
|
/// A simple "operator diffusion" MINIMIZER agent.
|
||||||
///
|
///
|
||||||
/// Tries to keep operators as far apart as possible, denying large numbers.
|
/// Tries to keep operators as far apart as possible, denying large numbers.
|
||||||
pub struct Diffuse {}
|
pub struct Diffuse {
|
||||||
|
player: Player,
|
||||||
|
}
|
||||||
|
|
||||||
impl Diffuse {
|
impl Diffuse {
|
||||||
|
pub fn new(player: Player) -> Self {
|
||||||
|
Self { player }
|
||||||
|
}
|
||||||
|
|
||||||
/// 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: &Board, symb: Symb) -> PlayerAction {
|
fn step_symb(&self, board: &Board, symb: Symb) -> PlayerAction {
|
||||||
|
@ -60,7 +66,7 @@ impl Diffuse {
|
||||||
}
|
}
|
||||||
|
|
||||||
if max_dist == 0 {
|
if max_dist == 0 {
|
||||||
panic!("there is no valid move!")
|
return Random::new(self.player).step_max(board).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
max_dist -= 1;
|
max_dist -= 1;
|
||||||
|
@ -68,6 +74,16 @@ impl Diffuse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Agent for Diffuse {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Diffuse"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player(&self) -> Player {
|
||||||
|
self.player
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MinimizerAgent for Diffuse {
|
impl MinimizerAgent for Diffuse {
|
||||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||||
let symb = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div]
|
let symb = [Symb::Minus, Symb::Times, Symb::Plus, Symb::Div]
|
||||||
|
@ -78,7 +94,7 @@ impl MinimizerAgent for Diffuse {
|
||||||
Ok(self.step_symb(board, *symb))
|
Ok(self.step_symb(board, *symb))
|
||||||
} else {
|
} else {
|
||||||
// No symbols available, play a random number
|
// No symbols available, play a random number
|
||||||
SimpleMinimax {}.step_min(board)
|
SimpleMinimax::new(self.player).step_min(board)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +109,7 @@ impl MaximizerAgent for Diffuse {
|
||||||
Ok(self.step_symb(board, *symb))
|
Ok(self.step_symb(board, *symb))
|
||||||
} else {
|
} else {
|
||||||
// No symbols available, play a random number
|
// No symbols available, play a random number
|
||||||
SimpleMinimax {}.step_max(board)
|
SimpleMinimax::new(self.player).step_max(board)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
util::{Player, Symb},
|
util::{Player, Symb},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{MaximizerAgent, MinimizerAgent};
|
use super::{Agent, MaximizerAgent, MinimizerAgent};
|
||||||
|
|
||||||
struct SymbolSelector {
|
struct SymbolSelector {
|
||||||
symbols: Vec<char>,
|
symbols: Vec<char>,
|
||||||
|
@ -68,13 +68,13 @@ impl SymbolSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PlayerAgent {
|
pub struct Human {
|
||||||
player: Player,
|
player: Player,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
symbol_selector: SymbolSelector,
|
symbol_selector: SymbolSelector,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerAgent {
|
impl Human {
|
||||||
pub fn new(player: Player) -> Self {
|
pub fn new(player: Player) -> Self {
|
||||||
Self {
|
Self {
|
||||||
player,
|
player,
|
||||||
|
@ -115,11 +115,7 @@ impl PlayerAgent {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| {
|
.map(|x| {
|
||||||
if board.contains(Symb::from_char(*x).unwrap()) {
|
if board.contains(Symb::from_char(*x).unwrap()) {
|
||||||
format!(
|
" ".to_string()
|
||||||
"{}{x}{}",
|
|
||||||
color::Fg(color::LightBlack),
|
|
||||||
color::Fg(color::Reset),
|
|
||||||
)
|
|
||||||
} else if *x == self.symbol_selector.current() {
|
} else if *x == self.symbol_selector.current() {
|
||||||
format!(
|
format!(
|
||||||
"{}{x}{}",
|
"{}{x}{}",
|
||||||
|
@ -138,6 +134,9 @@ impl PlayerAgent {
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
for c in stdin.keys() {
|
for c in stdin.keys() {
|
||||||
match c.unwrap() {
|
match c.unwrap() {
|
||||||
|
Key::Ctrl('c') => {
|
||||||
|
panic!("ctrl-c, exitint")
|
||||||
|
}
|
||||||
Key::Char('q') => bail!("player ended game"),
|
Key::Char('q') => bail!("player ended game"),
|
||||||
Key::Right => {
|
Key::Right => {
|
||||||
self.cursor = cursor_max.min(self.cursor + 1);
|
self.cursor = cursor_max.min(self.cursor + 1);
|
||||||
|
@ -190,13 +189,23 @@ impl PlayerAgent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MinimizerAgent for PlayerAgent {
|
impl Agent for Human {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Player"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player(&self) -> Player {
|
||||||
|
self.player
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MinimizerAgent for Human {
|
||||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||||
self.step(board, true)
|
self.step(board, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaximizerAgent for PlayerAgent {
|
impl MaximizerAgent for Human {
|
||||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||||
self.step(board, false)
|
self.step(board, false)
|
||||||
}
|
}
|
|
@ -1,16 +1,22 @@
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
|
|
||||||
use super::{MaximizerAgent, MinimizerAgent, RandomAgent};
|
use super::{Agent, MaximizerAgent, MinimizerAgent, Random};
|
||||||
use crate::{
|
use crate::{
|
||||||
agents::util::free_slots_by_influence,
|
agents::util::free_slots_by_influence,
|
||||||
board::{Board, PlayerAction},
|
board::{Board, PlayerAction},
|
||||||
util::Symb,
|
util::{Player, Symb},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SimpleMinimax {}
|
pub struct SimpleMinimax {
|
||||||
|
player: Player,
|
||||||
|
}
|
||||||
|
|
||||||
impl SimpleMinimax {
|
impl SimpleMinimax {
|
||||||
|
pub fn new(player: Player) -> Self {
|
||||||
|
Self { player }
|
||||||
|
}
|
||||||
|
|
||||||
fn step(&mut self, board: &Board, minimize: bool) -> Result<PlayerAction> {
|
fn step(&mut self, board: &Board, minimize: bool) -> Result<PlayerAction> {
|
||||||
let available_numbers = (0..=9)
|
let available_numbers = (0..=9)
|
||||||
.map(|x| match x {
|
.map(|x| match x {
|
||||||
|
@ -24,34 +30,64 @@ impl SimpleMinimax {
|
||||||
// min_slots + max_slots <= available_numbers.len
|
// min_slots + max_slots <= available_numbers.len
|
||||||
let n_free = board.get_board().iter().filter(|x| x.is_none()).count();
|
let n_free = board.get_board().iter().filter(|x| x.is_none()).count();
|
||||||
if available_numbers.len() < n_free || n_free >= 10 {
|
if available_numbers.len() < n_free || n_free >= 10 {
|
||||||
return RandomAgent {}.step_min(board);
|
return Random::new(self.player).step_min(board);
|
||||||
}
|
}
|
||||||
|
|
||||||
let t = free_slots_by_influence(&board);
|
let t = free_slots_by_influence(&board);
|
||||||
|
if t.is_none() {
|
||||||
|
bail!("could not compute next move!")
|
||||||
|
}
|
||||||
|
let t = t.unwrap();
|
||||||
|
|
||||||
if t.len() == 0 {
|
if t.len() == 0 {
|
||||||
return RandomAgent {}.step_min(board);
|
return Random::new(self.player).step_min(board);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (pos, val) = t[0];
|
let (pos, val) = t[0];
|
||||||
|
|
||||||
let symb = {
|
// Choose next number if we can't make the a move.
|
||||||
if minimize {
|
// Prevents division by zero.
|
||||||
if val >= 0.0 {
|
// This isn't perfect, and may fail if we run out of numbers
|
||||||
available_numbers[0]
|
// (This is, however, very unlikely)
|
||||||
|
let mut symb = None;
|
||||||
|
let mut offset = 0;
|
||||||
|
while symb.is_none()
|
||||||
|
|| !board.can_play(&PlayerAction {
|
||||||
|
symb: symb.unwrap(),
|
||||||
|
pos,
|
||||||
|
}) {
|
||||||
|
symb = Some({
|
||||||
|
if minimize {
|
||||||
|
if val >= 0.0 {
|
||||||
|
available_numbers[offset]
|
||||||
|
} else {
|
||||||
|
available_numbers[available_numbers.len() - 1 - offset]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
available_numbers[available_numbers.len() - 1]
|
if val <= 0.0 {
|
||||||
|
available_numbers[offset]
|
||||||
|
} else {
|
||||||
|
available_numbers[available_numbers.len() - 1 - offset]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
if val <= 0.0 {
|
offset += 1;
|
||||||
available_numbers[0]
|
}
|
||||||
} else {
|
|
||||||
available_numbers[available_numbers.len() - 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PlayerAction { symb, pos })
|
Ok(PlayerAction {
|
||||||
|
symb: symb.unwrap(),
|
||||||
|
pos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Agent for SimpleMinimax {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Minimax"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player(&self) -> Player {
|
||||||
|
self.player
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{Board, PlayerAction},
|
board::{Board, PlayerAction},
|
||||||
util::Symb,
|
util::{Player, Symb},
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
|
|
||||||
use super::{MaximizerAgent, MinimizerAgent};
|
use super::{Agent, MaximizerAgent, MinimizerAgent};
|
||||||
|
|
||||||
pub struct RandomAgent {}
|
pub struct Random {
|
||||||
|
player: Player,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Random {
|
||||||
|
pub fn new(player: Player) -> Self {
|
||||||
|
Self { player }
|
||||||
|
}
|
||||||
|
|
||||||
impl RandomAgent {
|
|
||||||
fn random_action(&self, board: &Board) -> PlayerAction {
|
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();
|
||||||
|
@ -36,7 +42,17 @@ impl RandomAgent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MinimizerAgent for RandomAgent {
|
impl Agent for Random {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"Random"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player(&self) -> Player {
|
||||||
|
self.player
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MinimizerAgent for Random {
|
||||||
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
fn step_min(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||||
let mut action = self.random_action(board);
|
let mut action = self.random_action(board);
|
||||||
while !board.can_play(&action) {
|
while !board.can_play(&action) {
|
||||||
|
@ -46,7 +62,7 @@ impl MinimizerAgent for RandomAgent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaximizerAgent for RandomAgent {
|
impl MaximizerAgent for Random {
|
||||||
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
fn step_max(&mut self, board: &Board) -> Result<PlayerAction> {
|
||||||
let mut action = self.random_action(board);
|
let mut action = self.random_action(board);
|
||||||
while !board.can_play(&action) {
|
while !board.can_play(&action) {
|
||||||
|
|
|
@ -8,14 +8,21 @@ use crate::{board::Board, util::Symb};
|
||||||
/// - coords are the coordinate of this slot's partial
|
/// - coords are the coordinate of this slot's partial
|
||||||
/// - char_idx is the index of this slot in its partial
|
/// - char_idx is the index of this slot in its partial
|
||||||
/// - f32 is the influence of this slot
|
/// - f32 is the influence of this slot
|
||||||
pub fn free_slots_by_influence(board: &Board) -> Vec<(usize, f32)> {
|
pub fn free_slots_by_influence(board: &Board) -> Option<Vec<(usize, f32)>> {
|
||||||
// Fill all empty slots with fives and compute starting value
|
// Fill all empty slots with fives and compute starting value
|
||||||
let filled = Board::from_board(board.get_board().map(|x| match x {
|
let filled = {
|
||||||
None => Symb::from_char('5'),
|
// This should always result in an evaluatable expression,
|
||||||
_ => x,
|
// since parenthesis do not exist.
|
||||||
}));
|
// (the only way to divide by zero is by doing something like /(5-2+3) )
|
||||||
|
let f = Board::from_board(board.get_board().map(|x| match x {
|
||||||
|
None => Symb::from_char('5'),
|
||||||
|
_ => x,
|
||||||
|
}));
|
||||||
|
|
||||||
let base = filled.evaluate().unwrap();
|
f
|
||||||
|
};
|
||||||
|
|
||||||
|
let base = filled.evaluate()?;
|
||||||
|
|
||||||
// Test each slot:
|
// Test each slot:
|
||||||
// Increase its value by 1, and record its effect on the
|
// Increase its value by 1, and record its effect on the
|
||||||
|
@ -36,13 +43,17 @@ pub fn free_slots_by_influence(board: &Board) -> Vec<(usize, f32)> {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort by most to least influence
|
// Sort by most to least influence
|
||||||
slots.sort_by(|a, b| b.1.abs().partial_cmp(&a.1.abs()).unwrap());
|
slots.sort_by(|a, b| {
|
||||||
slots
|
b.1.abs()
|
||||||
|
.partial_cmp(&a.1.abs())
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
});
|
||||||
|
Some(slots)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the maximum possible value of the given board
|
/// Find the maximum possible value of the given board
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn maximize_value(board: &Board) -> Board {
|
pub fn maximize_value(board: &Board) -> Option<Board> {
|
||||||
let n_free = board.get_board().iter().filter(|x| x.is_none()).count();
|
let n_free = board.get_board().iter().filter(|x| x.is_none()).count();
|
||||||
|
|
||||||
// Assume we have 10 or fewer available slots
|
// Assume we have 10 or fewer available slots
|
||||||
|
@ -58,7 +69,7 @@ pub fn maximize_value(board: &Board) -> Board {
|
||||||
.filter(|x| !board.contains(*x))
|
.filter(|x| !board.contains(*x))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let slots = free_slots_by_influence(&board);
|
let slots = free_slots_by_influence(&board)?;
|
||||||
|
|
||||||
let all_symbols = {
|
let all_symbols = {
|
||||||
// We need this many from the bottom, and this many from the top.
|
// We need this many from the bottom, and this many from the top.
|
||||||
|
@ -125,5 +136,5 @@ pub fn maximize_value(board: &Board) -> Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
best_board.unwrap()
|
best_board
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ enum InterTreeElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InterTreeElement {
|
impl InterTreeElement {
|
||||||
fn to_value(&self) -> TreeElement {
|
fn to_value(&self) -> Option<TreeElement> {
|
||||||
match self {
|
Some(match self {
|
||||||
InterTreeElement::Processed(x) => x.clone(),
|
InterTreeElement::Processed(x) => x.clone(),
|
||||||
InterTreeElement::Unprocessed(Token::Value(s)) => {
|
InterTreeElement::Unprocessed(Token::Value(s)) => {
|
||||||
if let Some(s) = s.strip_prefix('-') {
|
if let Some(s) = s.strip_prefix('-') {
|
||||||
|
@ -23,18 +23,24 @@ impl InterTreeElement {
|
||||||
if s.contains('_') {
|
if s.contains('_') {
|
||||||
Box::new(TreeElement::Partial(s.to_string()))
|
Box::new(TreeElement::Partial(s.to_string()))
|
||||||
} else {
|
} else {
|
||||||
Box::new(TreeElement::Number(s.parse().unwrap()))
|
Box::new(TreeElement::Number(match s.parse() {
|
||||||
|
Ok(x) => x,
|
||||||
|
_ => return None,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if s.contains('_') {
|
} else if s.contains('_') {
|
||||||
TreeElement::Partial(s.to_string())
|
TreeElement::Partial(s.to_string())
|
||||||
} else {
|
} else {
|
||||||
TreeElement::Number(s.parse().unwrap())
|
TreeElement::Number(match s.parse() {
|
||||||
|
Ok(x) => x,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => return None,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +102,7 @@ impl Board {
|
||||||
pub fn prettyprint(&self) -> Result<String> {
|
pub fn prettyprint(&self) -> Result<String> {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
// Print board
|
// Print board
|
||||||
for (i, (symb, p)) in self.board.iter().zip(self.placed_by.iter()).enumerate() {
|
for (i, (symb, _)) in self.board.iter().zip(self.placed_by.iter()).enumerate() {
|
||||||
match symb {
|
match symb {
|
||||||
Some(symb) => write!(
|
Some(symb) => write!(
|
||||||
s,
|
s,
|
||||||
|
@ -105,12 +111,9 @@ impl Board {
|
||||||
// If last_placed is None, this check will always fail
|
// If last_placed is None, this check will always fail
|
||||||
// since self.board.len is always greater than i.
|
// since self.board.len is always greater than i.
|
||||||
if self.last_placed.unwrap_or(self.board.len()) == i {
|
if self.last_placed.unwrap_or(self.board.len()) == i {
|
||||||
color::Fg(&color::Red as &dyn Color)
|
color::Fg(&color::Magenta as &dyn Color)
|
||||||
} else {
|
} else {
|
||||||
match p {
|
color::Fg(&color::Reset as &dyn Color)
|
||||||
Some(player) => color::Fg(player.color()),
|
|
||||||
None => color::Fg(&color::Reset as &dyn Color),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
symb,
|
symb,
|
||||||
color::Fg(color::Reset)
|
color::Fg(color::Reset)
|
||||||
|
@ -210,8 +213,10 @@ impl Board {
|
||||||
for s in self.board.iter() {
|
for s in self.board.iter() {
|
||||||
match s {
|
match s {
|
||||||
Some(Symb::Div) => {
|
Some(Symb::Div) => {
|
||||||
tokens.push(Token::Value(current_num.clone()));
|
if !current_num.is_empty() {
|
||||||
current_num.clear();
|
tokens.push(Token::Value(current_num.clone()));
|
||||||
|
current_num.clear();
|
||||||
|
}
|
||||||
tokens.push(Token::OpDiv);
|
tokens.push(Token::OpDiv);
|
||||||
is_neg = true;
|
is_neg = true;
|
||||||
}
|
}
|
||||||
|
@ -219,21 +224,27 @@ impl Board {
|
||||||
if is_neg {
|
if is_neg {
|
||||||
current_num = format!("-{}", current_num);
|
current_num = format!("-{}", current_num);
|
||||||
} else {
|
} else {
|
||||||
tokens.push(Token::Value(current_num.clone()));
|
if !current_num.is_empty() {
|
||||||
current_num.clear();
|
tokens.push(Token::Value(current_num.clone()));
|
||||||
|
current_num.clear();
|
||||||
|
}
|
||||||
tokens.push(Token::OpSub);
|
tokens.push(Token::OpSub);
|
||||||
is_neg = true;
|
is_neg = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Symb::Plus) => {
|
Some(Symb::Plus) => {
|
||||||
tokens.push(Token::Value(current_num.clone()));
|
if !current_num.is_empty() {
|
||||||
current_num.clear();
|
tokens.push(Token::Value(current_num.clone()));
|
||||||
|
current_num.clear();
|
||||||
|
}
|
||||||
tokens.push(Token::OpAdd);
|
tokens.push(Token::OpAdd);
|
||||||
is_neg = true;
|
is_neg = true;
|
||||||
}
|
}
|
||||||
Some(Symb::Times) => {
|
Some(Symb::Times) => {
|
||||||
tokens.push(Token::Value(current_num.clone()));
|
if !current_num.is_empty() {
|
||||||
current_num.clear();
|
tokens.push(Token::Value(current_num.clone()));
|
||||||
|
current_num.clear();
|
||||||
|
}
|
||||||
tokens.push(Token::OpMult);
|
tokens.push(Token::OpMult);
|
||||||
is_neg = true;
|
is_neg = true;
|
||||||
}
|
}
|
||||||
|
@ -252,11 +263,14 @@ impl Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens.push(Token::Value(current_num));
|
if !current_num.is_empty() {
|
||||||
|
tokens.push(Token::Value(current_num.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
tokens
|
tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_tree(&self) -> TreeElement {
|
pub fn to_tree(&self) -> Option<TreeElement> {
|
||||||
let tokens = self.tokenize();
|
let tokens = self.tokenize();
|
||||||
|
|
||||||
let mut tree: Vec<_> = tokens
|
let mut tree: Vec<_> = tokens
|
||||||
|
@ -283,8 +297,13 @@ impl Board {
|
||||||
_ => false,
|
_ => false,
|
||||||
} {
|
} {
|
||||||
did_something = true;
|
did_something = true;
|
||||||
let l = tree[i - 1].to_value();
|
|
||||||
let r = tree[i + 1].to_value();
|
if i == 0 || i + 1 >= tree.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let l = tree[i - 1].to_value()?;
|
||||||
|
let r = tree[i + 1].to_value()?;
|
||||||
|
|
||||||
let v = match tree[i] {
|
let v = match tree[i] {
|
||||||
InterTreeElement::Unprocessed(Token::OpAdd) => TreeElement::Add {
|
InterTreeElement::Unprocessed(Token::OpAdd) => TreeElement::Add {
|
||||||
|
@ -318,14 +337,14 @@ impl Board {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match tree.into_iter().next().unwrap() {
|
Some(match tree.into_iter().next().unwrap() {
|
||||||
InterTreeElement::Processed(x) => x,
|
InterTreeElement::Processed(x) => x,
|
||||||
x => x.to_value(),
|
x => x.to_value()?,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate(&self) -> Option<f32> {
|
pub fn evaluate(&self) -> Option<f32> {
|
||||||
self.to_tree().evaluate()
|
self.to_tree()?.evaluate()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_board(board: [Option<Symb>; 11]) -> Self {
|
pub fn from_board(board: [Option<Symb>; 11]) -> Self {
|
||||||
|
|
|
@ -2,12 +2,21 @@
|
||||||
mod board;
|
mod board;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
pub use board::Board;
|
pub use board::Board;
|
||||||
pub use tree::TreeElement;
|
pub use tree::TreeElement;
|
||||||
|
|
||||||
use crate::Symb;
|
use crate::Symb;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PlayerAction {
|
pub struct PlayerAction {
|
||||||
pub symb: Symb,
|
pub symb: Symb,
|
||||||
pub pos: usize,
|
pub pos: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for PlayerAction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} at {}", self.symb, self.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -116,7 +116,10 @@ impl TreeElement {
|
||||||
Self::Div { l, r } => {
|
Self::Div { l, r } => {
|
||||||
let l = l.evaluate();
|
let l = l.evaluate();
|
||||||
let r = r.evaluate();
|
let r = r.evaluate();
|
||||||
if let (Some(l), Some(r)) = (l, r) {
|
|
||||||
|
if r == Some(0.0) {
|
||||||
|
None
|
||||||
|
} else if let (Some(l), Some(r)) = (l, r) {
|
||||||
Some(l / r)
|
Some(l / r)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
Loading…
Reference in New Issue