diff --git a/src/search.rs b/src/search.rs index 1d23721..a7f422f 100644 --- a/src/search.rs +++ b/src/search.rs @@ -20,9 +20,34 @@ use std::cmp::max; // min can't be represented as positive const EVAL_WORST: EvalInt = -(EvalInt::MAX); +const EVAL_BEST: EvalInt = EvalInt::MAX; + +#[cfg(test)] +mod test_eval_int { + use super::*; + + #[test] + fn test_eval_worst_best_symm() { + // int limits will bite you if you don't test this + assert_eq!(EVAL_WORST, -EVAL_BEST); + assert_eq!(-EVAL_WORST, EVAL_BEST); + } +} /// Search the game tree to find the absolute (positive good) eval for the current player. -fn minmax(board: &mut Board, depth: usize) -> EvalInt { +/// +/// # Arguments +/// +/// * board: board position to analyze. +/// * depth: how deep to analyze the game tree. +/// * alpha: best score (absolute, from current player perspective) guaranteed for current player. +/// * beta: best score (absolute, from current player perspective) guaranteed for other player. +fn minmax(board: &mut Board, depth: usize, alpha: Option, beta: Option) -> EvalInt { + // default to worst, then gradually improve + let mut alpha = alpha.unwrap_or(EVAL_WORST); + // our best is their worst + let beta = beta.unwrap_or(EVAL_BEST); + if depth == 0 { let eval = board.eval(); match board.turn { @@ -46,8 +71,19 @@ fn minmax(board: &mut Board, depth: usize) -> EvalInt { for mv in mvs { let anti_mv = mv.make(board); - abs_best = max(abs_best, -minmax(board, depth - 1)); + let abs_score = -minmax(board, depth - 1, Some(-beta), Some(-alpha)); + abs_best = max(abs_best, abs_score); + alpha = max(alpha, abs_best); anti_mv.unmake(board); + if alpha >= beta { + // alpha-beta prune. + // + // Beta represents the best eval that the other player can get in sibling branches + // (different moves in the parent node). Alpha >= beta means the eval here is _worse_ + // for the other player, so they will never make the move that leads into this branch. + // Therefore, we stop evaluating this branch at all. + break; + } } abs_best @@ -64,7 +100,7 @@ fn search(board: &mut Board) -> Option { for mv in mvs { let anti_mv = mv.make(board); - let abs_eval = -minmax(board, DEPTH); + let abs_eval = -minmax(board, DEPTH, None, None); if abs_eval >= best_eval { best_eval = abs_eval; best_mv = Some(mv);