From da6b3f20f951d7c5c7b6747df331dfce99985960 Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Sat, 2 Nov 2024 21:36:54 -0400 Subject: [PATCH] test: alpha-beta results are the same as without --- src/bin/engine.rs | 3 +- src/search.rs | 82 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/bin/engine.rs b/src/bin/engine.rs index 756907a..be2be8e 100644 --- a/src/bin/engine.rs +++ b/src/bin/engine.rs @@ -12,7 +12,6 @@ Copyright © 2024 dogeystamp //! Main UCI engine binary. -use chess_inator::eval::EvalInt; use chess_inator::fen::FromFen; use chess_inator::movegen::{FromUCIAlgebraic, Move, ToUCIAlgebraic}; use chess_inator::search::{best_line, SearchEval}; @@ -88,7 +87,7 @@ fn cmd_position(mut tokens: std::str::SplitWhitespace<'_>) -> Board { /// Play the game. fn cmd_go(mut _tokens: std::str::SplitWhitespace<'_>, board: &mut Board) { - let (line, eval) = best_line(board); + let (line, eval) = best_line(board, None); let chosen = line.last().copied(); println!( "info pv{}", diff --git a/src/search.rs b/src/search.rs index 6c7ca33..ee98c17 100644 --- a/src/search.rs +++ b/src/search.rs @@ -90,6 +90,24 @@ impl PartialOrd for SearchEval { } } +/// Configuration for the gametree search. +#[derive(Clone, Copy, Debug)] +pub struct SearchConfig { + /// Enable alpha-beta pruning. + alpha_beta_on: bool, + /// Limit search depth (will probably change as quiescence search is implemented) + depth: usize, +} + +impl Default for SearchConfig { + fn default() -> Self { + SearchConfig { + alpha_beta_on: true, + depth: 5, + } + } +} + /// Search the game tree to find the absolute (positive good) move and corresponding eval for the /// current player. /// @@ -105,6 +123,7 @@ impl PartialOrd for SearchEval { /// The best line (in reverse move order), and its corresponding absolute eval for the current player. fn minmax( board: &mut Board, + config: &SearchConfig, depth: usize, alpha: Option, beta: Option, @@ -139,7 +158,7 @@ fn minmax( for mv in mvs { let anti_mv = mv.make(board); - let (continuation, score) = minmax(board, depth - 1, Some(-beta), Some(-alpha)); + let (continuation, score) = minmax(board, config, depth - 1, Some(-beta), Some(-alpha)); let abs_score = score.increment(); if abs_score > abs_best { abs_best = abs_score; @@ -148,7 +167,7 @@ fn minmax( } alpha = max(alpha, abs_best.into()); anti_mv.unmake(board); - if alpha >= beta { + if alpha >= beta && config.alpha_beta_on { // alpha-beta prune. // // Beta represents the best eval that the other player can get in sibling branches @@ -167,13 +186,64 @@ fn minmax( } /// Find the best line (in reverse order) and its evaluation. -pub fn best_line(board: &mut Board) -> (Vec, SearchEval) { - let (line, eval) = minmax(board, 5, None, None); +pub fn best_line(board: &mut Board, config: Option) -> (Vec, SearchEval) { + let config = config.unwrap_or_default(); + let (line, eval) = minmax(board, &config, config.depth, None, None); (line, eval) } /// Find the best move. -pub fn best_move(board: &mut Board) -> Option { - let (line, _eval) = best_line(board); +pub fn best_move(board: &mut Board, config: Option) -> Option { + let (line, _eval) = best_line(board, Some(config.unwrap_or_default())); line.last().copied() } + +#[cfg(test)] +mod tests { + use super::*; + use crate::fen::{FromFen, ToFen}; + use crate::movegen::ToUCIAlgebraic; + + /// Theoretically, alpha-beta pruning should not affect the result of minmax. + #[test] + fn alpha_beta_same_result() { + let test_cases = [ + // in these cases the engines really likes to sacrifice its pieces for no gain... + "r2q1rk1/1bp1pp1p/p2p2p1/1p1P2P1/2n1P3/3Q1P2/PbPBN2P/3RKB1R b K - 5 15", + "r1b1k2r/p1qpppbp/1p4pn/2B3N1/1PP1P3/2P5/P4PPP/RN1QR1K1 w kq - 0 14", + ]; + for fen in test_cases { + let mut board = Board::from_fen(fen).unwrap(); + let mv_no_prune = best_move( + &mut board, + Some(SearchConfig { + alpha_beta_on: false, + depth: 3, + }), + ) + .unwrap(); + + assert_eq!(board.to_fen(), fen); + + let mv_with_prune = best_move( + &mut board, + Some(SearchConfig { + alpha_beta_on: true, + depth: 3, + }), + ) + .unwrap(); + + assert_eq!(board.to_fen(), fen); + + println!( + "without ab prune got {}, otherwise {}, fen {}", + mv_no_prune.to_uci_algebraic(), + mv_with_prune.to_uci_algebraic(), + fen + ); + + assert_eq!(mv_no_prune, mv_with_prune); + } + } +}