use crate::{ board::{game::Game, history::MoveParameters}, evaluation::evaluation::evaluate_position, movegen::r#move::Move, }; pub fn negamax( game: &mut Game, mut alpha: i32, beta: i32, depth: u8, plies: u8, ) -> (Option, i32) { let color = game.current_player(); if depth == 0 { return (None, evaluate_position(&game.board)); } let (mut best_move, mut best_score, mate_score) = (None, -100000, -50000 + plies as i32); let mut legal_moves = 0; for mv in game.board.pseudo_moves_all(color) { let move_parameters = MoveParameters::build(&game.board, &mv); game.board.make_move(&mv); if !game.board.king_under_check(color) { legal_moves += 1; let move_score = -negamax(game, -beta, -alpha, depth - 1, plies + 1).1; if move_score > best_score { best_score = move_score; best_move = Some(mv) } if move_score >= beta { game.board.unmake_move(move_parameters); return (Some(mv), beta); // fail hard beta-cutoff } if move_score > alpha { alpha = move_score; // alpha acts like max in minimax } } game.board.unmake_move(move_parameters); } if legal_moves == 0 { if game.board.king_under_check(color) { return (None, mate_score); } else { return (None, 0); } } (best_move, best_score) } #[cfg(test)] mod tests { use crate::board::{fen::from_fen, square::Square}; use crate::evaluation::evaluation::{MAX_EVAL, MIN_EVAL}; use crate::movegen::attack::init_attacks; use crate::movegen::r#move::Move; use crate::search::negamax::negamax; const FEN_MATE_IN_1: &str = "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1"; #[test] fn test_negamax() -> Result<(), String> { init_attacks(); let mut game = from_fen(FEN_MATE_IN_1)?; let e3f2 = Move::new(Square::E3, Square::F2); assert_eq!( e3f2, negamax(&mut game, MIN_EVAL, MAX_EVAL, 2, 0).0.unwrap() ); assert_eq!( e3f2, negamax(&mut game, MIN_EVAL, MAX_EVAL, 3, 0).0.unwrap() ); assert_eq!( e3f2, negamax(&mut game, MIN_EVAL, MAX_EVAL, 4, 0).0.unwrap() ); assert_eq!( e3f2, negamax(&mut game, MIN_EVAL, MAX_EVAL, 5, 0).0.unwrap() ); Ok(()) } }