From ede46552feeb0ecf8f7971df5d58224a3de4adf0 Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Sat, 28 Dec 2024 15:59:27 -0500 Subject: [PATCH] feat: uci options, quiet position detector --- src/coordination.rs | 2 ++ src/main.rs | 72 ++++++++++++++++++++++++++++++++++++++++++--- src/search.rs | 33 +++++++++++++++++---- 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/coordination.rs b/src/coordination.rs index 5593977..5259217 100644 --- a/src/coordination.rs +++ b/src/coordination.rs @@ -165,6 +165,8 @@ pub struct MsgBestmove { pub pv: Vec, /// Evaluation of the position pub eval: SearchEval, + /// Extra information (displayed as `info string`). + pub info: Vec, } /// Interface messages that may be received by main's channel. diff --git a/src/main.rs b/src/main.rs index 8dcba10..14aa9d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,8 +51,9 @@ macro_rules! ignore { /// UCI engine metadata query. fn cmd_uci() -> String { let str = "id name chess_inator\n\ - id author dogeystamp\n\ - uciok"; + id author dogeystamp\n\ + option name NNUETrainInfo type check default false\n\ + uciok"; str.into() } @@ -123,7 +124,6 @@ fn cmd_go(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) { }; } - while let Some(token) = tokens.next() { match token { "wtime" => { @@ -207,6 +207,50 @@ fn cmd_eval(mut _tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) { println!("- total: {}", res.total_eval); } +fn match_true_false(s: &str) -> Option { + match s { + "true" => Some(true), + "false" => Some(false), + _ => None, + } +} + +/// Set engine options via UCI. +fn cmd_setoption(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) { + while let Some(token) = tokens.next() { + fn get_val(mut tokens: std::str::SplitWhitespace<'_>) -> Option { + if let Some("value") = tokens.next() { + if let Some(value) = tokens.next() { + return Some(value.to_string()); + } + } + None + } + + match token { + "name" => { + if let Some(name) = tokens.next() { + match name { + "NNUETrainInfo" => { + if let Some(value) = get_val(tokens) { + if let Some(value) = match_true_false(&value) { + state.config.nnue_train_info = value; + } + } + } + _ => { + println!("info string Unknown option: {}", name) + } + } + } + } + _ => ignore!(), + } + + break; + } +} + /// Root UCI parser. fn cmd_root(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) { while let Some(token) = tokens.next() { @@ -242,6 +286,9 @@ fn cmd_root(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) { state.tx_engine.send(MsgToEngine::Stop).unwrap(); } } + "setoption" => { + cmd_setoption(tokens, state); + } // non-standard command. "eval" => { cmd_eval(tokens, state); @@ -274,6 +321,10 @@ fn outp_bestmove(bestmove: MsgBestmove) { panic!("info string ERROR: stopped search") } } + for line in bestmove.info { + println!("info string {line}"); + } + match chosen { Some(mv) => println!("bestmove {}", mv.to_uci_algebraic()), None => println!("bestmove 0000"), @@ -315,8 +366,21 @@ fn task_engine(tx_main: Sender, rx_engine: Receiver) { state.config = msg_box.config; state.time_lims = msg_box.time_lims; let (pv, eval) = best_line(&mut board, &mut state); + + let mut info: Vec = Vec::new(); + if state.config.nnue_train_info { + let is_quiet = chess_inator::search::is_quiescent_position(&board, eval); + + let is_quiet = if is_quiet {"quiet"} else {"non-quiet"}; + info.push(format!("NNUETrainInfo {}", is_quiet)) + } + tx_main - .send(MsgToMain::Bestmove(MsgBestmove { pv, eval })) + .send(MsgToMain::Bestmove(MsgBestmove { + pv, + eval, + info, + })) .unwrap(); } MsgToEngine::Stop => {} diff --git a/src/search.rs b/src/search.rs index ddb9c76..834d421 100644 --- a/src/search.rs +++ b/src/search.rs @@ -128,6 +128,8 @@ pub struct SearchConfig { pub enable_trans_table: bool, /// Transposition table size (2^n where this is n) pub transposition_size: usize, + /// Print machine-readable information about the position during NNUE training data generation. + pub nnue_train_info: bool, } impl Default for SearchConfig { @@ -139,6 +141,7 @@ impl Default for SearchConfig { contempt: 0, enable_trans_table: true, transposition_size: 24, + nnue_train_info: false, } } } @@ -450,11 +453,7 @@ impl TimeLimits { /// Make time limits based on wtime, btime (but color-independent). /// /// Also takes in eval metrics, for instance to avoid wasting too much time in the opening. - pub fn from_ourtime_theirtime( - ourtime_ms: u64, - _theirtime_ms: u64, - eval: EvalMetrics, - ) -> Self { + pub fn from_ourtime_theirtime(ourtime_ms: u64, _theirtime_ms: u64, eval: EvalMetrics) -> Self { // hard timeout (max) let mut hard_ms = 100_000; // soft timeout (default max) @@ -550,3 +549,27 @@ pub fn best_move(board: &mut Board, engine_state: &mut EngineState) -> Option bool { + // max centipawn value difference to call "similar" + const THRESHOLD: EvalInt = 170; + + if board.is_check(board.turn) { + return false; + } + + if matches!(eval, SearchEval::Checkmate(_)) { + return false; + } + + // white perspective + let abs_eval = EvalInt::from(eval) * EvalInt::from(board.turn.sign()); + + (board.eval() - EvalInt::from(abs_eval)).abs() <= THRESHOLD.abs() +}