diff options
author | eug-vs <eugene@eug-vs.xyz> | 2023-08-21 23:29:58 +0300 |
---|---|---|
committer | eug-vs <eugene@eug-vs.xyz> | 2023-08-21 23:29:58 +0300 |
commit | 2645673ae3e16ee5d7ce24a1efbf367d9fe9a8f0 (patch) | |
tree | b35f4eb5fef532685b3c4d52b0985765a32f31a5 | |
parent | 90190dc10c9d4ed00d4bb976d42c73f2f30098a5 (diff) | |
download | chessnost-2645673ae3e16ee5d7ce24a1efbf367d9fe9a8f0.tar.gz |
feat: add pondering
Also use SeqCst as the strongest ordering to avoid data races
-rw-r--r-- | src/grossmeister/UCI.rs | 69 | ||||
-rw-r--r-- | src/grossmeister/search.rs | 15 |
2 files changed, 58 insertions, 26 deletions
diff --git a/src/grossmeister/UCI.rs b/src/grossmeister/UCI.rs index 1997221..e10088a 100644 --- a/src/grossmeister/UCI.rs +++ b/src/grossmeister/UCI.rs @@ -1,4 +1,4 @@ -use std::{io::stdin, thread::{JoinHandle, spawn, sleep}, str::Split, time::Duration, sync::{Arc, atomic::AtomicBool}}; +use std::{io::stdin, thread::{JoinHandle, spawn, sleep}, str::Split, time::Duration, sync::{Arc, atomic::{AtomicBool, Ordering}}}; use crate::{board::{Board, io::IO, color::Color}, moves::{Move, MoveKind}}; @@ -96,12 +96,27 @@ impl Grossmeister { self.transposition_table = better_self.transposition_table; } - search_handle = Some(self.parse_go(tokens)); + search_handle = Some(self.parse_go(tokens, false)); } "stop" => { - self.should_halt.store(true, std::sync::atomic::Ordering::Relaxed); + self.should_halt.store(true, std::sync::atomic::Ordering::SeqCst); + } + "ponderhit" => { + // Since we were pondering without any scheduled halting previously, + // in case of ponderhit we need to schedule a new halting based on clock + // TODO: isn't the color flipped here? Might lead to time management bugs + let color = self.board.color(); + let duration = (self.board.clock.time[color as usize] + self.board.clock.increment[color as usize]) / 20; + let halt_scheduled = self.schedule_halt(duration); + + // Join to the current pondering search + if let Some(hand) = search_handle.take() { + hand.join().unwrap(); + halt_scheduled.store(false, Ordering::SeqCst); // Cancel scheduled halting + } else { + panic!("Search thread not found!"); + } } - "ponderhit" => todo!(), "quit" => break, _ => {}, } @@ -109,11 +124,14 @@ impl Grossmeister { } } - fn parse_go(&mut self, mut tokens: Split<char>) -> JoinHandle<Self> { + fn parse_go(&mut self, mut tokens: Split<char>, ponder: bool) -> JoinHandle<Self> { if let Some(token) = tokens.next() { match token { "searchmoves" => todo!(), - "ponder" => todo!(), + "ponder" => { + println!("info will start in pondering mode"); + return self.parse_go(tokens, true) + } "wtime" => { if let Some(time) = tokens.next() { let time: u64 = time.parse().unwrap(); @@ -160,40 +178,53 @@ impl Grossmeister { }, _ => {} } - return self.parse_go(tokens) + return self.parse_go(tokens, ponder); } let color = self.board.color(); - let duration = (self.board.clock.time[color as usize] + self.board.clock.increment[color as usize]) / 20; + + let duration = if ponder { + Duration::MAX + } else { + (self.board.clock.time[color as usize] + self.board.clock.increment[color as usize]) / 20 + }; self.create_search_thread(u8::MAX, duration) } - fn create_search_thread(&self, depth: u8, duration: Duration) -> JoinHandle<Self> { - let ordering = std::sync::atomic::Ordering::Relaxed; - let should_halt = self.should_halt.clone(); - // Make sure we don't halt right away - should_halt.store(false, std::sync::atomic::Ordering::Relaxed); - - let should_terminate = Arc::new(AtomicBool::new(true)); + fn schedule_halt(&self, duration: Duration) -> Arc<AtomicBool> { + let halt_scheduled = Arc::new(AtomicBool::new(false)); if duration < Duration::MAX { + halt_scheduled.store(true, Ordering::SeqCst); + let should_halt = self.should_halt.clone(); + // Create a thread that will terminate search when the duration is expired - let should_terminate = should_terminate.clone(); + let halt_scheduled = halt_scheduled.clone(); spawn(move || { println!("info string terminating search in {:?}", duration); sleep(duration); - if should_terminate.load(ordering) { - should_halt.store(true, ordering); + // Make sure schedule is not cancelled externally + if halt_scheduled.load(Ordering::SeqCst) { + should_halt.store(true, Ordering::SeqCst); } }); } + halt_scheduled + } + + fn create_search_thread(&self, depth: u8, duration: Duration) -> JoinHandle<Self> { + // Make sure we don't halt right away + self.should_halt.store(false, Ordering::SeqCst); + + let halt_scheduled = self.schedule_halt(duration); + // Clone current self and move it into thread to analyze a position let mut gm = self.clone(); spawn(move || { gm.iterative_deepening(depth); - should_terminate.store(false, ordering); // No need to terminate search anymore + halt_scheduled.store(false, Ordering::SeqCst); // Cancel the scheduled halting gm // Return better version of Self }) } diff --git a/src/grossmeister/search.rs b/src/grossmeister/search.rs index 532e9ed..7b40fab 100644 --- a/src/grossmeister/search.rs +++ b/src/grossmeister/search.rs @@ -133,7 +133,7 @@ impl Grossmeister { } // Could not finish in time, return what we have so far - if self.should_halt.load(std::sync::atomic::Ordering::Relaxed) { + if self.should_halt.load(std::sync::atomic::Ordering::SeqCst) { break; } } @@ -227,11 +227,8 @@ impl Grossmeister { break; } - if self.should_halt.load(std::sync::atomic::Ordering::Relaxed) { - if self.debug { - println!("info string aborting"); - } - self.should_halt.store(false, std::sync::atomic::Ordering::Relaxed); + if self.should_halt.load(std::sync::atomic::Ordering::SeqCst) { + println!("info string halting search"); break; } @@ -273,7 +270,11 @@ impl Grossmeister { match result { Some(r) => { - println!("bestmove {}", r.1[0]); + print!("bestmove {}", r.1[0]); + if r.1.len() > 1 { + print!(" ponder {}", r.1[1]) + } + println!(); r } None => { |