use self::params::SearchParams;
use super::stats::SearchStats;
use super::*;
use crate::cache::counters::CMTable;
use crate::cache::history::HTable;
use crate::cache::killers::KTable;
use crate::cache::pawns::PHTable;
use crate::cache::search::TTable;
use crate::engine::clock;
use crate::state::movescan::Move;
use crate::state::representation::Board;
use crate::utils::panic_fast;
use std::cmp;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::RwLock;
use std::thread;
use std::time::SystemTime;
pub struct SearchContext {
pub board: Board,
pub params: SearchParams,
pub search_id: u8,
pub time: u32,
pub inc_time: u32,
pub current_depth: i8,
pub forced_depth: i8,
pub max_nodes_count: u64,
pub max_move_time: u32,
pub moves_to_go: u32,
pub moves_to_search: Vec<Move>,
pub search_time_start: SystemTime,
pub deadline: u32,
pub multipv: bool,
pub lines: Vec<SearchResultLine>,
pub search_done: bool,
pub uci_debug: bool,
pub ponder_mode: bool,
pub syzygy_enabled: bool,
pub syzygy_probe_limit: u32,
pub syzygy_probe_depth: i8,
pub ttable: Arc<TTable>,
pub phtable: Arc<PHTable>,
pub ktable: KTable,
pub htable: HTable,
pub cmtable: CMTable,
pub helper_contexts: Arc<RwLock<Vec<SearchContext>>>,
pub abort_flag: Arc<AtomicBool>,
pub ponder_flag: Arc<AtomicBool>,
pub stats: SearchStats,
pub last_score: i16,
}
pub struct SearchResult {
pub time: u32,
pub depth: i8,
}
pub struct SearchResultLine {
pub score: i16,
pub pv_line: Vec<Move>,
}
impl SearchContext {
pub fn new(board: Board, ttable: Arc<TTable>, phtable: Arc<PHTable>, abort_flag: Arc<AtomicBool>, ponder_flag: Arc<AtomicBool>) -> Self {
Self {
board,
params: SearchParams::default(),
search_id: 0,
time: 0,
inc_time: 0,
current_depth: 1,
forced_depth: 0,
max_nodes_count: 0,
max_move_time: 0,
moves_to_go: 0,
moves_to_search: Vec::new(),
search_time_start: SystemTime::now(),
deadline: 0,
multipv: false,
lines: Vec::new(),
search_done: false,
uci_debug: false,
ponder_mode: false,
syzygy_enabled: false,
syzygy_probe_limit: 0,
syzygy_probe_depth: 0,
ttable,
phtable,
ktable: KTable::default(),
htable: HTable::default(),
cmtable: CMTable::default(),
helper_contexts: Arc::default(),
abort_flag,
ponder_flag,
stats: SearchStats::default(),
last_score: 0,
}
}
}
impl Iterator for SearchContext {
type Item = SearchResult;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.search_done || self.current_depth >= MAX_DEPTH {
return None;
}
if self.forced_depth != 0 && self.current_depth > self.forced_depth {
return None;
}
if self.ponder_mode && self.forced_depth != 0 && self.current_depth == self.forced_depth {
loop {
if self.abort_flag.load(Ordering::Relaxed) {
break;
}
}
}
if self.forced_depth == 0 && self.current_depth == 1 {
if let Some(r#move) = self.board.get_instant_move() {
self.search_done = true;
self.lines.push(SearchResultLine::new(0, vec![r#move]));
return Some(SearchResult::new(0, self.current_depth));
}
if self.syzygy_enabled {
if let Some((r#move, score)) = self.board.get_tablebase_move(self.syzygy_probe_limit) {
self.search_done = true;
self.stats.tb_hits = 1;
self.lines.push(SearchResultLine::new(score, vec![r#move]));
return Some(SearchResult::new(0, self.current_depth));
}
}
}
let desired_time = if self.max_move_time != 0 {
self.max_move_time
} else {
let desired_time = clock::get_time_for_move(self.board.fullmove_number, self.time, self.inc_time, self.moves_to_go);
cmp::min(desired_time, self.time)
};
self.deadline = if self.max_move_time != 0 {
self.max_move_time
} else if self.current_depth > 1 {
let deadline = ((desired_time as f32) * DEADLINE_MULTIPLIER) as u32;
cmp::min(deadline, self.time)
} else {
u32::MAX
};
self.lines.clear();
let helper_contexts_arc = self.helper_contexts.clone();
let mut helper_contexts_lock = helper_contexts_arc.write().unwrap();
thread::scope(|scope| {
let depth = self.current_depth;
let mut threads = Vec::new();
for helper_context in helper_contexts_lock.iter_mut() {
helper_context.forced_depth = depth;
threads.push(scope.spawn(move || {
search::run(helper_context, depth);
}));
}
search::run(self, self.current_depth);
let reset_abort_flag = !self.abort_flag.load(Ordering::Relaxed);
self.abort_flag.store(true, Ordering::Relaxed);
for thread in threads {
thread.join().unwrap();
}
if reset_abort_flag {
self.abort_flag.store(false, Ordering::Relaxed);
}
});
for helper_context in helper_contexts_lock.iter() {
self.stats += &helper_context.stats;
}
if self.abort_flag.load(Ordering::Relaxed) {
if self.ponder_flag.load(Ordering::Relaxed) {
self.current_depth = 1;
self.forced_depth = 0;
self.search_time_start = SystemTime::now();
self.stats = SearchStats::default();
self.ponder_flag.store(false, Ordering::Relaxed);
self.abort_flag.store(false, Ordering::Relaxed);
continue;
} else {
if self.uci_debug {
println!("info string Search aborted");
}
return None;
}
}
if self.lines.is_empty() || self.lines[0].pv_line.is_empty() {
println!("info string Invalid position");
return None;
}
if self.lines[0].pv_line[0].is_empty() {
panic_fast!("Invalid PV move: {}", self.lines[0].pv_line[0]);
}
let search_time = self.search_time_start.elapsed().unwrap().as_millis() as u32;
self.lines.sort_by(|a, b| a.score.cmp(&b.score).reverse());
self.current_depth += 1;
if self.forced_depth == 0 && self.max_nodes_count == 0 {
if search_time > ((desired_time as f32) * TIME_THRESHOLD_RATIO) as u32 {
self.search_done = true;
}
if is_score_near_checkmate(self.lines[0].score) && self.current_depth >= (CHECKMATE_SCORE - self.lines[0].score.abs()) as i8 {
self.search_done = true;
}
}
return Some(SearchResult::new(search_time, self.current_depth - 1));
}
}
}
impl SearchResult {
pub fn new(time: u32, depth: i8) -> Self {
Self { time, depth }
}
}
impl SearchResultLine {
pub fn new(score: i16, pv_line: Vec<Move>) -> Self {
Self { score, pv_line }
}
}