use crate::cache::search::TTableScoreType;
use crate::engine::context::SearchContext;
use crate::engine::*;
use crate::state::movescan::Move;
use crate::tablebases::syzygy;
use crate::tablebases::WdlResult;
use crate::utils::assert_fast;
use crate::utils::dev;
use crate::utils::param;
use context::SearchResultLine;
use search::movepick;
use search::movepick::MoveGenStage;
use search::movepick::MoveGenState;
use std::cmp;
use std::sync::atomic::Ordering;
pub fn run(context: &mut SearchContext, depth: i8) {
let king_checked = context.board.is_king_checked(context.board.stm);
if depth < param!(context.params.aspwin_min_depth) {
context.last_score = run_internal::<true, true>(context, depth, 0, MIN_ALPHA, MIN_BETA, true, king_checked, Move::default());
} else {
let mut delta = param!(context.params.aspwin_delta);
let mut alpha = context.last_score - delta;
let mut beta = context.last_score + delta;
loop {
let score = run_internal::<true, true>(context, depth, 0, alpha, beta, true, king_checked, Move::default());
if score.abs() == INVALID_SCORE.abs() {
break;
}
if score <= alpha {
alpha -= delta;
} else if score >= beta {
beta += delta;
} else {
context.last_score = score;
break;
}
delta *= 2;
if delta >= param!(context.params.aspwin_max_width) {
alpha = MIN_ALPHA;
beta = MIN_BETA;
}
context.lines.clear();
}
}
}
fn run_internal<const ROOT: bool, const PV: bool>(
context: &mut SearchContext,
mut depth: i8,
ply: u16,
mut alpha: i16,
mut beta: i16,
mut allow_null_move: bool,
friendly_king_checked: bool,
previous_move: Move,
) -> i16 {
assert_fast!(alpha <= beta);
if context.abort_flag.load(Ordering::Relaxed) {
return INVALID_SCORE;
}
if context.forced_depth == 0 && context.max_nodes_count == 0 && (context.stats.nodes_count & 8191) == 0 {
if unsafe { context.search_time_start.elapsed().unwrap_unchecked().as_millis() } > context.deadline as u128 {
context.abort_flag.store(true, Ordering::Relaxed);
return INVALID_SCORE;
}
}
if context.max_nodes_count != 0 {
if context.stats.nodes_count + context.stats.q_nodes_count >= context.max_nodes_count {
context.abort_flag.store(true, Ordering::Relaxed);
return INVALID_SCORE;
}
}
context.stats.nodes_count += 1;
if context.board.is_king_checked(context.board.stm ^ 1) {
dev!(context.stats.leafs_count += 1);
if friendly_king_checked {
return INVALID_SCORE;
} else {
return CHECKMATE_SCORE - (ply as i16);
}
}
if context.board.is_repetition_draw(if ROOT { 3 } else { 2 }) || context.board.is_fifty_move_rule_draw() || context.board.is_insufficient_material_draw() {
dev!(context.stats.leafs_count += 1);
return DRAW_SCORE;
}
if context.syzygy_enabled && depth >= context.syzygy_probe_depth && context.board.get_pieces_count() <= syzygy::probe::get_max_pieces_count() {
if let Some(wdl) = syzygy::probe::get_wdl(&context.board) {
context.stats.tb_hits += 1;
return match wdl {
WdlResult::Win => TBMATE_SCORE - (ply as i16),
WdlResult::Loss => -TBMATE_SCORE + (ply as i16),
WdlResult::Draw => DRAW_SCORE,
};
}
}
if check_extensions_can_be_applied(friendly_king_checked) {
depth += check_extensions_get_e();
}
if depth <= 0 {
dev!(context.stats.leafs_count += 1);
return qsearch::run(context, ply, alpha, beta);
}
let original_alpha = alpha;
let mut tt_entry_found = false;
let mut hash_move = Move::default();
match context.ttable.get(context.board.state.hash, ply) {
Some(entry) => {
dev!(context.stats.tt_hits += 1);
if entry.best_move.is_some() {
if entry.best_move.is_legal(&context.board) {
hash_move = entry.best_move;
dev!(context.stats.tt_legal_hashmoves += 1);
} else {
dev!(context.stats.tt_illegal_hashmoves += 1);
}
}
if !ROOT {
if entry.depth >= depth {
tt_entry_found = true;
match entry.r#type {
TTableScoreType::UPPER_BOUND => {
beta = cmp::min(beta, entry.score);
}
TTableScoreType::LOWER_BOUND => {
alpha = cmp::max(alpha, entry.score);
}
_ => {
if !PV || entry.age == 0 {
if !context.board.is_repetition_draw(2) {
dev!(context.stats.leafs_count += 1);
return entry.score;
}
}
}
}
if alpha >= beta {
dev!(context.stats.leafs_count += 1);
dev!(context.stats.beta_cutoffs += 1);
return entry.score;
}
} else {
match entry.r#type {
TTableScoreType::UPPER_BOUND | TTableScoreType::EXACT_SCORE => {
if entry.score < beta {
allow_null_move = false;
}
}
_ => {}
}
}
}
}
None => {
dev!(context.stats.tt_misses += 1);
}
};
if iir_can_be_applied(context, depth, hash_move) {
depth -= iir_get_r(context, depth);
}
let mut static_eval = None;
if razoring_can_be_applied::<PV>(context, depth, alpha, friendly_king_checked) {
let margin = razoring_get_margin(context, depth);
let static_eval_value = match static_eval {
Some(value) => value,
None => context.board.evaluate_fast(context.board.stm, &context.phtable, &mut context.stats),
};
dev!(context.stats.razoring_attempts += 1);
if static_eval_value + margin <= alpha {
let score = qsearch::run(context, ply, alpha, beta);
if score <= alpha {
dev!(context.stats.leafs_count += 1);
dev!(context.stats.razoring_accepted += 1);
return score;
} else {
dev!(context.stats.razoring_rejected += 1);
}
}
static_eval = Some(static_eval_value);
}
if snmp_can_be_applied::<PV>(context, depth, beta, friendly_king_checked) {
let margin = snmp_get_margin(context, depth);
let static_eval_value = match static_eval {
Some(value) => value,
None => context.board.evaluate_fast(context.board.stm, &context.phtable, &mut context.stats),
};
dev!(context.stats.snmp_attempts += 1);
if static_eval_value - margin >= beta {
dev!(context.stats.leafs_count += 1);
dev!(context.stats.snmp_accepted += 1);
return static_eval_value - margin;
} else {
dev!(context.stats.snmp_rejected += 1);
}
static_eval = Some(static_eval_value);
}
if nmp_can_be_applied::<PV>(context, depth, beta, allow_null_move, friendly_king_checked) {
let margin = param!(context.params.nmp_margin);
let static_eval_value = match static_eval {
Some(value) => value,
None => context.board.evaluate_fast(context.board.stm, &context.phtable, &mut context.stats),
};
dev!(context.stats.nmp_attempts += 1);
if static_eval_value + margin >= beta {
let r = nmp_get_r(context, depth);
context.board.make_null_move();
let score = -run_internal::<false, false>(context, depth - r - 1, ply + 1, -beta, -beta + 1, false, false, Move::default());
context.board.undo_null_move();
if score >= beta {
dev!(context.stats.leafs_count += 1);
dev!(context.stats.nmp_accepted += 1);
return score;
} else {
dev!(context.stats.nmp_rejected += 1);
}
}
static_eval = Some(static_eval_value);
}
let mut best_score = -CHECKMATE_SCORE;
let mut best_move = Move::default();
let mut state = MoveGenState { hash_move, ply, friendly_king_checked, previous_move, ..Default::default() };
while let Some((r#move, score)) = movepick::get_next_move(context, &mut state) {
if ROOT && !context.moves_to_search.is_empty() && !context.moves_to_search.contains(&r#move) {
continue;
}
if lmp_can_be_applied::<PV>(context, depth, state.move_number, score, friendly_king_checked) {
dev!(context.stats.lmp_accepted += 1);
break;
} else {
dev!(context.stats.lmp_rejected += 1);
}
context.board.make_move(r#move);
context.ttable.prefetch(context.board.state.hash);
let king_checked = context.board.is_king_checked(context.board.stm);
let r = if lmr_can_be_applied::<PV>(context, depth, r#move, state.move_number, score, friendly_king_checked, king_checked) {
lmr_get_r::<PV>(context, state.move_number)
} else {
0
};
let score = if PV {
if state.move_index == 0 {
dev!(context.stats.pvs_full_window_searches += 1);
-run_internal::<false, true>(context, depth - 1, ply + 1, -beta, -alpha, true, king_checked, r#move)
} else {
let zero_window_score = -run_internal::<false, false>(context, depth - r - 1, ply + 1, -alpha - 1, -alpha, true, king_checked, r#move);
dev!(context.stats.pvs_zero_window_searches += 1);
if zero_window_score > alpha && (alpha != beta - 1 || r > 0) && zero_window_score != -INVALID_SCORE {
dev!(context.stats.pvs_rejected_searches += 1);
-run_internal::<false, true>(context, depth - 1, ply + 1, -beta, -alpha, true, king_checked, r#move)
} else {
zero_window_score
}
}
} else {
let zero_window_score = -run_internal::<false, false>(context, depth - r - 1, ply + 1, -beta, -alpha, true, king_checked, r#move);
dev!(context.stats.pvs_zero_window_searches += 1);
if zero_window_score > alpha && r > 0 && zero_window_score != -INVALID_SCORE {
dev!(context.stats.pvs_rejected_searches += 1);
-run_internal::<false, false>(context, depth - 1, ply + 1, -beta, -alpha, true, king_checked, r#move)
} else {
zero_window_score
}
};
context.board.undo_move(r#move);
if score == -INVALID_SCORE {
continue;
}
if score > best_score {
best_score = cmp::max(best_score, score);
alpha = cmp::max(alpha, best_score);
best_move = r#move;
if alpha >= beta {
if r#move.is_quiet() {
context.ktable.add(ply, r#move);
context.htable.add(r#move.get_from(), r#move.get_to(), depth as u8);
if previous_move.is_some() {
context.cmtable.add(previous_move, r#move);
}
if state.stage == MoveGenStage::AllGenerated {
for i in state.quiet_moves_start_index..state.moves_count {
assert_fast!(state.moves_count < MAX_MOVES_COUNT);
let move_from_list = unsafe { state.moves[i].assume_init() };
if move_from_list.is_quiet() && move_from_list != best_move {
context.htable.punish(move_from_list.get_from(), move_from_list.get_to(), depth as u8);
}
}
}
}
dev!(context.stats.beta_cutoffs += 1);
if state.move_number == 0 {
dev!(context.stats.perfect_cutoffs += 1);
} else {
dev!(context.stats.non_perfect_cutoffs += 1);
}
break;
}
}
if ROOT && context.multipv {
context.board.make_move(best_move);
if !context.board.is_king_checked(context.board.stm ^ 1) {
let mut pv_line = context.ttable.get_pv_line(&mut context.board.clone(), 0);
pv_line.insert(0, best_move);
context.lines.push(SearchResultLine::new(best_score, pv_line));
}
context.board.undo_move(best_move);
alpha = original_alpha;
best_score = -CHECKMATE_SCORE;
}
}
if best_score == -CHECKMATE_SCORE + (ply as i16) + 1 && !friendly_king_checked {
return DRAW_SCORE;
}
if (!tt_entry_found || alpha != original_alpha) && !context.abort_flag.load(Ordering::Relaxed) {
let score_type = if alpha <= original_alpha {
TTableScoreType::UPPER_BOUND
} else if alpha >= beta {
TTableScoreType::LOWER_BOUND
} else {
TTableScoreType::EXACT_SCORE
};
context.ttable.add(context.board.state.hash, alpha, best_move, depth, ply, score_type, context.search_id);
dev!(context.stats.tt_added += 1);
}
if ROOT && !context.multipv {
let pv_line = context.ttable.get_pv_line(&mut context.board.clone(), 0);
context.lines.push(SearchResultLine::new(best_score, pv_line));
}
best_score
}
fn check_extensions_can_be_applied(friendly_king_checked: bool) -> bool {
friendly_king_checked
}
fn check_extensions_get_e() -> i8 {
1
}
fn iir_can_be_applied(context: &mut SearchContext, depth: i8, hash_move: Move) -> bool {
depth >= param!(context.params.iir_min_depth) && hash_move == Move::default()
}
fn iir_get_r(_context: &mut SearchContext, _depth: i8) -> i8 {
1
}
fn razoring_can_be_applied<const PV: bool>(context: &mut SearchContext, depth: i8, alpha: i16, friendly_king_checked: bool) -> bool {
let min_depth = param!(context.params.razoring_min_depth);
let max_depth = param!(context.params.razoring_max_depth);
!PV && depth >= min_depth && depth <= max_depth && !is_score_near_checkmate(alpha) && !friendly_king_checked
}
fn razoring_get_margin(context: &mut SearchContext, depth: i8) -> i16 {
let depth_margin_base = param!(context.params.razoring_depth_margin_base);
let min_depth = param!(context.params.razoring_min_depth);
let depth_margin_multiplier = param!(context.params.razoring_depth_margin_multiplier);
depth_margin_base + ((depth - min_depth) as i16) * depth_margin_multiplier
}
fn snmp_can_be_applied<const PV: bool>(context: &mut SearchContext, depth: i8, beta: i16, friendly_king_checked: bool) -> bool {
let min_depth = param!(context.params.snmp_min_depth);
let max_depth = param!(context.params.snmp_max_depth);
!PV && depth >= min_depth && depth <= max_depth && !is_score_near_checkmate(beta) && !friendly_king_checked
}
fn snmp_get_margin(context: &mut SearchContext, depth: i8) -> i16 {
let depth_margin_base = param!(context.params.snmp_depth_margin_base);
let min_depth = param!(context.params.snmp_min_depth);
let depth_margin_multiplier = param!(context.params.snmp_depth_margin_multiplier);
depth_margin_base + ((depth - min_depth) as i16) * depth_margin_multiplier
}
fn nmp_can_be_applied<const PV: bool>(context: &mut SearchContext, depth: i8, beta: i16, allow_null_move: bool, friendly_king_checked: bool) -> bool {
let min_depth = param!(context.params.nmp_min_depth);
let min_game_phase = param!(context.params.nmp_min_game_phase);
!PV && depth >= min_depth && context.board.game_phase > min_game_phase && !is_score_near_checkmate(beta) && !friendly_king_checked && allow_null_move
}
fn nmp_get_r(context: &mut SearchContext, depth: i8) -> i8 {
let depth_base = param!(context.params.nmp_depth_base);
let depth_divider = param!(context.params.nmp_depth_divider);
depth_base + depth / depth_divider
}
fn lmp_can_be_applied<const PV: bool>(context: &mut SearchContext, depth: i8, move_index: usize, move_score: i16, friendly_king_checked: bool) -> bool {
let min_depth = param!(context.params.lmp_min_depth);
let max_depth = param!(context.params.lmp_max_depth);
let move_index_margin_base = param!(context.params.lmp_move_index_margin_base);
let move_index_margin_multiplier = param!(context.params.lmp_move_index_margin_multiplier);
let max_score = param!(context.params.lmp_max_score);
!PV && depth >= min_depth
&& depth <= max_depth
&& move_index >= move_index_margin_base + (depth as usize - 1) * move_index_margin_multiplier
&& move_score <= max_score
&& !friendly_king_checked
}
fn lmr_can_be_applied<const PV: bool>(
context: &mut SearchContext,
depth: i8,
r#move: Move,
move_index: usize,
move_score: i16,
friendly_king_checked: bool,
enemy_king_checked: bool,
) -> bool {
let min_depth = param!(context.params.lmr_min_depth);
let min_move_index = if PV { param!(context.params.lmr_pv_min_move_index) } else { param!(context.params.lmr_min_move_index) };
let max_score = param!(context.params.lmr_max_score);
depth >= min_depth && move_index >= min_move_index && move_score <= max_score && r#move.is_quiet() && !friendly_king_checked && !enemy_king_checked
}
fn lmr_get_r<const PV: bool>(context: &mut SearchContext, move_index: usize) -> i8 {
let (max, r) = if PV {
let max_reduction = param!(context.params.lmr_pv_max_reduction);
let reduction_base = param!(context.params.lmr_pv_reduction_base);
let min_move_index = param!(context.params.lmr_pv_min_move_index);
let reduction_step = param!(context.params.lmr_pv_reduction_step);
(max_reduction, (reduction_base + (move_index - min_move_index) / reduction_step))
} else {
let max_reduction = param!(context.params.lmr_max_reduction);
let reduction_base = param!(context.params.lmr_reduction_base);
let min_move_index = param!(context.params.lmr_min_move_index);
let reduction_step = param!(context.params.lmr_reduction_step);
(max_reduction, (reduction_base + (move_index - min_move_index) / reduction_step))
};
cmp::min(max, r as i8)
}