From 429485ae7374aeaad3570d706e7909c1d488cf15 Mon Sep 17 00:00:00 2001 From: stefiosif Date: Sun, 10 Nov 2024 14:22:24 +0200 Subject: [PATCH] Use one TT per game, use time manger in ID --- src/interface/uci.rs | 78 ++++++++++++++++++++----------- src/search/iterative_deepening.rs | 55 ++++++++++++++++++---- src/search/mod.rs | 7 ++- 3 files changed, 100 insertions(+), 40 deletions(-) diff --git a/src/interface/uci.rs b/src/interface/uci.rs index c7ac581..32d6ca4 100644 --- a/src/interface/uci.rs +++ b/src/interface/uci.rs @@ -4,9 +4,12 @@ use std::{ }; use crate::{ - board::game::Game, + board::{board::Color, game::Game}, movegen::r#move::Move, - search::{self, MAX_DEPTH, MOVE_TIME}, + search::{ + iterative_deepening, transposition_table::TranspositionTable, MAX_DEPTH, MAX_TT_SIZE, + REMAINING_TIME_DEFAULT, + }, }; #[derive(PartialEq, Eq, Debug)] @@ -62,6 +65,8 @@ struct UciParameters { movetime: Option, depth: Option, game: Option, + wtime: Option, + btime: Option, } impl UciParameters { @@ -70,6 +75,8 @@ impl UciParameters { movetime: None, depth: None, game: None, + wtime: None, + btime: None, } } @@ -81,6 +88,14 @@ impl UciParameters { self.depth = Some(depth); } + fn add_wtime(&mut self, wtime: u128) { + self.wtime = Some(wtime); + } + + fn add_btime(&mut self, btime: u128) { + self.btime = Some(btime); + } + fn add_game(&mut self, game: Game) { self.game = Some(game); } @@ -116,38 +131,41 @@ pub fn uci_position(position: &mut SplitWhitespace) -> Result { Ok(game) } -pub fn uci_go(go: &mut SplitWhitespace, game: &mut Game) -> Result { +pub fn uci_go( + go_iter: &mut SplitWhitespace, + game: &mut Game, + tt: &mut TranspositionTable, +) -> Result { let mut params = UciParameters::new(); - while let Some(subcommand) = go.next() { + while let Some(subcommand) = go_iter.next() { match subcommand { - // TODO: Add new commands - "wtime" => (), - "btime" => (), - "movestogo" => (), - "depth" => { - let depth_str = go.next().ok_or("Expected depth value")?; - let depth = depth_str.parse::().map_err(|_| "Invalid depth value")?; - params.add_depth(depth); - } - "movetime" => { - let movetime_str = go.next().ok_or("Expected movetime value")?; - let movetime = movetime_str - .parse::() - .map_err(|_| "Invalid movetime value")?; - params.add_movetime(movetime); - } + "wtime" => params.add_wtime(parse_next(go_iter, "wtime")?), + "btime" => params.add_btime(parse_next(go_iter, "btime")?), + "depth" => params.add_depth(parse_next(go_iter, "depth")?), + "movetime" => params.add_movetime(parse_next(go_iter, "movetime")?), _ => (), } } - Ok( - search::iterative_deepening::iterative_deepening(game, MAX_DEPTH, MOVE_TIME) - .expect("No move selected"), - ) + let remaining_time = match game.current_player() { + Color::White => params.wtime.unwrap_or(REMAINING_TIME_DEFAULT), + Color::Black => params.btime.unwrap_or(REMAINING_TIME_DEFAULT), + }; + + iterative_deepening::iterative_deepening(game, MAX_DEPTH, remaining_time, tt) + .ok_or_else(|| "No move selected".to_string()) +} + +fn parse_next(go_iter: &mut SplitWhitespace, val: &str) -> Result { + go_iter + .next() + .ok_or_else(|| format!("Expected {val}")) + .and_then(|v| v.parse::().map_err(|_| format!("Invalid {val}"))) } pub fn uci_loop(input: R, mut output: W) -> Result<(), String> { let mut params = UciParameters::new(); + let mut tt = TranspositionTable::new(MAX_TT_SIZE); for line in input.lines() { let line_str = line.unwrap_or_else(|_| "quit".to_string()); @@ -156,15 +174,17 @@ pub fn uci_loop(input: R, mut output: W) -> Result<(), Str let response = match command { Command::Uci => Response::UciOk, Command::IsReady => Response::ReadyOk, - Command::UciNewGame => Response::Info("Clear cache".to_string()), + Command::UciNewGame => { + tt = TranspositionTable::new(MAX_TT_SIZE); + Response::Info("Clear cache".to_string()) + } Command::Position => { params.add_game(uci_position(&mut parts)?); - // dbg!(¶ms); Response::Info("Initialized position".to_string()) } Command::Go => { if let Some(ref mut game) = params.game { - let best_move = uci_go(&mut parts, game)?; + let best_move = uci_go(&mut parts, game, &mut tt)?; Response::BestMove(best_move.parse_into_str()) } else { Response::Info("Going?".to_string()) @@ -188,6 +208,7 @@ mod tests { board::{fen::from_fen, square::Square}, interface::uci::{parse_command, Command}, movegen::{attack_generator::init_attacks, r#move::Move}, + search::{transposition_table::TranspositionTable, MAX_TT_SIZE}, }; use super::uci_go; @@ -223,10 +244,11 @@ mod tests { #[test] fn test_uci_go() -> Result<(), String> { init_attacks(); + let mut tt = TranspositionTable::new(MAX_TT_SIZE); let mut game = from_fen(FEN_MATE_IN_1)?; let command_go = "go depth 2"; let mut parts = command_go.split_whitespace(); - let response = uci_go(&mut parts, &mut game)?; + let response = uci_go(&mut parts, &mut game, &mut tt)?; assert_eq!(Move::new(Square::E3, Square::F2), response); diff --git a/src/search/iterative_deepening.rs b/src/search/iterative_deepening.rs index 7f82ece..ab4bac1 100644 --- a/src/search/iterative_deepening.rs +++ b/src/search/iterative_deepening.rs @@ -1,30 +1,58 @@ +use std::time::Instant; + use crate::{ board::game::Game, evaluation::{MAX_SCORE, MIN_SCORE}, movegen::r#move::Move, }; -use super::{negamax, transposition_table::TranspositionTable, MAX_TT_SIZE}; +use super::{ + negamax, transposition_table::TranspositionTable, HARD_LIMIT_DIVISION, SOFT_EVAL_THRESHOLD, + SOFT_LIMIT_DIVISION, +}; -pub fn iterative_deepening(game: &mut Game, max_depth: u8, move_time: u128) -> Option { - let mut tt = TranspositionTable::new(MAX_TT_SIZE); - let mut mv: Option = None; +pub fn iterative_deepening( + game: &mut Game, + max_depth: u8, + remaining_time: u128, + tt: &mut TranspositionTable, +) -> Option { + let (mut best_move, mut best_eval): (Option, i32) = (None, MIN_SCORE); let time_now = std::time::Instant::now(); for depth in 1..=max_depth { - if time_now.elapsed().as_millis() >= move_time { - return mv; + if time_limit_reached(&time_now, remaining_time, best_eval) { + return best_move; } - (mv, _) = negamax::negamax(game, MIN_SCORE, MAX_SCORE, depth, 0, &mut tt); + (best_move, best_eval) = negamax::negamax(game, MIN_SCORE, MAX_SCORE, depth, 0, tt); } - mv + best_move +} + +fn time_limit_reached(time: &Instant, remaining_time: u128, eval: i32) -> bool { + hard_limit(time, remaining_time) || soft_limit(time, remaining_time, eval) +} + +fn hard_limit(time_now: &Instant, remaining_time: u128) -> bool { + time_now.elapsed().as_millis() >= remaining_time / HARD_LIMIT_DIVISION +} + +fn soft_limit(time: &Instant, remaining_time: u128, eval: i32) -> bool { + time.elapsed().as_millis() >= remaining_time / SOFT_LIMIT_DIVISION && eval > SOFT_EVAL_THRESHOLD } #[cfg(test)] mod tests { - use crate::{board::fen::from_fen, movegen::attack_generator::init_attacks, search}; + use crate::{ + board::fen::from_fen, + movegen::attack_generator::init_attacks, + search::{ + iterative_deepening, transposition_table::TranspositionTable, MAX_DEPTH, MAX_TT_SIZE, + REMAINING_TIME_DEFAULT, + }, + }; const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1"; @@ -32,9 +60,16 @@ mod tests { fn test_iterative_deepening() -> Result<(), String> { init_attacks(); let mut game = from_fen(FEN)?; + let mut tt = TranspositionTable::new(MAX_TT_SIZE); let time_now = std::time::Instant::now(); - let _ = search::iterative_deepening::iterative_deepening(&mut game, 6, 1000); + iterative_deepening::iterative_deepening( + &mut game, + MAX_DEPTH, + REMAINING_TIME_DEFAULT, + &mut tt, + ); + dbg!(time_now.elapsed()); Ok(()) diff --git a/src/search/mod.rs b/src/search/mod.rs index 356b936..e763baf 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -5,7 +5,10 @@ pub mod perft; pub mod quiescence; pub mod transposition_table; -pub const MAX_DEPTH: u8 = 4; +pub const MAX_DEPTH: u8 = 7; pub const QUIESCENCE_DEPTH: u8 = 3; -pub const MOVE_TIME: u128 = 1000; +pub const REMAINING_TIME_DEFAULT: u128 = 100000; // in ms +pub const HARD_LIMIT_DIVISION: u128 = 10; +pub const SOFT_LIMIT_DIVISION: u128 = 2; +pub const SOFT_EVAL_THRESHOLD: i32 = 500; pub const MAX_TT_SIZE: u64 = 1000000;