diff --git a/README.md b/README.md index 47741a3..d71068a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Features: - Piece-square tables - Tapered midgame-endgame evaluation - UCI compatibility +- Iterative deepening ## instructions diff --git a/src/bin/engine.rs b/src/bin/engine.rs index 288811c..62c4523 100644 --- a/src/bin/engine.rs +++ b/src/bin/engine.rs @@ -12,12 +12,15 @@ Copyright © 2024 dogeystamp //! Main UCI engine binary. +use chess_inator::eval::eval_metrics; use chess_inator::fen::FromFen; use chess_inator::movegen::{FromUCIAlgebraic, Move, ToUCIAlgebraic}; -use chess_inator::search::{best_line, SearchEval}; -use chess_inator::eval::{eval_metrics}; +use chess_inator::search::{best_line, InterfaceMsg, SearchEval}; use chess_inator::Board; use std::io; +use std::sync::mpsc::channel; +use std::thread; +use std::time::{Duration, Instant}; /// UCI protocol says to ignore any unknown words. /// @@ -88,7 +91,18 @@ fn cmd_position(mut tokens: std::str::SplitWhitespace<'_>) -> Board { /// Play the game. fn cmd_go(mut _tokens: std::str::SplitWhitespace<'_>, board: &mut Board) { - let (line, eval) = best_line(board, None); + // interface-to-engine + let (tx1, rx) = channel(); + let tx2 = tx1.clone(); + + // timeout + thread::spawn(move || { + thread::sleep(Duration::from_millis(1000)); + let _ = tx2.send(InterfaceMsg::Stop); + }); + + let (line, eval) = best_line(board, None, Some(rx)); + let chosen = line.last().copied(); println!( "info pv{}", diff --git a/src/search.rs b/src/search.rs index a7ea5de..8abb364 100644 --- a/src/search.rs +++ b/src/search.rs @@ -17,6 +17,7 @@ use crate::eval::{Eval, EvalInt}; use crate::movegen::{Move, MoveGen, ToUCIAlgebraic}; use crate::{Board, Piece}; use std::cmp::max; +use std::sync::mpsc; // min can't be represented as positive const EVAL_WORST: EvalInt = -(EvalInt::MAX); @@ -103,7 +104,7 @@ impl Default for SearchConfig { fn default() -> Self { SearchConfig { alpha_beta_on: true, - depth: 5, + depth: 10, } } } @@ -216,16 +217,54 @@ fn minmax( (best_continuation, abs_best) } +/// Messages from the interface to the search thread. +pub enum InterfaceMsg { + Stop, +} + +type InterfaceRx = mpsc::Receiver; + +/// Iteratively deepen search until it is stopped. +fn iter_deep( + board: &mut Board, + config: &SearchConfig, + interface: Option, +) -> (Vec, SearchEval) { + for depth in 1..=config.depth { + let (line, eval) = minmax(board, config, depth, None, None); + if let Some(ref rx) = interface { + match rx.try_recv() { + Ok(msg) => match msg { + InterfaceMsg::Stop => return (line, eval), + }, + Err(e) => match e { + mpsc::TryRecvError::Empty => {} + mpsc::TryRecvError::Disconnected => panic!("interface thread stopped"), + }, + } + } + } + panic!("iterative deepening did not search at all") +} + /// Find the best line (in reverse order) and its evaluation. -pub fn best_line(board: &mut Board, config: Option) -> (Vec, SearchEval) { +pub fn best_line( + board: &mut Board, + config: Option, + interface: Option, +) -> (Vec, SearchEval) { let config = config.unwrap_or_default(); - let (line, eval) = minmax(board, &config, config.depth, None, None); + let (line, eval) = iter_deep(board, &config, interface); (line, eval) } /// Find the best move. -pub fn best_move(board: &mut Board, config: Option) -> Option { - let (line, _eval) = best_line(board, Some(config.unwrap_or_default())); +pub fn best_move( + board: &mut Board, + config: Option, + interface: Option, +) -> Option { + let (line, _eval) = best_line(board, Some(config.unwrap_or_default()), interface); line.last().copied() } @@ -250,8 +289,8 @@ mod tests { Some(SearchConfig { alpha_beta_on: false, depth: 3, - quiesce_depth: Default::default(), }), + None, ) .unwrap(); @@ -262,8 +301,8 @@ mod tests { Some(SearchConfig { alpha_beta_on: true, depth: 3, - quiesce_depth: Default::default(), }), + None, ) .unwrap();