use crate::{
game::players::Player,
logic::{GameMove, GameState, Mark},
};
pub struct MinimaxPlayer {
mark: Mark,
}
impl MinimaxPlayer {
pub fn new(mark: Mark) -> Self {
MinimaxPlayer { mark }
}
}
impl Player for MinimaxPlayer {
fn get_move(&self, game_state: &GameState) -> Option<GameMove> {
find_best_move(game_state)
}
fn get_mark(&self) -> Mark {
self.mark
}
}
fn find_best_move(game_state: &GameState) -> Option<GameMove> {
let maximized_player = game_state.current_mark();
let alpha = i32::MIN;
let beta = i32::MAX;
game_state
.possible_moves()
.into_iter()
.max_by_key(|move_| minimax_with_pruning(move_, maximized_player, false, alpha, beta))
}
#[allow(dead_code)]
fn minimax(move_: &GameMove, maximized_player: Mark, choose_highest_score: bool) -> i32 {
if move_.after_state().game_over() {
return move_.after_state().score(maximized_player).unwrap();
};
let scores = move_
.after_state()
.possible_moves()
.into_iter()
.map(|move_| minimax(&move_, maximized_player, !choose_highest_score));
if choose_highest_score {
scores.max().unwrap()
} else {
scores.min().unwrap()
}
}
fn minimax_with_pruning(
move_: &GameMove,
maximized_player: Mark,
choose_highest_score: bool,
alpha: i32,
beta: i32,
) -> i32 {
if move_.after_state().game_over() {
return move_.after_state().score(maximized_player).unwrap();
}
let mut best_score = if choose_highest_score {
i32::MIN
} else {
i32::MAX
};
let mut new_alpha = alpha;
let mut new_beta = beta;
for child_move in move_.after_state().possible_moves() {
let score = minimax_with_pruning(
&child_move,
maximized_player,
!choose_highest_score,
new_alpha,
new_beta,
);
if choose_highest_score {
best_score = best_score.max(score);
new_alpha = new_alpha.max(score);
} else {
best_score = best_score.min(score);
new_beta = new_beta.min(score);
}
if beta <= alpha {
break; }
}
best_score
}