Compare commits
	
		
			1 commit
		
	
	
		
			main
			...
			chessfrien
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 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