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"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chessfriend_core",
|
||||
"chessfriend_moves",
|
||||
"chessfriend_position",
|
||||
"clap",
|
||||
"shlex",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -3,3 +3,10 @@ name = "chessfriend"
|
|||
version = "0.1.0"
|
||||
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>
|
||||
|
||||
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]
|
||||
mod macros;
|
||||
|
||||
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy};
|
||||
pub use chessfriend_moves::{GeneratedMove, ValidateMove};
|
||||
pub use chessfriend_board::{fen, PlacePieceError, PlacePieceStrategy, ZobristState};
|
||||
pub use chessfriend_moves::{GeneratedMove, Move, ValidateMove};
|
||||
pub use position::Position;
|
||||
|
||||
pub mod perft;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue