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}}; use super::Grossmeister; const NAME: &str = "Chessnost"; const AUTHOR: &str = "Eugene Sokolov"; impl Grossmeister { pub fn start(&mut self) { let mut search_handle: Option> = None; loop { let mut cmd = String::new(); stdin().read_line(&mut cmd).unwrap(); let mut tokens = cmd.trim().split(' '); if let Some(token) = tokens.next() { match token { "uci" => { println!("id name {}", NAME); println!("id author {}", AUTHOR); println!("uciok"); } "debug" => { if let Some(token) = tokens.next() { match token { "on" => self.debug = true, "off" => self.debug = false, _ => { panic!("Wrong option to debug: {}. CMD: {}", token, cmd); }, } }; } "isready" => { println!("readyok"); } "setoption" => { todo!() } "ucinewgame" => { *self = Self::default() } "position" => { if let Some(token) = tokens.next() { match token { "startpos" => { self.board = Board::new(); if let Some(token) = tokens.next() { if token == "moves" { for token in tokens.by_ref() { let input_move = Move::from_notation(token.chars()); let moves = self.board.generate_pseudolegal_moves(); if let Some(mov) = moves .iter() .find(|m| { let promo_matches = match input_move.kind { MoveKind::Promotion(piece) => match m.kind { MoveKind::Promotion(another_piece) => piece.without_color() == another_piece.without_color(), _ => false }, _ => true, }; promo_matches && m.source == input_move.source && m.target == input_move.target }) { self.board.make_move(*mov); } else { panic!("Illegal move: {}", input_move); } } } else { panic!("Unexpected token: {}. CMD: {}", token, cmd); } } } "fen" => { // TODO: handle "moves" command after "fen"? let index = "position fen".len() + 1; if let Some(fen) = cmd.get(index..) { self.board = Board::from_FEN(fen.to_string()); }; } _ => {} } } else { todo!("Decide what to do when position is now provided") } } "go" => { // Before we go, let's join to the latest search if let Some(hand) = search_handle.take() { let better_self = hand.join().unwrap(); self.transposition_table = better_self.transposition_table; } search_handle = Some(self.parse_go(tokens, false)); } "stop" => { 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!"); } } "quit" => break, _ => {}, } } } } fn parse_go(&mut self, mut tokens: Split, ponder: bool) -> JoinHandle { if let Some(token) = tokens.next() { match token { "searchmoves" => 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(); self.board.clock.time[Color::White as usize] = Duration::from_millis(time); } } "btime" => { if let Some(time) = tokens.next() { let time: u64 = time.parse().unwrap(); self.board.clock.time[Color::Black as usize] = Duration::from_millis(time); } } "winc" => { if let Some(time) = tokens.next() { let time: u64 = time.parse().unwrap(); self.board.clock.increment[Color::White as usize] = Duration::from_millis(time); } } "binc" => { if let Some(time) = tokens.next() { let time: u64 = time.parse().unwrap(); self.board.clock.increment[Color::Black as usize] = Duration::from_millis(time); } } "movestogo" => {} "depth" => { if let Some(depth) = tokens.next() { let depth: u8 = depth.parse().unwrap(); return self.create_search_thread(depth, Duration::MAX); } }, "nodes" => todo!(), "mate" => todo!(), "movetime" => { if let Some(time) = tokens.next() { let time: u64 = time.parse().unwrap(); let duration = Duration::from_millis(time); return self.create_search_thread(u8::MAX, duration) } }, "infinite" => { return self.create_search_thread(u8::MAX, Duration::MAX); }, _ => {} } return self.parse_go(tokens, ponder); } let color = self.board.color(); 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 schedule_halt(&self, duration: Duration) -> Arc { 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 halt_scheduled = halt_scheduled.clone(); spawn(move || { println!("info string terminating search in {:?}", duration); sleep(duration); // 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 { // 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); halt_scheduled.store(false, Ordering::SeqCst); // Cancel the scheduled halting gm // Return better version of Self }) } }