diff options
author | eug-vs <eugene@eug-vs.xyz> | 2023-02-24 17:35:35 +0300 |
---|---|---|
committer | eug-vs <eugene@eug-vs.xyz> | 2023-02-24 17:35:35 +0300 |
commit | 10a68da1249931a6220ca06affa063c187836bbd (patch) | |
tree | 7c691ed0c37a369d41e286263d2758f0597e1906 | |
parent | a7a26f7810e9bc5fec6020324a83a2a89b69ff79 (diff) | |
download | chessnost-10a68da1249931a6220ca06affa063c187836bbd.tar.gz |
feat: add initial multi-threaded UCI impl
-rw-r--r-- | src/grossmeister/UCI.rs | 71 | ||||
-rw-r--r-- | src/grossmeister/mod.rs | 11 | ||||
-rw-r--r-- | src/grossmeister/search.rs | 24 | ||||
-rw-r--r-- | src/grossmeister/ttable.rs | 1 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 96 | ||||
-rw-r--r-- | src/moves.rs | 8 | ||||
-rw-r--r-- | src/player.rs | 3 |
8 files changed, 102 insertions, 113 deletions
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<AtomicBool> } 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<Move>) { + fn analyze(&mut self, board: Board) -> (f32, Vec<Move>) { 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<Move>, deadline: Instant) -> (f32, Vec<Move>) { + pub fn negamax_search(&mut self, mut alpha: f32, beta: f32, depth_left: u8, parent_killers: &mut Vec<Move>) -> (f32, Vec<Move>) { 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<Move>) { - let start = Instant::now(); - let deadline = start + duration; + pub fn iterative_deepening(&mut self, max_depth: u8, print: bool) -> (f32, Vec<Move>) { 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<Option<TranspositionTableItem>>; @@ -5,4 +5,3 @@ pub mod attacks; pub mod moves; pub mod player; pub mod grossmeister; - diff --git a/src/main.rs b/src/main.rs index 049dc68..72f0299 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,100 +1,6 @@ -use std::time::{Duration, Instant}; -use std::env; - -use chessnost::board::color::Color; -use chessnost::board::io::IO; -use chessnost::board::Board; use chessnost::grossmeister::Grossmeister; -use chessnost::player::Player; - -fn opponent_move(board: &mut Board) { - let mov = match board.read_move() { - Ok(m) => m, - Err(e) => { - println!("Error: {}", e); - return opponent_move(board); - } - }; - print!("{:?}", mov); - board.make_move(mov); - board.print(); -} - -fn computer_move(board: &mut Board, gm: &mut Grossmeister, time_left: &mut Duration) { - let allowed_move_duration = *time_left / 20; - // let max_depth = 7 + (board.ply as i32 / 15) as u8; - println!("~{:?} left, allocating {:?} for a move", time_left, allowed_move_duration); - - let move_start = Instant::now(); - let (score, pv) = gm.analyze(*board, allowed_move_duration); - let elapsed = move_start.elapsed(); - - if *time_left >= elapsed { - *time_left -= elapsed; - } else { - println!("You are probably out of time. I will keep going anyway..."); - } - - println!("Finished in {:?}: score={:?}", elapsed, score); - - let mov = pv[0]; - println!("{:?}", mov); - board.make_move(mov); - board.print(); - - // Ponder for some time - // println!("Pondering, assume opponent move from PV: {:?}", pv[1]); - // let ep_target_before = gm.board.ep_target; - // let castling_rights_before = gm.board.castling_rights; - // let hash_before = gm.board.hash; - // let captured_piece = gm.board.make_move(pv[1]); - // gm.iterative_deepening(max_depth, Duration::from_secs(3), false); - // gm.board.unmake_move(pv[1], captured_piece, ep_target_before, castling_rights_before, hash_before); -} fn main() { - let args: Vec<String> = env::args().collect(); - assert!(args.len() >= 4, "Provide all arguments [COLOR] [TIME_MINUTES] [INCREMENT_SECONDS]"); - - let color = match args[1].as_str() { - "w" => Color::White, - "b" => Color::Black, - _ => panic!("Please provide a color [w|b]") - }; - - let mut time_left = Duration::from_secs(60 * match args[2].parse() { - Ok(x) => x, - Err(..) => 15 - }); - - let increment = Duration::from_secs(match args[3].parse() { - Ok(x) => x, - Err(..) => 10 - }); - let manual_decrement = Duration::from_secs(7); // Time to sync moves with the game - - let mut board = if args.len() == 5 { - Board::from_FEN(args[4].to_string()) - } else { - Board::new() - }; - board.print(); - let mut gm = Grossmeister::default(); - - loop { - time_left += increment; - time_left -= manual_decrement; - - match color { - Color::White => { - computer_move(&mut board, &mut gm, &mut time_left); - opponent_move(&mut board); - } - Color::Black => { - opponent_move(&mut board); - computer_move(&mut board, &mut gm, &mut time_left); - } - } - } + gm.start(); } diff --git a/src/moves.rs b/src/moves.rs index e27d74c..3cf9fd3 100644 --- a/src/moves.rs +++ b/src/moves.rs @@ -1,3 +1,5 @@ +use std::str::Chars; + use crate::{square::Square, bitboard::BitboardFns, board::piece::Piece}; #[derive(Debug, Clone, PartialEq, Eq, Copy)] @@ -27,4 +29,10 @@ impl Move { pub fn is_tactical(&self) -> bool { matches!(self.kind, MoveKind::Capture | MoveKind::EnPassant) } + + pub fn from_notation(mut notation: Chars) -> Self { + let source = Square::from_notation(&mut notation).unwrap(); + let target = Square::from_notation(&mut notation).unwrap(); + Move { source, target, kind: MoveKind::Quiet } + } } diff --git a/src/player.rs b/src/player.rs index 15f4a86..0257024 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,8 +1,7 @@ -use std::time::Duration; use crate::{board::Board, moves::Move}; pub trait Player { /// Analyze a position on a given board, giving /// the score for the side to move and Principal Variation. - fn analyze(&mut self, board: Board, duration: Duration) -> (f32, Vec<Move>); + fn analyze(&mut self, board: Board) -> (f32, Vec<Move>); } |