use chess::{Board, ChessMove, Color, MoveGen, EMPTY};
use rustc_hash::{FxHashMap, FxHashSet};
use super::{
heuristics::{classify_move, MoveVariation},
utils::insufficient_white_material,
};
#[derive(Debug)]
pub(crate) struct Search {
pub(crate) max_depth: u8,
pub(crate) depth_node_limit: u64,
pub(crate) global_node_limit: u64,
pub(crate) iteration_nodes: u64,
pub(crate) global_nodes: u64,
pub(crate) interrupted: bool,
pub(crate) moves: Vec<ChessMove>,
pub(crate) past_progress: bool,
pub(crate) transposition_table: Option<FxHashMap<Board, u8>>,
pub(crate) punishable: Option<FxHashSet<ChessMove>>,
pub(crate) bishop_distances: Option<[u8; 64]>,
}
impl Search {
pub(crate) fn new() -> Self {
Self {
max_depth: 0,
depth_node_limit: u64::MAX,
global_node_limit: 1_000_000,
iteration_nodes: 0,
global_nodes: 0,
interrupted: false,
moves: Vec::with_capacity(128),
past_progress: false,
transposition_table: None,
punishable: None,
bishop_distances: None,
}
}
pub(crate) fn reset_iteration(&mut self) {
self.global_nodes += self.iteration_nodes;
self.iteration_nodes = 0;
self.interrupted = false;
}
pub(crate) fn reset_global_nodes(&mut self) {
self.global_nodes = 0;
}
fn is_limit_reached(&self) -> bool {
self.is_depth_limit_reached() || self.is_global_limit_reached()
}
fn is_depth_limit_reached(&self) -> bool {
self.depth_node_limit != u64::MAX
&& self.iteration_nodes > (self.max_depth as u64) * self.depth_node_limit
}
fn is_global_limit_reached(&self) -> bool {
self.global_nodes + self.iteration_nodes > self.global_node_limit
}
}
pub(crate) fn helpmate(board: &Board, search: &mut Search, depth: u8) -> Option<Vec<ChessMove>> {
let num_moves_left = search.max_depth.saturating_sub(depth);
if let Some(ref tt) = search.transposition_table {
if let Some(entry_depth) = tt.get(board) {
if entry_depth >= &num_moves_left {
return None;
}
}
}
let mut legal_moves = MoveGen::new_legal(board).peekable();
if legal_moves.peek().is_none() {
if board.side_to_move() == Color::Black && *board.checkers() != EMPTY {
return Some(search.moves.clone()); }
return None; }
if insufficient_white_material(board) {
return None;
}
if depth >= search.max_depth || search.moves.len() >= 128 {
search.interrupted = true;
return None;
}
if let Some(ref mut tt) = search.transposition_table {
tt.insert(*board, num_moves_left);
}
for m in legal_moves {
search.iteration_nodes += 1;
if search.is_limit_reached() {
search.interrupted = true;
return None;
}
let variation = classify_move(board, m, &search.punishable, &search.bishop_distances);
let new_board = board.make_move_new(m);
let new_depth = match variation {
MoveVariation::Reward => depth,
MoveVariation::Punish => depth + 4,
MoveVariation::Normal => depth + (!search.past_progress as u8),
};
search.moves.push(m);
let saved_past_progress = search.past_progress;
search.past_progress = variation == MoveVariation::Reward;
if let Some(helpmate) = helpmate(&new_board, search, new_depth) {
return Some(helpmate);
}
search.past_progress = saved_past_progress;
search.moves.pop();
}
None
}