use std::fmt::Display; use termion::color::{self, Color}; use super::{PlayerAction, TreeElement}; use crate::util::{Player, Symb}; #[derive(Debug)] enum InterTreeElement { Unprocessed(Token), Processed(TreeElement), } impl InterTreeElement { fn to_value(&self) -> TreeElement { match self { InterTreeElement::Processed(x) => x.clone(), InterTreeElement::Unprocessed(Token::Value(s)) => { if let Some(s) = s.strip_prefix('-') { TreeElement::Neg { r: { if s.contains('_') { Box::new(TreeElement::Partial(s.to_string())) } else { Box::new(TreeElement::Number(s.parse().unwrap())) } }, } } else if s.contains('_') { TreeElement::Partial(s.to_string()) } else { TreeElement::Number(s.parse().unwrap()) } } _ => unreachable!(), } } } #[derive(Debug, PartialEq, Clone)] enum Token { Value(String), OpAdd, OpSub, OpMult, OpDiv, } #[derive(Clone)] pub struct Board { board: [Option<(Symb, Player)>; 11], /// Number of Nones in `board` free_spots: usize, /// Index of the last board index that was changed last_placed: Option, } impl Display for Board { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // Print board for (i, o) in self.board.iter().enumerate() { match o { Some((symb, player)) => write!( f, "{}{}{}", // If index matches last placed, draw symbol in red. // If last_placed is None, this check will always fail // since self.board.len is always greater than i. if self.last_placed.unwrap_or(self.board.len()) == i { color::Fg(&color::Red as &dyn Color) } else { color::Fg(player.color()) }, symb, color::Fg(color::Reset) )?, None => write!(f, "_")?, } } Ok(()) } } #[allow(dead_code)] impl Board { pub fn new() -> Self { Self { free_spots: 11, board: Default::default(), last_placed: None, } } pub fn iter(&self) -> impl Iterator> { self.board.iter() } pub fn get(&self, idx: usize) -> Option<&Option<(Symb, Player)>> { self.board.get(idx) } pub fn is_done(&self) -> bool { self.free_spots == 0 } pub fn size(&self) -> usize { self.board.len() } pub fn contains(&self, s: Symb) -> bool { for i in self.board.iter().flatten() { if i.0 == s { return true; } } false } /// 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(action.symb) { return false; } // Check syntax match action.symb { Symb::Minus => { if action.pos == self.board.len() - 1 { return false; } let r = &self.board[action.pos + 1]; if r.is_some_and(|(s, _)| s.is_op() && !s.is_minus()) { return false; } } Symb::Zero => { if action.pos != 0 { let l = &self.board[action.pos - 1].map(|x| x.0); if l == &Some(Symb::Div) { return false; } } } Symb::Div | Symb::Plus | Symb::Times => { if action.pos == 0 || action.pos == self.board.len() - 1 { return false; } let l = &self.board[action.pos - 1].map(|x| x.0); let r = &self.board[action.pos + 1].map(|x| x.0); if action.symb == Symb::Div && r == &Some(Symb::Zero) { return false; } if l.is_some_and(|s| s.is_op()) || r.is_some_and(|s| s.is_op() && !s.is_minus()) { return false; } } _ => {} } } } true } /// Place the marked symbol at the given position. /// Returns true for valid moves and false otherwise. pub fn play(&mut self, action: PlayerAction, player: Player) -> bool { if !self.can_play(&action) { return false; } self.board[action.pos] = Some((action.symb, player)); self.free_spots -= 1; self.last_placed = Some(action.pos); true } fn tokenize(&self) -> Vec { let mut tokens = Vec::new(); let mut is_neg = true; // if true, - is negative. if false, subtract. let mut current_num = String::new(); for s in self.board.iter().map(|x| x.map(|(s, _)| s)) { match s { Some(Symb::Div) => { tokens.push(Token::Value(current_num.clone())); current_num.clear(); tokens.push(Token::OpDiv); is_neg = true; } Some(Symb::Minus) => { if is_neg { current_num = format!("-{}", current_num); } else { tokens.push(Token::Value(current_num.clone())); current_num.clear(); tokens.push(Token::OpSub); is_neg = true; } } Some(Symb::Plus) => { tokens.push(Token::Value(current_num.clone())); current_num.clear(); tokens.push(Token::OpAdd); is_neg = true; } Some(Symb::Times) => { tokens.push(Token::Value(current_num.clone())); current_num.clear(); tokens.push(Token::OpMult); is_neg = true; } Some(Symb::Zero) => { current_num.push('0'); is_neg = false; } Some(Symb::Number(x)) => { current_num.push_str(&x.to_string()); is_neg = false; } None => { current_num.push('_'); is_neg = false; } } } tokens.push(Token::Value(current_num)); tokens } pub fn to_tree(&self) -> TreeElement { let tokens = self.tokenize(); let mut tree: Vec<_> = tokens .iter() .map(|x| InterTreeElement::Unprocessed(x.clone())) .collect(); let mut priority_level = 0; let mut did_something; while tree.len() > 1 { did_something = false; for i in 0..tree.len() { if match priority_level { 0 => matches!( tree[i], InterTreeElement::Unprocessed(Token::OpMult) | InterTreeElement::Unprocessed(Token::OpDiv) ), 1 => matches!( tree[i], InterTreeElement::Unprocessed(Token::OpAdd) | InterTreeElement::Unprocessed(Token::OpSub) ), _ => false, } { did_something = true; let l = tree[i - 1].to_value(); let r = tree[i + 1].to_value(); let v = match tree[i] { InterTreeElement::Unprocessed(Token::OpAdd) => TreeElement::Add { l: Box::new(l), r: Box::new(r), }, InterTreeElement::Unprocessed(Token::OpDiv) => TreeElement::Div { l: Box::new(l), r: Box::new(r), }, InterTreeElement::Unprocessed(Token::OpMult) => TreeElement::Mul { l: Box::new(l), r: Box::new(r), }, InterTreeElement::Unprocessed(Token::OpSub) => TreeElement::Sub { l: Box::new(l), r: Box::new(r), }, _ => unreachable!(), }; tree.remove(i - 1); tree.remove(i - 1); tree[i - 1] = InterTreeElement::Processed(v); break; } } if !did_something { priority_level += 1; } } match tree.into_iter().next().unwrap() { InterTreeElement::Processed(x) => x, x => x.to_value(), } } pub fn evaluate(&self) -> Option { self.to_tree().evaluate() } /// Hacky method to parse a board from a string pub fn from_string(s: &str, current_player: Player) -> Option { if s.len() != 11 { return None; } let x = s .chars() .filter_map(|c| { if c == '_' { Some(None) } else { Symb::from_char(c).map(|s| Some((s, current_player))) } }) .collect::>(); if x.len() != 11 { return None; } let mut free_spots = 11; let mut board = [None; 11]; for i in 0..x.len() { board[i] = x[i]; if x[i].is_some() { free_spots -= 1; } } Some(Self { board, free_spots, last_placed: None, }) } }