WIP
This commit is contained in:
parent
c7b9544004
commit
90ee3e416f
7 changed files with 344 additions and 2 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -72,6 +72,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
name = "chessfriend"
|
name = "chessfriend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chessfriend_core",
|
||||||
|
"chessfriend_moves",
|
||||||
|
"chessfriend_position",
|
||||||
|
"clap",
|
||||||
|
"shlex",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -3,3 +3,10 @@ name = "chessfriend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chessfriend_core = { path = "../core" }
|
||||||
|
chessfriend_moves = { path = "../moves" }
|
||||||
|
chessfriend_position = { path = "../position" }
|
||||||
|
clap = { version = "4.4.12", features = ["derive"] }
|
||||||
|
shlex = "1.2.0"
|
||||||
|
thiserror = "2"
|
||||||
|
|
87
chessfriend/src/chessfriend.rs
Normal file
87
chessfriend/src/chessfriend.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
core::{Piece, Square},
|
||||||
|
position::{MakeMoveError, Move, ValidateMove},
|
||||||
|
threadpool::ThreadPool,
|
||||||
|
};
|
||||||
|
use chessfriend_core::random::RandomNumberGenerator;
|
||||||
|
use chessfriend_position::{
|
||||||
|
PlacePieceError, PlacePieceStrategy, Position, ZobristState, fen::FromFenStr,
|
||||||
|
};
|
||||||
|
use std::{num::NonZero, sync::Arc};
|
||||||
|
|
||||||
|
pub struct ChessFriend {
|
||||||
|
/// A pool of worker threads over which tasks may be distributed.
|
||||||
|
thread_pool: ThreadPool,
|
||||||
|
|
||||||
|
zobrist_state: Arc<ZobristState>,
|
||||||
|
|
||||||
|
/// A global Position for the engine.
|
||||||
|
position: Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChessFriend {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(options: Options) -> Self {
|
||||||
|
let mut rng = RandomNumberGenerator::default();
|
||||||
|
|
||||||
|
let zobrist_state = Arc::new(ZobristState::new(&mut rng));
|
||||||
|
|
||||||
|
let position = match options.initial_position {
|
||||||
|
InitialPosition::Empty => Position::empty(Some(zobrist_state.clone())),
|
||||||
|
InitialPosition::Starting => Position::starting(Some(zobrist_state.clone())),
|
||||||
|
InitialPosition::Fen(fen) => {
|
||||||
|
let mut position = Position::from_fen_str(fen).unwrap_or_default();
|
||||||
|
position.set_zobrist_state(zobrist_state.clone());
|
||||||
|
position
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let thread_pool = ThreadPool::new(options.threading.0);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
thread_pool,
|
||||||
|
zobrist_state,
|
||||||
|
position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChessFriend {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Eq, PartialEq)]
|
||||||
|
pub struct Options<'a> {
|
||||||
|
initial_position: InitialPosition<'a>,
|
||||||
|
threading: Threading,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default, Eq, PartialEq)]
|
||||||
|
pub enum InitialPosition<'a> {
|
||||||
|
Empty,
|
||||||
|
#[default]
|
||||||
|
Starting,
|
||||||
|
Fen(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub struct Threading(NonZero<usize>);
|
||||||
|
|
||||||
|
impl Threading {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(n: NonZero<usize>) -> Self {
|
||||||
|
Self(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn new_with_available_parallelism() -> Self {
|
||||||
|
const ONE: NonZero<usize> = NonZero::new(1).unwrap();
|
||||||
|
Self(std::thread::available_parallelism().unwrap_or(ONE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Threading {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_with_available_parallelism()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,19 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
mod chessfriend;
|
||||||
|
mod threadpool;
|
||||||
|
mod uci;
|
||||||
|
|
||||||
|
pub use crate::chessfriend::ChessFriend;
|
||||||
|
|
||||||
|
pub mod options {
|
||||||
|
pub use crate::chessfriend::{InitialPosition, Options, Threading};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod core {
|
||||||
|
pub use chessfriend_core::{Color, Piece, Shape, Square};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod position {
|
||||||
|
pub use chessfriend_moves::{MakeMoveError, Move, ValidateMove};
|
||||||
|
}
|
||||||
|
|
77
chessfriend/src/threadpool.rs
Normal file
77
chessfriend/src/threadpool.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
num::NonZero,
|
||||||
|
panic,
|
||||||
|
sync::{Arc, Mutex, mpsc},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) trait Job: FnOnce() + Send + 'static {}
|
||||||
|
|
||||||
|
pub(crate) struct ThreadPool {
|
||||||
|
workers: Vec<Worker>,
|
||||||
|
sender: Option<mpsc::Sender<Box<dyn Job>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadPool {
|
||||||
|
pub fn new(threads_count: NonZero<usize>) -> Self {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
|
||||||
|
let receiver = Arc::new(Mutex::new(receiver));
|
||||||
|
let workers: Vec<_> = (0..threads_count.into())
|
||||||
|
.map(|i| Worker::new(i, receiver.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
workers,
|
||||||
|
sender: Some(sender),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ThreadPool {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
drop(self.sender.take());
|
||||||
|
self.workers.drain(..).for_each(Worker::join);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Worker {
|
||||||
|
id: usize,
|
||||||
|
handle: thread::JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Worker {
|
||||||
|
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Box<dyn Job>>>>) -> Self {
|
||||||
|
// TODO: A note from the Rust Programming Language
|
||||||
|
//
|
||||||
|
// Note: If the operating system can’t create a thread because there
|
||||||
|
// aren’t enough system resources, thread::spawn will panic. That will
|
||||||
|
// cause our whole server to panic, even though the creation of some
|
||||||
|
// threads might succeed. For simplicity’s sake, this behavior is fine,
|
||||||
|
// but in a production thread pool implementation, you’d likely want to
|
||||||
|
// use std::thread::Builder and its spawn method that returns Result
|
||||||
|
// instead.
|
||||||
|
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
let job = {
|
||||||
|
let receiver = receiver.lock().unwrap();
|
||||||
|
receiver.recv().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
job();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { id, handle }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join(self) {
|
||||||
|
match self.handle.join() {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(error) => panic::resume_unwind(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
chessfriend/src/uci.rs
Normal file
148
chessfriend/src/uci.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
use clap::{Error as ClapError, Parser, Subcommand, ValueEnum};
|
||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
io::{BufRead, Write},
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(multicall = true)]
|
||||||
|
pub struct Uci {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
enum Command {
|
||||||
|
/// Establish UCI (Universal Chess Interface) as the channel's exchange protocol.
|
||||||
|
Uci,
|
||||||
|
|
||||||
|
/// Toggle debug state on or off.
|
||||||
|
Debug { state: DebugState },
|
||||||
|
|
||||||
|
/// Synchronize the engine with the client. Can also be used as a 'ping'.
|
||||||
|
IsReady,
|
||||||
|
|
||||||
|
/// Stop calculating as soon as possible.
|
||||||
|
Stop,
|
||||||
|
|
||||||
|
/// Stop all processing and quit the program.
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
|
||||||
|
enum DebugState {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Response<'a> {
|
||||||
|
/// Declares one aspect of the engine's identity.
|
||||||
|
Id(IdValue<'a>),
|
||||||
|
|
||||||
|
/// Declares that communicating in UCI is acceptable.
|
||||||
|
UciOk,
|
||||||
|
|
||||||
|
/// Declares that the engine is ready to receive commands from the client.
|
||||||
|
ReadyOk,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Response<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Response::Id(value) => write!(f, "id {value}"),
|
||||||
|
Response::UciOk => write!(f, "uciok"),
|
||||||
|
Response::ReadyOk => write!(f, "readyok"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum IdValue<'a> {
|
||||||
|
Name(&'a str),
|
||||||
|
Author(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IdValue<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
IdValue::Name(name) => write!(f, "name {name}"),
|
||||||
|
IdValue::Author(author) => write!(f, "author {author}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("unable to parse command")]
|
||||||
|
LexError,
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
ClapError(#[from] ClapError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uci {
|
||||||
|
/// Respond to a command.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if parsing the command string fails, otherwise returns an array of
|
||||||
|
/// responses.
|
||||||
|
///
|
||||||
|
pub fn respond(line: &str) -> Result<Vec<Response>, Error> {
|
||||||
|
let arguments = shlex::split(line).ok_or(Error::LexError)?;
|
||||||
|
|
||||||
|
let interface = Self::try_parse_from(arguments)?;
|
||||||
|
|
||||||
|
match interface.command {
|
||||||
|
Command::Uci => {
|
||||||
|
const IDENTITIES: [Response; 2] = [
|
||||||
|
Response::Id(IdValue::Name("ChessFriend")),
|
||||||
|
Response::Id(IdValue::Author("Eryn Wells")),
|
||||||
|
];
|
||||||
|
|
||||||
|
let options: Vec<Response> = vec![];
|
||||||
|
|
||||||
|
Ok(IDENTITIES.into_iter().chain(options).collect())
|
||||||
|
}
|
||||||
|
Command::Debug { state: _ } => Ok(vec![]),
|
||||||
|
Command::IsReady => Ok(vec![Response::ReadyOk]),
|
||||||
|
Command::Stop => Ok(vec![]),
|
||||||
|
Command::Quit => Ok(vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UciInterface {}
|
||||||
|
|
||||||
|
impl UciInterface {
|
||||||
|
pub fn read_until_quit(
|
||||||
|
&self,
|
||||||
|
input: impl BufRead,
|
||||||
|
output: &mut impl Write,
|
||||||
|
) -> Result<(), UciInterfaceError> {
|
||||||
|
for line in input.lines() {
|
||||||
|
let line = line?;
|
||||||
|
|
||||||
|
let responses = Uci::respond(line.as_str())?;
|
||||||
|
|
||||||
|
// TODO: Dispatch command to background processing thread.
|
||||||
|
|
||||||
|
for r in responses {
|
||||||
|
write!(output, "{r}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UciInterfaceError {
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("uci error: {0}")]
|
||||||
|
UciError(#[from] Error),
|
||||||
|
}
|
|
@ -5,8 +5,8 @@ mod position;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy, ZobristState};
|
||||||
pub use chessfriend_moves::{GeneratedMove, ValidateMove};
|
pub use chessfriend_moves::{GeneratedMove, Move, ValidateMove};
|
||||||
pub use position::Position;
|
pub use position::Position;
|
||||||
|
|
||||||
pub mod perft;
|
pub mod perft;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue