use super::*;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct EngineProperties {
_use_mate_distance_pruning: bool,
_clear_table_after_each_search: bool,
_use_lmr: bool,
}
impl EngineProperties {
#[inline]
pub fn use_mate_distance_pruning(&self) -> bool {
self._use_mate_distance_pruning && !DISABLE_ALL_PRUNINGS
}
#[inline]
pub fn set_using_mate_distance_pruning(&mut self, value: bool) {
self._use_mate_distance_pruning = value;
}
#[inline]
pub fn clear_table_after_each_search(&self) -> bool {
self._clear_table_after_each_search || DISABLE_ALL_PRUNINGS
}
#[inline]
pub fn set_clearing_table_after_each_search(&mut self, value: bool) {
self._clear_table_after_each_search = value;
}
#[inline]
pub fn use_lmr(&self) -> bool {
self._use_lmr && !DISABLE_ALL_PRUNINGS
}
#[inline]
pub fn set_using_lmr(&mut self, value: bool) {
self._use_lmr = value;
}
}
impl Default for EngineProperties {
fn default() -> Self {
Self {
_use_mate_distance_pruning: true,
_clear_table_after_each_search: true,
_use_lmr: true,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CustomEngine<T: SearchControl<Searcher<P>>, P: PositionEvaluation> {
board: Board,
transposition_table: Arc<TranspositionTable>,
evaluator: P,
last_score: Option<Score>,
controller: T,
num_threads: NonZeroUsize,
num_nodes_searched: Arc<AtomicUsize>,
selective_depth: Arc<AtomicUsize>,
#[cfg_attr(feature = "serde", serde(skip))]
optional_io_reader: Option<IoReader>,
stop_command: Arc<AtomicBool>,
terminate: Arc<AtomicBool>,
properties: EngineProperties,
#[cfg_attr(feature = "serde", serde(skip))]
opening_book: Option<Arc<dyn PolyglotBook>>,
}
impl<T: SearchControl<Searcher<P>>, P: PositionEvaluation> CustomEngine<T, P> {
pub fn new(
board: Board,
transposition_table: TranspositionTable,
controller: T,
evaluator: P,
) -> Self {
Self {
board,
transposition_table: transposition_table.into(),
evaluator,
last_score: None,
controller,
num_threads: TIMECAT_DEFAULTS.num_threads,
num_nodes_searched: AtomicUsize::new(0).into(),
selective_depth: AtomicUsize::new(0).into(),
optional_io_reader: None,
stop_command: AtomicBool::new(false).into(),
terminate: AtomicBool::new(false).into(),
properties: EngineProperties::default(),
opening_book: TIMECAT_DEFAULTS
.inbuilt_book_bytes
.and_then(|bytes| PolyglotBookHashMap::try_from(bytes).ok())
.map(|book| Arc::new(book) as Arc<dyn PolyglotBook>),
}
}
#[inline]
fn get_transposition_table(&self) -> &TranspositionTable {
&self.transposition_table
}
#[inline]
pub fn get_search_controller(&self) -> &(impl SearchControl<Searcher<P>> + use<T, P>) {
&self.controller
}
#[inline]
pub fn get_search_controller_mut(
&mut self,
) -> &mut (impl SearchControl<Searcher<P>> + use<T, P>) {
&mut self.controller
}
#[inline]
pub fn get_properties(&self) -> &EngineProperties {
&self.properties
}
#[inline]
pub fn get_properties_mut(&mut self) -> &mut EngineProperties {
&mut self.properties
}
#[inline]
fn get_num_nodes_searched(&self) -> usize {
self.num_nodes_searched.load(MEMORY_ORDERING)
}
#[inline]
pub fn get_selective_depth(&self) -> Ply {
self.selective_depth.load(MEMORY_ORDERING)
}
#[inline]
pub fn get_num_threads(&self) -> usize {
self.num_threads.get()
}
#[inline]
pub fn get_move_overhead(&self) -> Duration {
self.controller.get_move_overhead()
}
#[inline]
pub fn get_optional_io_reader(&self) -> Option<IoReader> {
self.optional_io_reader.clone()
}
pub fn reset_variables(&mut self) {
self.num_nodes_searched.store(0, MEMORY_ORDERING);
self.selective_depth.store(0, MEMORY_ORDERING);
self.controller.reset_variables();
self.evaluator.reset_variables();
if self.properties.clear_table_after_each_search() {
self.transposition_table.clear();
}
self.transposition_table.reset_variables();
self.set_stop_command(false);
self.set_termination(false);
}
#[inline]
pub fn generate_searcher(&self, id: usize) -> Searcher<P> {
Searcher::new(
id,
self.last_score,
self.board.clone(),
self.evaluator.clone(),
self.transposition_table.clone(),
self.num_nodes_searched.clone(),
self.selective_depth.clone(),
self.stop_command.clone(),
self.properties.clone(),
)
}
#[inline]
pub fn get_stop_command(&self) -> bool {
self.stop_command.load(MEMORY_ORDERING)
}
#[inline]
pub fn set_stop_command(&self, b: bool) {
self.stop_command.store(b, MEMORY_ORDERING);
}
fn update_stop_command(
stop_command: Arc<AtomicBool>,
io_reader: IoReader,
terminate: Arc<AtomicBool>,
) {
while !stop_command.load(MEMORY_ORDERING) {
match io_reader
.read_line_once()
.unwrap_or_default()
.to_lowercase()
.trim()
{
"stop" => stop_command.store(true, MEMORY_ORDERING),
"quit" | "exit" => {
stop_command.store(true, MEMORY_ORDERING);
terminate.store(true, MEMORY_ORDERING);
}
_ => {}
}
}
}
}
impl<T: SearchControl<Searcher<P>>, P: PositionEvaluation> ChessEngine for CustomEngine<T, P> {
type IoReader = IoReader;
#[inline]
fn get_board(&self) -> &Board {
&self.board
}
#[inline]
fn get_board_mut(&mut self) -> &mut Board {
&mut self.board
}
fn set_fen(&mut self, fen: &str) -> Result<()> {
self.get_board_mut().set_fen(fen)?;
self.reset_variables();
Ok(())
}
fn set_transposition_table_size(&self, size: CacheTableSize) {
self.transposition_table.set_size(size);
if GLOBAL_TIMECAT_STATE.is_in_debug_mode() {
self.transposition_table.print_info();
}
}
#[inline]
fn set_num_threads(&mut self, num_threads: NonZeroUsize) {
self.num_threads = num_threads;
}
#[inline]
fn set_move_overhead(&mut self, duration: Duration) {
self.controller.set_move_overhead(duration);
}
#[inline]
fn get_opening_book(&self) -> Option<&dyn PolyglotBook> {
self.opening_book.as_deref()
}
#[inline]
fn set_opening_book<B: PolyglotBook + 'static>(&mut self, book: Option<Arc<B>>) {
self.opening_book = book.map(|b| b as Arc<dyn PolyglotBook>);
}
#[inline]
fn terminate(&self) -> bool {
self.terminate.load(MEMORY_ORDERING)
}
#[inline]
fn set_termination(&self, b: bool) {
self.terminate.store(b, MEMORY_ORDERING);
}
fn clear_hash(&mut self) {
self.get_transposition_table().clear();
self.evaluator.clear();
}
fn print_info(&self) {
print_engine_version();
println_wasm!();
self.transposition_table.print_info();
self.evaluator.print_info();
}
#[inline]
fn set_optional_io_reader(&mut self, optional_io_reader: Self::IoReader) {
self.optional_io_reader = Some(optional_io_reader);
}
#[inline]
fn evaluate_current_position(&mut self) -> Score {
self.evaluator.evaluate(&self.board)
}
#[inline]
fn evaluate_current_position_flipped(&mut self) -> Score {
self.evaluator.evaluate_flipped(&self.board)
}
fn search(&mut self, config: &SearchConfig, verbose: bool) -> SearchInfo {
if let Some(WeightedMove { move_, weight }) = self.get_opening_book_weighted_move() {
return SearchInfoBuilder::new(self.board.get_position().clone(), vec![move_])
.set_score(weight as Score)
.build();
}
self.reset_variables();
let mut join_handles = vec![];
for id in 1..self.num_threads.get() {
let mut threaded_searcher = self.generate_searcher(id);
let controller = self.controller.clone();
let join_handle = thread::spawn(move || {
threaded_searcher.search(
const { &SearchConfig::new_infinite() },
controller,
false,
);
});
join_handles.push(join_handle);
}
if let Some(io_reader) = self.optional_io_reader.as_ref() {
let stop_command = self.stop_command.clone();
let reader = io_reader.clone();
let terminate = self.terminate.clone();
join_handles.push(thread::spawn(move || {
Self::update_stop_command(stop_command, reader, terminate);
}));
}
let mut main_thread_searcher = self.generate_searcher(0);
main_thread_searcher.search(config, self.controller.clone(), verbose);
self.set_stop_command(true);
for join_handle in join_handles {
join_handle.join().unwrap();
}
let mut search_info = main_thread_searcher.get_search_info();
if search_info.get_pv().is_empty() && self.board.status() == BoardStatus::Ongoing {
search_info.set_pv(vec![
self.board
.get_single_legal_move(BitBoard::ALL, BitBoard::ALL)
.unwrap(),
]);
}
self.last_score = search_info.get_score();
search_info
}
}
impl<T: SearchControl<Searcher<P>> + Default, P: PositionEvaluation + Default> CustomEngine<T, P> {
#[inline]
pub fn from_board(board: Board) -> Self {
Self::new(
board,
TranspositionTable::default(),
T::default(),
P::default(),
)
}
#[inline]
pub fn from_fen(fen: &str) -> Result<Self> {
Ok(Self::from_board(Board::from_fen(fen)?))
}
}
impl<T: SearchControl<Searcher<P>>, P: PositionEvaluation> Clone for CustomEngine<T, P> {
fn clone(&self) -> Self {
Self {
board: self.board.clone(),
transposition_table: self.transposition_table.as_ref().clone().into(),
evaluator: self.evaluator.clone(),
controller: self.controller.clone(),
num_nodes_searched: AtomicUsize::new(self.num_nodes_searched.load(MEMORY_ORDERING))
.into(),
selective_depth: AtomicUsize::new(self.selective_depth.load(MEMORY_ORDERING)).into(),
optional_io_reader: self.optional_io_reader.clone(),
stop_command: AtomicBool::new(self.stop_command.load(MEMORY_ORDERING)).into(),
terminate: AtomicBool::new(self.terminate.load(MEMORY_ORDERING)).into(),
properties: self.properties.clone(),
opening_book: self.opening_book.clone(),
..*self
}
}
}
impl<T: SearchControl<Searcher<P>> + Default, P: PositionEvaluation + Default> Default
for CustomEngine<T, P>
{
fn default() -> Self {
Self::new(
Board::default(),
TranspositionTable::default(),
T::default(),
P::default(),
)
}
}