From 10a68da1249931a6220ca06affa063c187836bbd Mon Sep 17 00:00:00 2001 From: eug-vs Date: Fri, 24 Feb 2023 17:35:35 +0300 Subject: feat: add initial multi-threaded UCI impl --- src/grossmeister/UCI.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++ src/grossmeister/mod.rs | 11 +++++-- src/grossmeister/search.rs | 24 ++++++++-------- src/grossmeister/ttable.rs | 1 + 4 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/grossmeister/UCI.rs (limited to 'src/grossmeister') diff --git a/src/grossmeister/UCI.rs b/src/grossmeister/UCI.rs new file mode 100644 index 0000000..2c09a7b --- /dev/null +++ b/src/grossmeister/UCI.rs @@ -0,0 +1,71 @@ +use std::{io::stdin, thread::Builder}; + +use crate::{board::{Board, io::IO}, moves::Move, player::Player}; + +use super::Grossmeister; + +const NAME: &str = "Chessnost"; +const AUTHOR: &str = "Eugene Sokolov"; + +impl Grossmeister { + pub fn start(&mut self) { + let mut board = Board::new(); + let mut handle = None; + + loop { + let mut cmd = String::new(); + stdin().read_line(&mut cmd).unwrap(); + let tokens: Vec<&str> = cmd.trim().split(' ').collect(); + println!("Command: {:?}, tokens: {:?}", cmd, tokens); + + match tokens[0] { + "uci" => { + println!("id name {}", NAME); + println!("id author {}", AUTHOR); + println!("uciok"); + } + "isready" => { + println!("readyok"); + } + "position" => { + match tokens[1] { + "startpos" => { + board = Board::new(); + } + _ => { + todo!("Parse FEN") + } + } + assert!(tokens[2] == "moves"); + for move_token in tokens.iter().skip(3) { + let mov = Move::from_notation(move_token.chars()); + board.make_move(mov); + println!("{:?}", mov); + board.print(); + } + } + "go" => { + match tokens[1] { + "infinite" => { + let mut gm = self.clone(); + let builder = Builder::new(); + + handle = Some(builder.spawn(move || { + gm.analyze(board); + }).unwrap()); + }, + _ => todo!() + } + }, + "stop" => { + self.should_halt.store(true, std::sync::atomic::Ordering::Relaxed); + if let Some(hand) = handle.take() { + hand.join().unwrap(); + } + self.should_halt.store(false, std::sync::atomic::Ordering::Relaxed); + } + _ => {}, + } + } + } +} diff --git a/src/grossmeister/mod.rs b/src/grossmeister/mod.rs index d07aeee..10788d5 100644 --- a/src/grossmeister/mod.rs +++ b/src/grossmeister/mod.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::sync::{atomic::AtomicBool, Arc}; use crate::{board::Board, player::Player, moves::Move}; use self::ttable::{TranspositionTable, TTABLE_SIZE}; @@ -6,10 +6,12 @@ use self::ttable::{TranspositionTable, TTABLE_SIZE}; mod ttable; mod evaluation; mod search; +mod UCI; /// Grossmeister is a powerful entity that plays the game of Chess. /// This structure represents a player, it stores his knowledge /// and experience about the game. +#[derive(Clone)] pub struct Grossmeister { /// GM's internal board representation /// This is usually a copy of a real board @@ -19,6 +21,8 @@ pub struct Grossmeister { /// has seen and evaluated. /// It's indexex by Zobrist hash of a position mod size transposition_table: TranspositionTable, + + should_halt: Arc } impl Default for Grossmeister { @@ -32,13 +36,14 @@ impl Grossmeister { Self { board, transposition_table: vec![None; TTABLE_SIZE as usize], + should_halt: Arc::new(AtomicBool::new(false)), } } } impl Player for Grossmeister { - fn analyze(&mut self, board: Board, duration: Duration) -> (f32, Vec) { + fn analyze(&mut self, board: Board) -> (f32, Vec) { self.board = board; // Copy the board into GM's head - self.iterative_deepening(8, duration, true) + self.iterative_deepening(8, true) } } diff --git a/src/grossmeister/search.rs b/src/grossmeister/search.rs index ff6375c..20e1dd9 100644 --- a/src/grossmeister/search.rs +++ b/src/grossmeister/search.rs @@ -1,5 +1,7 @@ use std::cmp::Ordering; -use std::{time::{Instant, Duration}, f32::INFINITY}; +use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::{time::Instant, f32::INFINITY}; use crate::{moves::{Move, MoveKind}, board::io::IO}; @@ -17,7 +19,7 @@ pub struct PerftResult { } impl Grossmeister { - pub fn negamax_search(&mut self, mut alpha: f32, beta: f32, depth_left: u8, parent_killers: &mut Vec, deadline: Instant) -> (f32, Vec) { + pub fn negamax_search(&mut self, mut alpha: f32, beta: f32, depth_left: u8, parent_killers: &mut Vec) -> (f32, Vec) { let mut principal_variation = Vec::new(); let mut killer_moves = Vec::new(); let color = self.board.color(); @@ -65,16 +67,16 @@ impl Grossmeister { let (mut score, mut subtree_pv) = if should_pv_search { // Assume PV-node is high in the list (if move ordering is good) - self.negamax_search(-beta, -alpha, depth_left - 1, &mut killer_moves, deadline) + self.negamax_search(-beta, -alpha, depth_left - 1, &mut killer_moves) } else { // After we have PV-node (that raised alpha) all other nodes will be searched // with zero-window first to confirm this assumption // TODO: changing 0.001 -> 0.0001 leads to a weird bug - let result = self.negamax_search(-(alpha + 0.001), -alpha, depth_left - 1, &mut killer_moves, deadline); + let result = self.negamax_search(-(alpha + 0.001), -alpha, depth_left - 1, &mut killer_moves); // In case some of the other nodes raises alpha, then it's true PV node now, // let's research with full window to find its exact value if -result.0 > alpha { - self.negamax_search(-beta, -alpha, depth_left - 1, &mut killer_moves, deadline) + self.negamax_search(-beta, -alpha, depth_left - 1, &mut killer_moves) } else { result } @@ -128,7 +130,7 @@ impl Grossmeister { } // Could not finish in time, return what we have so far - if Instant::now() > deadline { + if self.should_halt.load(std::sync::atomic::Ordering::Relaxed) { break; } } @@ -190,9 +192,7 @@ impl Grossmeister { alpha } - pub fn iterative_deepening(&mut self, max_depth: u8, duration: Duration, print: bool) -> (f32, Vec) { - let start = Instant::now(); - let deadline = start + duration; + pub fn iterative_deepening(&mut self, max_depth: u8, print: bool) -> (f32, Vec) { let mut result = None; let mut depth = 1; let mut alpha = -INFINITY; @@ -206,13 +206,13 @@ impl Grossmeister { if print { println!("\nSearching depth({}) in the window {:?}", depth, (alpha, beta)); } - let search_result = self.negamax_search(alpha, beta, depth, &mut root_killers, deadline); + let search_result = self.negamax_search(alpha, beta, depth, &mut root_killers); if search_result.0.abs() >= VALUE_WIN { return search_result } - if Instant::now() > deadline { + if self.should_halt.load(std::sync::atomic::Ordering::Relaxed) { if print { println!("Aborting..."); } @@ -220,7 +220,7 @@ impl Grossmeister { } if print { - println!("Finished depth({}) {:?} [{:?} left]", depth, search_result, deadline - Instant::now()); + println!("Finished depth({}) {:?}", depth, search_result); } if search_result.0 <= alpha { // Alpha-cutoff diff --git a/src/grossmeister/ttable.rs b/src/grossmeister/ttable.rs index 4bf2398..87099c2 100644 --- a/src/grossmeister/ttable.rs +++ b/src/grossmeister/ttable.rs @@ -24,6 +24,7 @@ pub struct TranspositionTableItem { } pub const TTABLE_SIZE: u64 = 2u64.pow(24); + pub type TranspositionTable = Vec>; -- cgit v1.2.3