use crate::board::{Board, Coord, Tile}; use crate::{GameState, check_state}; #[derive(Debug)] pub struct BestMove { pub coord: Coord, eval: i8, } fn eval_for_player(board: &Board, player: Tile) -> i8 { match player { Tile::PlayerOne => { return match check_state(board) { GameState::PlayerTwoWin => -100, GameState::Draw => -10, GameState::InProgress => 0, GameState::PlayerOneWin => 100, }; } Tile::PlayerTwo => { return match check_state(board) { GameState::PlayerOneWin => -100, GameState::Draw => -10, GameState::InProgress => 0, GameState::PlayerTwoWin => 100, }; } Tile::Unowned => { return match check_state(board) { GameState::PlayerOneWin => -100, GameState::PlayerTwoWin => -100, GameState::InProgress => 0, GameState::Draw => 100, }; } } } pub fn get_best_move(board: &Board, player: Tile, is_tl: bool) -> BestMove { //base case //game is over, return eval let eval = eval_for_player(board, player); //eval 0 means the game is in progress if eval != 0 { return BestMove { coord: (99, 99), eval, }; } //get all possible moves let possible_moves: Vec = (0..9) .map(|i| { return (i % 3, i / 3); }) .filter(|coord| { return board.get_at_coord(*coord) == Tile::Unowned; }) .collect(); let mut cur_best = BestMove { eval: i8::min_value(), coord: possible_moves[0], }; let p_move_count = possible_moves.len(); for p_move in possible_moves { let mut new_board = board.clone(); new_board.set_at_coord(p_move, player); let other_player = match player { Tile::PlayerOne => Tile::PlayerTwo, Tile::PlayerTwo => Tile::PlayerOne, //this allows the AI to set as player 'unowned', which will cause it to try to draw //todo: fix this Tile::Unowned => Tile::PlayerOne, }; let response_move = get_best_move(&new_board, other_player, false); if response_move.coord.0 != 99 { new_board.set_at_coord(response_move.coord, other_player); } //this is the inverse of the response eval let response_eval = -response_move.eval; //slightly bias for the centre to beat weak players let centre_bias = if p_move == (1, 1) { 1 } else { 0 }; if response_eval > cur_best.eval { cur_best = BestMove { coord: p_move, eval: response_eval + centre_bias, }; } } if is_tl { //log thought process println!("AI > There are {p_move_count} moves that I can play"); let sentiment = match cur_best.eval { -100 | -101 => "I'll lose this game if my opponent plays right", -9 | -10 | -11 => "If my opponent plays well, this will be a draw", 100 | 101 => "I'll win this game", _ => "I can't predict the outcome of this game", }; println!("AI > ({sentiment})"); println!("AI > I'll play {cur_best:?}") } return cur_best; }