feat: iterative deepening

This commit is contained in:
dogeystamp 2024-11-16 15:57:01 -05:00
parent 69dfa98bef
commit bec310c182
3 changed files with 64 additions and 10 deletions

View File

@ -8,6 +8,7 @@ Features:
- Piece-square tables
- Tapered midgame-endgame evaluation
- UCI compatibility
- Iterative deepening
## instructions

View File

@ -12,12 +12,15 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
//! 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{}",

View File

@ -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<InterfaceMsg>;
/// Iteratively deepen search until it is stopped.
fn iter_deep(
board: &mut Board,
config: &SearchConfig,
interface: Option<InterfaceRx>,
) -> (Vec<Move>, 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<SearchConfig>) -> (Vec<Move>, SearchEval) {
pub fn best_line(
board: &mut Board,
config: Option<SearchConfig>,
interface: Option<InterfaceRx>,
) -> (Vec<Move>, 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<SearchConfig>) -> Option<Move> {
let (line, _eval) = best_line(board, Some(config.unwrap_or_default()));
pub fn best_move(
board: &mut Board,
config: Option<SearchConfig>,
interface: Option<InterfaceRx>,
) -> Option<Move> {
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();