Spaces:
Runtime error
Runtime error
| use crate::position::Position; | |
| use crate::types::{Move, Color, PieceType}; | |
| use crate::movegen::generate_moves; | |
| use crate::evaluate::evaluate; | |
| use crate::tt::{TranspositionTable, TT_EXACT, TT_ALPHA, TT_BETA}; | |
| use crate::zobrist::Zobrist; | |
| use crate::timeman::TimeManager; | |
| use crate::movepick::{score_moves, pick_next_move}; | |
| pub struct Searcher { | |
| pub tt: TranspositionTable, | |
| pub zobrist: Zobrist, | |
| pub killers: [[Move; 64]; 2], | |
| pub nodes: u64, | |
| } | |
| impl Searcher { | |
| pub fn new() -> Self { | |
| Self { | |
| tt: TranspositionTable::new(16), | |
| zobrist: Zobrist::new(), | |
| killers: [[Move::NONE; 64]; 2], | |
| nodes: 0, | |
| } | |
| } | |
| pub fn search_best(&mut self, pos: &mut Position, tm: &mut TimeManager, max_depth: i32) -> Move { | |
| let mut best_move = Move::NONE; | |
| self.nodes = 0; | |
| for depth in 1..=max_depth { | |
| let mut current_best = Move::NONE; | |
| let score = self.alpha_beta(pos, tm, -30000, 30000, depth, 0, &mut current_best, false); | |
| if !tm.tick() { break; } | |
| best_move = current_best; | |
| println!("info depth {} score cp {} nodes {}", depth, score, self.nodes); | |
| } | |
| best_move | |
| } | |
| fn alpha_beta(&mut self, pos: &mut Position, tm: &mut TimeManager, mut alpha: i32, beta: i32, depth: i32, ply: usize, best_move: &mut Move, is_null: bool) -> i32 { | |
| if !tm.tick() { return 0; } | |
| self.nodes += 1; | |
| if depth <= 0 { return self.quiescence(pos, alpha, beta); } | |
| let hash = self.zobrist.hash(pos); | |
| let mut tt_move = Move::NONE; | |
| if let Some(entry) = self.tt.probe(hash) { | |
| tt_move = entry.best_move; | |
| if entry.depth >= depth as u8 && ply > 0 { | |
| if entry.flag == TT_EXACT { return entry.score; } | |
| if entry.flag == TT_ALPHA && entry.score <= alpha { return alpha; } | |
| if entry.flag == TT_BETA && entry.score >= beta { return beta; } | |
| } | |
| } | |
| let us = pos.side_to_move; | |
| let mut king_bb = pos.pieces[crate::types::PieceType::King as usize] & pos.colors[us as usize]; | |
| let in_check = if king_bb != 0 { | |
| let king_sq = crate::bitboard::pop_lsb(&mut king_bb); | |
| pos.is_square_attacked(crate::types::Square::from_u8(king_sq), us) | |
| } else { | |
| false | |
| }; | |
| // Null Move Pruning (NMP) | |
| // If we are not in check, not already a null move, and have depth to spare | |
| if !in_check && depth >= 3 && !is_null && ply > 0 { | |
| let eval = evaluate(pos); | |
| // If the position is so good that even doing NOTHING maintains an advantage | |
| if eval >= beta { | |
| pos.side_to_move = if pos.side_to_move == Color::White { Color::Black } else { Color::White }; | |
| let null_score = -self.alpha_beta(pos, tm, -beta, -beta + 1, depth - 3, ply + 1, &mut Move::NONE, true); | |
| pos.side_to_move = if pos.side_to_move == Color::White { Color::Black } else { Color::White }; | |
| if null_score >= beta { | |
| return beta; // Cut off the branch entirely | |
| } | |
| } | |
| } | |
| let mut moves = Vec::new(); | |
| generate_moves(pos, &mut moves); | |
| let mut move_list: Vec<(Move, i32)> = moves.into_iter().map(|m| (m, 0)).collect(); | |
| score_moves(pos, &mut move_list, tt_move, self.killers[0][ply], self.killers[1][ply]); | |
| let mut legal_moves = 0; | |
| let mut max_score = -30000; | |
| let mut local_best = Move::NONE; | |
| let original_alpha = alpha; | |
| let mut b_search_pv = true; | |
| for i in 0..move_list.len() { | |
| let m = pick_next_move(&mut move_list, i); | |
| if !pos.do_move(m) { continue; } | |
| legal_moves += 1; | |
| let is_capture = pos.get_piece_at(m.to()) != crate::types::PieceType::None; | |
| let is_promo = m.promotion() != crate::types::PieceType::None; | |
| let mut score; | |
| // Late Move Reductions (LMR) | |
| // If a move is late in the list and doesn't look critical, search it less | |
| let mut reduction = 0; | |
| if depth >= 3 && legal_moves > 4 && !in_check && !is_capture && !is_promo { | |
| reduction = 1; | |
| if legal_moves > 10 { reduction = 2; } | |
| } | |
| if b_search_pv { | |
| score = -self.alpha_beta(pos, tm, -beta, -alpha, depth - 1, ply + 1, &mut Move::NONE, false); | |
| } else { | |
| score = -self.alpha_beta(pos, tm, -alpha - 1, -alpha, depth - 1 - reduction, ply + 1, &mut Move::NONE, false); | |
| if score > alpha && reduction > 0 { | |
| // Re-search without reduction if it actually turned out to be a good move | |
| score = -self.alpha_beta(pos, tm, -alpha - 1, -alpha, depth - 1, ply + 1, &mut Move::NONE, false); | |
| } | |
| if score > alpha && score < beta { | |
| // Full window search if it beat alpha | |
| score = -self.alpha_beta(pos, tm, -beta, -alpha, depth - 1, ply + 1, &mut Move::NONE, false); | |
| } | |
| } | |
| pos.undo_move(m); | |
| if score > max_score { | |
| max_score = score; | |
| local_best = m; | |
| } | |
| if score > alpha { alpha = score; b_search_pv = false; } | |
| if alpha >= beta { | |
| if !is_capture { | |
| self.killers[1][ply] = self.killers[0][ply]; | |
| self.killers[0][ply] = m; | |
| } | |
| break; | |
| } | |
| } | |
| if legal_moves == 0 { | |
| if in_check { return -30000 + ply as i32; } // Checkmate | |
| return 0; // Stalemate | |
| } | |
| let flag = if max_score <= original_alpha { TT_ALPHA } else if max_score >= beta { TT_BETA } else { TT_EXACT }; | |
| self.tt.store(hash, depth as u8, flag, max_score, local_best); | |
| *best_move = local_best; | |
| max_score | |
| } | |
| fn quiescence(&mut self, pos: &mut Position, mut alpha: i32, beta: i32) -> i32 { | |
| self.nodes += 1; | |
| let stand_pat = evaluate(pos); | |
| if stand_pat >= beta { return beta; } | |
| if alpha < stand_pat { alpha = stand_pat; } | |
| let mut moves = Vec::new(); | |
| generate_moves(pos, &mut moves); | |
| let mut move_list: Vec<(Move, i32)> = moves.into_iter() | |
| .filter(|m| pos.get_piece_at(m.to()) != crate::types::PieceType::None || m.promotion() != crate::types::PieceType::None) | |
| .map(|m| (m, 0)).collect(); | |
| score_moves(pos, &mut move_list, Move::NONE, Move::NONE, Move::NONE); | |
| for i in 0..move_list.len() { | |
| let m = pick_next_move(&mut move_list, i); | |
| if !pos.do_move(m) { continue; } | |
| let score = -self.quiescence(pos, -beta, -alpha); | |
| pos.undo_move(m); | |
| if score >= beta { return beta; } | |
| if score > alpha { alpha = score; } | |
| } | |
| alpha | |
| } | |
| } | |