aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2023-02-24 17:35:35 +0300
committereug-vs <eugene@eug-vs.xyz>2023-02-24 17:35:35 +0300
commit10a68da1249931a6220ca06affa063c187836bbd (patch)
tree7c691ed0c37a369d41e286263d2758f0597e1906
parenta7a26f7810e9bc5fec6020324a83a2a89b69ff79 (diff)
downloadchessnost-10a68da1249931a6220ca06affa063c187836bbd.tar.gz
feat: add initial multi-threaded UCI impl
-rw-r--r--src/grossmeister/UCI.rs71
-rw-r--r--src/grossmeister/mod.rs11
-rw-r--r--src/grossmeister/search.rs24
-rw-r--r--src/grossmeister/ttable.rs1
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs96
-rw-r--r--src/moves.rs8
-rw-r--r--src/player.rs3
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>>;
diff --git a/src/lib.rs b/src/lib.rs
index 93dc573..4f1b161 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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>);
}