use crate::{ board::{ game::Game, transposition_table::{Bound, TTEntry, TranspositionTable}, }, evaluation::{MATE_SCORE, MIN_SCORE}, movegen::r#move::Move, }; use super::{move_ordering::score_by_mvv_lva, quiescence::quiescence}; const QUIESCENCE_DEPTH: u8 = 3; pub fn negamax( game: &mut Game, mut alpha: i32, beta: i32, depth: u8, plies: u8, tt: &mut TranspositionTable, nodes: &mut u64, ) -> (Option, i32) { *nodes += 1; if depth == 0 { let q = quiescence(game, alpha, beta, nodes, QUIESCENCE_DEPTH).1; return (None, q); } if let Some(entry) = tt.lookup(game.hash) { if entry.depth >= depth && (matches!(entry.bound, Bound::Exact) || (matches!(entry.bound, Bound::Lower) && entry.score >= beta) || (matches!(entry.bound, Bound::Upper) && entry.score <= alpha)) { return (entry.mv, entry.score); } } let color = game.current_player(); let (mut best_move, mut best_score, mate_score) = (None, MIN_SCORE, -MATE_SCORE + plies as i32); let mut legal_moves = 0; let mut pseudo_legal_moves = game.board.pseudo_moves_all(); pseudo_legal_moves.sort_unstable_by_key(|mv| score_by_mvv_lva(&game.mailbox, *mv)); for mv in pseudo_legal_moves { game.make_move(&mv); if game.board.king_under_check(color) { game.unmake_move(); continue; } legal_moves += 1; let move_score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, tt, nodes).1; game.unmake_move(); if move_score > best_score { best_score = move_score; best_move = Some(mv); } if move_score >= beta { tt.insert(TTEntry::new( game.hash, depth, best_score, None, Bound::Lower, )); return (best_move, beta); } alpha = alpha.max(move_score); } if legal_moves == 0 { if game.board.king_under_check(color) { tt.insert(TTEntry::new( game.hash, depth, mate_score, None, Bound::Exact, )); return (None, mate_score); } tt.insert(TTEntry::new(game.hash, depth, 0, None, Bound::Exact)); return (None, 0); } if best_score < alpha { tt.insert(TTEntry::new( game.hash, depth, best_score, best_move, Bound::Upper, )); } (best_move, best_score) } #[cfg(test)] mod tests { use crate::board::transposition_table::TranspositionTable; use crate::board::{fen::from_fen, square::Square}; use crate::evaluation::{MAX_SCORE, MIN_SCORE}; use crate::movegen::attack_generator::init_attacks; use crate::movegen::r#move::Move; use crate::search::negamax::negamax; const FEN_MATE_IN_1: [&str; 2] = [ "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1", "8/8/8/8/8/4Q1K1/8/5k2 w - - 0 1", ]; #[test] fn test_negamax() -> Result<(), String> { init_attacks(); let mut game = from_fen(FEN_MATE_IN_1[0])?; let mut tt = TranspositionTable::new(1000000); let e3f2 = Move::new(Square::E3, Square::F2); let mut nodes = 0; let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &mut tt, &mut nodes) .0 .unwrap(); dbg!(nodes); assert_eq!(e3f2, anointed_move); // let mut game = from_fen(FEN_MATE_IN_1[1])?; // let e3f2 = Move::new(Square::E3, Square::F2); // let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0).0.unwrap(); // assert_eq!(e3f2, anointed_move); Ok(()) } }