From 923aeb11d61b6e20ad33598ef7f44d0a6dfa6c15 Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 28 Dec 2023 10:26:06 -0500 Subject: Changes made to support the eventual C API --- engine/src/engine.rs | 48 +++++++++++++++++++++++++++++++++++++++++++--- engine/src/lib.rs | 3 ++- engine/src/main.rs | 54 +++++++++++++++++++++++++++++++++++----------------- engine/src/search.rs | 52 +++++++++++++++++++++++++++++++------------------- model/Cargo.toml | 1 - model/src/color.rs | 5 +++-- model/src/lib.rs | 2 +- model/src/moves.rs | 36 +++++++++++++++++++++++++++++------ 8 files changed, 150 insertions(+), 51 deletions(-) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 9bc4893..6402f21 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -15,6 +15,8 @@ pub const ENGINE_NAME: &str = "Ampere"; pub const ENGINE_AUTHOR: &str = "Mica White"; pub const ENGINE_ABOUT: &str = "Ampere Checkers Bot v1.0\nCopyright Mica White"; +type EvalThread = JoinHandle<(Evaluation, Option)>; + pub struct Engine<'a> { position: Mutex, transposition_table: TranspositionTable, @@ -22,7 +24,7 @@ pub struct Engine<'a> { debug: AtomicBool, frontend: &'a dyn Frontend, - current_thread: Mutex>>, + current_thread: Mutex>, current_task: Mutex>>>, pondering_task: Mutex>>>, } @@ -119,6 +121,7 @@ pub enum SearchLimit { } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(C)] pub struct ActualLimit { pub nodes: Option, pub depth: Option, @@ -155,6 +158,10 @@ impl<'a> Engine<'a> { PossibleMoves::moves(*position).contains(checker_move) } + pub fn current_position(&self) -> CheckersBitBoard { + *self.position.lock() + } + pub fn reset_position(&self) { self.set_position(CheckersBitBoard::starting_position()) } @@ -176,6 +183,41 @@ impl<'a> Engine<'a> { } } + pub fn evaluate( + &self, + cancel: Option<&AtomicBool>, + settings: EvaluationSettings, + ) -> (Evaluation, Option) { + // finish the pondering thread + let mut pondering_task = self.pondering_task.lock(); + if let Some(task) = pondering_task.take() { + task.end_ponder_flag.store(true, Ordering::Release); + } + + let position = *self.position.lock(); + let transposition_table = self.transposition_table.get_ref(); + let limits = settings.get_limits(position.turn()); + let allowed_moves = settings.restrict_moves; + let cancel_flag = AtomicBool::new(false); + let end_ponder_flag = AtomicBool::new(false); + + let nodes_explored = AtomicUsize::new(0); + + let task = EvaluationTask { + position, + transposition_table, + allowed_moves, + limits, + ponder: false, + cancel_flag, + end_ponder_flag, + + nodes_explored, + }; + + search(Arc::new(task), self.frontend, cancel) + } + pub fn start_evaluation(&'static self, settings: EvaluationSettings) { // finish the pondering thread let mut pondering_task = self.pondering_task.lock(); @@ -215,7 +257,7 @@ impl<'a> Engine<'a> { *pondering_task = Some(task_ref.clone()); } - let thread = std::thread::spawn(move || search(task_ref, self.frontend)); + let thread = std::thread::spawn(move || search(task_ref, self.frontend, None)); let mut thread_ptr = self.current_thread.lock(); *thread_ptr = Some(thread); } @@ -224,7 +266,7 @@ impl<'a> Engine<'a> { let current_task = self.current_task.lock().take()?; current_task.cancel_flag.store(true, Ordering::Release); - self.current_thread.lock().take(); + let _ = self.current_thread.lock().take()?.join(); Some(()) } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index c75d9c2..d87c225 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -7,9 +7,10 @@ pub use engine::{ ENGINE_AUTHOR, ENGINE_NAME, }; pub use eval::Evaluation; -pub use model::{CheckersBitBoard, Move, Piece, PieceColor, PossibleMoves}; +pub use model::{CheckersBitBoard, Move, MoveDirection, Piece, PieceColor, PossibleMoves}; pub use transposition_table::{TranspositionTable, TranspositionTableRef}; +pub mod c_abi; mod engine; mod eval; mod lazysort; diff --git a/engine/src/main.rs b/engine/src/main.rs index 32e0f62..d4bcc48 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -1,7 +1,8 @@ -use std::{num::NonZeroU8, thread::sleep, time::Duration}; +use std::num::NonZeroU8; use engine::{ActualLimit, Engine, EvaluationSettings, Frontend}; use mimalloc::MiMalloc; +use model::CheckersBitBoard; #[global_allocator] static ALLOCATOR: MiMalloc = MiMalloc; @@ -11,28 +12,47 @@ const DEPTH: u8 = 19; struct BasicFrontend; impl Frontend for BasicFrontend { - fn debug(&self, _: &str) {} + fn debug(&self, msg: &str) { + println!("{msg}"); + } fn report_best_move(&self, best_move: model::Move) { println!("{best_move}"); - std::process::exit(0); } } fn main() { let engine = Box::leak(Box::new(Engine::new(1_000_000, &BasicFrontend))); - engine.start_evaluation(EvaluationSettings { - restrict_moves: None, - ponder: false, - clock: engine::Clock::Unlimited, - search_until: engine::SearchLimit::Limited(ActualLimit { - nodes: None, - depth: Some(NonZeroU8::new(DEPTH).unwrap()), - time: None, - }), - }); - - loop { - sleep(Duration::from_millis(200)) - } + let (_, best) = engine.evaluate( + None, + EvaluationSettings { + restrict_moves: None, + ponder: false, + clock: engine::Clock::Unlimited, + search_until: engine::SearchLimit::Limited(ActualLimit { + nodes: None, + depth: Some(NonZeroU8::new(DEPTH).unwrap()), + time: None, + }), + }, + ); + engine.set_position(CheckersBitBoard::new( + 4294967295, + 2206409603, + 3005432691, + model::PieceColor::Light, + )); + engine.evaluate( + None, + EvaluationSettings { + restrict_moves: None, + ponder: false, + clock: engine::Clock::Unlimited, + search_until: engine::SearchLimit::Limited(ActualLimit { + nodes: None, + depth: Some(NonZeroU8::new(DEPTH).unwrap()), + time: None, + }), + }, + ); } diff --git a/engine/src/search.rs b/engine/src/search.rs index b8fd982..9c8ea26 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -99,9 +99,13 @@ pub fn negamax( } } -pub fn search(task: Arc, frontend: &dyn Frontend) -> Evaluation { +pub fn search( + task: Arc, + frontend: &dyn Frontend, + cancel: Option<&AtomicBool>, +) -> (Evaluation, Option) { let board = task.position; - let cancel_flag = &task.cancel_flag; + let cancel_flag = cancel.unwrap_or(&task.cancel_flag); let allowed_moves = task.allowed_moves.clone(); let limits = task.limits; @@ -115,25 +119,28 @@ pub fn search(task: Arc, frontend: &dyn Frontend) -> Evaluation let mut eval = Evaluation::DRAW; let mut best_move = None; loop { - if let Some(max_depth) = max_depth { - if depth > max_depth.get() { - break; + // don't leave search is no good moves have been found + if best_move.is_some() { + if let Some(max_depth) = max_depth { + if depth > max_depth.get() { + break; + } } - } - if let Some(max_time) = max_time { - if Instant::now() > max_time { - break; + if let Some(max_time) = max_time { + if Instant::now() > max_time { + break; + } } - } - if let Some(max_nodes) = max_nodes { - if task - .nodes_explored - .load(std::sync::atomic::Ordering::Acquire) - > max_nodes.get() - { - break; + if let Some(max_nodes) = max_nodes { + if task + .nodes_explored + .load(std::sync::atomic::Ordering::Acquire) + > max_nodes.get() + { + break; + } } } @@ -148,7 +155,7 @@ pub fn search(task: Arc, frontend: &dyn Frontend) -> Evaluation ); // prevent incomplete search from overwriting evaluation - if cancel_flag.load(std::sync::atomic::Ordering::Acquire) { + if best_move.is_some() && cancel_flag.load(std::sync::atomic::Ordering::Acquire) { break; } @@ -167,7 +174,7 @@ pub fn search(task: Arc, frontend: &dyn Frontend) -> Evaluation ); // prevent incomplete search from overwriting evaluation - if cancel_flag.load(std::sync::atomic::Ordering::Acquire) { + if best_move.is_some() && cancel_flag.load(std::sync::atomic::Ordering::Acquire) { break; } @@ -193,6 +200,11 @@ pub fn search(task: Arc, frontend: &dyn Frontend) -> Evaluation beta = eval.add_f32(0.125); } + if eval.is_force_sequence() { + // we don't need to search any deeper + return (eval, best_move); + } + depth += 1; } @@ -231,5 +243,5 @@ pub fn search(task: Arc, frontend: &dyn Frontend) -> Evaluation } } - eval + (eval, best_move) } diff --git a/model/Cargo.toml b/model/Cargo.toml index 9f2402a..e732e99 100644 --- a/model/Cargo.toml +++ b/model/Cargo.toml @@ -9,7 +9,6 @@ publish = false [dependencies] serde = { version = "1", optional = true, features = ["derive"] } -rayon = "1" [dev-dependencies] proptest = "1" diff --git a/model/src/color.rs b/model/src/color.rs index 26a1069..8a4d2a5 100644 --- a/model/src/color.rs +++ b/model/src/color.rs @@ -5,9 +5,10 @@ use std::fmt::Display; /// The color of a piece #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(C)] pub enum PieceColor { - Light, - Dark, + Light = 0, + Dark = 1, } impl Display for PieceColor { diff --git a/model/src/lib.rs b/model/src/lib.rs index 61ab2cc..b3d8007 100644 --- a/model/src/lib.rs +++ b/model/src/lib.rs @@ -8,6 +8,6 @@ mod possible_moves; pub use board::CheckersBitBoard; pub use color::PieceColor; pub use coordinates::SquareCoordinate; -pub use moves::Move; +pub use moves::{Move, MoveDirection}; pub use piece::Piece; pub use possible_moves::PossibleMoves; diff --git a/model/src/moves.rs b/model/src/moves.rs index c266f77..c840e8f 100644 --- a/model/src/moves.rs +++ b/model/src/moves.rs @@ -2,6 +2,7 @@ use crate::{CheckersBitBoard, SquareCoordinate}; use std::fmt::{Display, Formatter}; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[repr(C)] pub enum MoveDirection { ForwardLeft = 0, ForwardRight = 1, @@ -72,6 +73,21 @@ impl Move { dest as usize } + /// Calculates the value of the position that was jumped over + /// + /// # Safety + /// + /// The result of this function is undefined if the move isn't a jump + pub const unsafe fn jump_position(self) -> usize { + let pos = match self.direction() { + MoveDirection::ForwardLeft => (self.start() + 7) % 32, + MoveDirection::ForwardRight => (self.start() + 1) % 32, + MoveDirection::BackwardLeft => self.start().wrapping_sub(1) % 32, + MoveDirection::BackwardRight => self.start().wrapping_sub(7) % 32, + }; + pos as usize + } + /// Apply the move to a board. This does not mutate the original board, /// but instead returns a new one. /// @@ -128,12 +144,20 @@ impl Move { impl Display for Move { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}-{}", - SquareCoordinate::from_ampere_value(self.start() as usize), - SquareCoordinate::from_ampere_value(self.end_position()) - ) + let Some(start) = + SquareCoordinate::from_ampere_value(self.start() as usize).to_normal_value() + else { + return Err(std::fmt::Error); + }; + + let separator = if self.is_jump() { "x" } else { "-" }; + + let Some(end) = SquareCoordinate::from_ampere_value(self.end_position()).to_normal_value() + else { + return Err(std::fmt::Error); + }; + + write!(f, "{start}{separator}{end}") } } -- cgit v1.2.3