From b98100aa718f6210bf16949244fc4714c707f450 Mon Sep 17 00:00:00 2001 From: stefiosif Date: Tue, 4 Feb 2025 23:53:18 +0200 Subject: [PATCH] Add uci movetime, hash and initialize TT based on size in MBs --- src/board/fen.rs | 4 +- src/board/game.rs | 4 +- src/interface/uci.rs | 67 +++++++++++++++++++++++++------ src/search/mod.rs | 2 +- src/search/transposition_table.rs | 21 +++++++--- 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/board/fen.rs b/src/board/fen.rs index c60d30f..1074c9d 100644 --- a/src/board/fen.rs +++ b/src/board/fen.rs @@ -2,7 +2,7 @@ use crate::board::board::{Board, Color, PieceType}; use crate::board::game::Game; use crate::board::state::{Castle, State}; use crate::search::transposition_table::TranspositionTable; -use crate::search::{MAX_DEPTH, MAX_TT_SIZE}; +use crate::search::MAX_DEPTH; use String as FenError; pub fn from_fen(fen: &str) -> Result { @@ -36,7 +36,7 @@ pub fn from_fen(fen: &str) -> Result { history: History::new(), mailbox, hash, - tt: TranspositionTable::new(MAX_TT_SIZE), + tt: TranspositionTable::new(), killer: [None; MAX_DEPTH as usize], }) } diff --git a/src/board/game.rs b/src/board/game.rs index cbf6662..992bb44 100644 --- a/src/board/game.rs +++ b/src/board/game.rs @@ -1,7 +1,7 @@ use crate::{ board::fen::from_fen, movegen::r#move::{Move, MoveType}, - search::{transposition_table::TranspositionTable, MAX_DEPTH, MAX_TT_SIZE}, + search::{transposition_table::TranspositionTable, MAX_DEPTH}, }; use String as FenError; @@ -39,7 +39,7 @@ impl Game { history: History::new(), mailbox: Mailbox::from_board(&Board::startpos()), hash: zobrist_keys().calculate_hash(&Board::startpos()), - tt: TranspositionTable::new(MAX_TT_SIZE), + tt: TranspositionTable::new(), killer: [None; MAX_DEPTH as usize], } } diff --git a/src/interface/uci.rs b/src/interface/uci.rs index 370618c..fb190bf 100644 --- a/src/interface/uci.rs +++ b/src/interface/uci.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, io::{BufRead, Write}, str::SplitWhitespace, }; @@ -13,7 +14,7 @@ use crate::{ movegen::r#move::Move, search::{ iterative_deepening, time::TimeInfo, transposition_table::TranspositionTable, INC, - MAX_DEPTH, MAX_TT_SIZE, TIME, + MAX_DEPTH, TIME, }, }; @@ -26,6 +27,7 @@ pub enum Command { Go, Stop, Quit, + SetOption, } fn parse_command(parts: &mut SplitWhitespace) -> anyhow::Result { @@ -37,6 +39,7 @@ fn parse_command(parts: &mut SplitWhitespace) -> anyhow::Result { Some("go") => Ok(Command::Go), Some("stop") => Ok(Command::Stop), Some("quit") => Ok(Command::Quit), + Some("setoption") => Ok(Command::SetOption), _ => bail!("Unrecognised command"), } } @@ -75,10 +78,11 @@ struct UciParameters { btime: Option, winc: Option, binc: Option, + options: HashMap, } impl UciParameters { - const fn new() -> Self { + fn new() -> Self { Self { depth: None, game: None, @@ -86,9 +90,15 @@ impl UciParameters { btime: None, winc: None, binc: None, + options: HashMap::new(), } } + fn add_move_time(&mut self, movetime: u128) { + self.wtime = Some(movetime); + self.btime = Some(movetime); + } + fn add_depth(&mut self, depth: u8) { self.depth = Some(depth); } @@ -112,6 +122,10 @@ impl UciParameters { fn add_game(&mut self, game: Game) { self.game = Some(game); } + + fn add_option(&mut self, name: String, value: String) { + self.options.insert(name, value); + } } impl Default for UciParameters { @@ -156,6 +170,7 @@ pub fn uci_go(go_iter: &mut SplitWhitespace, game: &mut Game) -> anyhow::Result< "winc" => params.add_winc(parse_next(go_iter, "winc")?), "binc" => params.add_binc(parse_next(go_iter, "binc")?), "depth" => params.add_depth(parse_next(go_iter, "depth")?), + "movetime" => params.add_move_time(parse_next(go_iter, "movetime")?), _ => (), } } @@ -173,9 +188,34 @@ pub fn uci_go(go_iter: &mut SplitWhitespace, game: &mut Game) -> anyhow::Result< .ok_or_else(|| anyhow!("No stored best move found")) } -fn parse_next(go_iter: &mut SplitWhitespace, val: &str) -> anyhow::Result { - go_iter - .next() +fn uci_option(option_iter: &mut SplitWhitespace, game: &mut Game) -> anyhow::Result<()> { + let mut params = UciParameters::new(); + + //TODO: Implement the rest of the options and refactor + while let Some(subcommand) = option_iter.next() { + if subcommand == "name" { + let name: String = parse_next(option_iter, "name")?; + option_iter.next(); + let value: String = parse_next(option_iter, "value")?; + params.add_option(name, value); + } + } + + if let Some(name) = params.options.get("name") { + if name == "Hash" { + let value = params.options.get("value").expect("Expected key value"); + let size = value + .parse::() + .map_err(|_| anyhow!("Invalid value for Hash"))?; + game.tt = TranspositionTable::new_with_mb_size(size); + } + } + + Ok(()) +} + +fn parse_next(iter: &mut SplitWhitespace, val: &str) -> anyhow::Result { + iter.next() .ok_or_else(|| anyhow!("Expected {val}")) .and_then(|v| v.parse::().map_err(|_| anyhow!("Invalid {val}"))) } @@ -192,18 +232,14 @@ pub fn uci_loop(input: R, mut output: W) -> anyhow::Result Command::IsReady => write_response(&mut output, &Response::ReadyOk)?, Command::UciNewGame => { if let Some(game) = params.game.as_mut() { - game.tt = TranspositionTable::new(MAX_TT_SIZE); + game.tt = TranspositionTable::new(); game.board = Board::startpos(); } else { let game = Game::new(); params.add_game(game); } - write_response(&mut output, &Response::Info("Clear cache".to_string()))?; - } - Command::Position => { - //TODO: doesnt have to create a new game every time, we can just update the game - params.add_game(uci_position(&mut parts)?); } + Command::Position => params.add_game(uci_position(&mut parts)?), Command::Go => { if let Some(game) = params.game.as_mut() { match uci_go(&mut parts, game) { @@ -222,6 +258,14 @@ pub fn uci_loop(input: R, mut output: W) -> anyhow::Result } Command::Stop => break, Command::Quit => break, + Command::SetOption => { + if let Some(game) = params.game.as_mut() { + uci_option(&mut parts, game)?; + } else { + let game = Game::new(); + params.add_game(game); + }; + } }; output.flush().map_err(|e| anyhow!(e))? @@ -299,7 +343,6 @@ mod tests { let expected_response = "id name zeal\n\ id author stefiosif\n\ uciok\n\ - Clear cache\n\ bestmove e3f2\n"; let actual_response = String::from_utf8(output).expect("Invalid UTF-8 in output"); diff --git a/src/search/mod.rs b/src/search/mod.rs index 56d4b73..00343b8 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -11,4 +11,4 @@ pub const TIME: u128 = 8000; pub const INC: u128 = 80; pub const HARD_LIMIT_DIVISION: u128 = 3; pub const SOFT_LIMIT_DIVISION: u128 = 20; -pub const MAX_TT_SIZE: u64 = 1500000; +pub const TT_SIZE_IN_MB: usize = 64; diff --git a/src/search/transposition_table.rs b/src/search/transposition_table.rs index 9280467..f7419a9 100644 --- a/src/search/transposition_table.rs +++ b/src/search/transposition_table.rs @@ -1,5 +1,7 @@ use crate::{board::zobrist::ZobristHash, movegen::r#move::Move}; +use super::TT_SIZE_IN_MB; + #[derive(Clone, Debug, Eq, PartialEq)] pub struct TranspositionTable { positions: Vec>, @@ -7,10 +9,17 @@ pub struct TranspositionTable { } impl TranspositionTable { - pub fn new(size: u64) -> Self { + pub fn new() -> Self { + Self::new_with_mb_size(TT_SIZE_IN_MB) + } + + pub fn new_with_mb_size(size_in_mega_bytes: usize) -> Self { + let size_in_bytes = size_in_mega_bytes * 1024 * 1024; + let num_of_entries = size_in_bytes / std::mem::size_of::(); + Self { - positions: vec![None; size as usize], - size, + positions: vec![None; num_of_entries], + size: num_of_entries as u64, } } @@ -66,7 +75,7 @@ mod tests { board::fen::from_fen, evaluation::{MAX_SCORE, MIN_SCORE}, movegen::attack_generator::init_attacks, - search::{negamax::negamax, time::TimeInfo}, + search::{negamax::negamax, time::TimeInfo, INC, TIME}, }; const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1"; @@ -77,10 +86,10 @@ mod tests { fn test_transposition_table() -> anyhow::Result<()> { init_attacks(); let mut game = from_fen(FEN).unwrap(); - let time_info = TimeInfo::new(std::time::Instant::now(), 30000, 100); + let time_info = TimeInfo::new(std::time::Instant::now(), TIME, INC); negamax( - &mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut 0, true, + &mut game, MIN_SCORE, MAX_SCORE, 3, 0, &time_info, &mut 0, true, ) .expect("Expected a search result");