diff --git a/Cargo.toml b/Cargo.toml index 3feb671..d208ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ - "crates/*" + "crates/*", + "crates/uci/uif" ] [workspace.package] @@ -14,6 +15,7 @@ authors = ["Sachin Cherian "] sealion_board = { path = "crates/board" } sealion_fen = { path = "crates/fen" } sealion_engine = { path = "crates/engine" } +sealion_uci = { path = "crates/uci" } # --- sealion binary --- @@ -29,6 +31,7 @@ authors = { workspace = true } sealion_board = { workspace = true } sealion_fen = { workspace = true } sealion_engine = { workspace = true } +sealion_uci = { workspace = true } [profile.release] lto = true diff --git a/crates/uci/Cargo.toml b/crates/uci/Cargo.toml new file mode 100644 index 0000000..3c829a3 --- /dev/null +++ b/crates/uci/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sealion_uci" +edition = { workspace = true } +version = { workspace = true } +publish = { workspace = true } +license = { workspace = true } +authors = { workspace = true } diff --git a/crates/uci/src/engine.rs b/crates/uci/src/engine.rs new file mode 100644 index 0000000..c69699d --- /dev/null +++ b/crates/uci/src/engine.rs @@ -0,0 +1,42 @@ +//! Engine to GUI interface. + +use std::time::Duration; + +#[derive(Debug)] +pub struct Id { + name: String, + author: String, +} + +#[derive(Debug)] +pub enum Score { + CentiPawn(isize), + Mate(isize), + LowerBound, + UpperBound, +} + +#[derive(Debug)] +pub enum Info { + Depth(usize), + SelectiveDepth(usize), + Time(Duration), + Nodes(usize), + PV(Vec), + Score(Score), + // TODO +} + +/// Messages sent by the engine. +#[derive(Debug)] +pub enum Message { + /// Sent after the IsReady command to sync the GUI with the engine. + ReadyOk, + /// Update some data to the UI. + Info(Info), + /// Best move found after a search in the currently setup position. + BestMove { + p_move: String, + ponder: Option, + }, // todo: copyprotection, registration, option +} diff --git a/crates/uci/src/gui.rs b/crates/uci/src/gui.rs new file mode 100644 index 0000000..51e31cb --- /dev/null +++ b/crates/uci/src/gui.rs @@ -0,0 +1,38 @@ +//! GUI to engine interface. + +#[derive(Debug)] +pub enum DebugMode { + On, + Off, +} + +#[derive(Debug)] +pub enum Position { + Fen(String), + StartPos, +} + +/// Messages sent by the GUI. +#[derive(Debug)] +pub enum Message { + /// Toggle the engine's debug mode on or off. + Debug(DebugMode), + /// Synchronize the GUI with the engine. + IsReady, + /// Set an engine parameter. + SetOption { name: String, value: String }, + /// Specify that the next position will be from a new game. + NewGame, + /// Setup the provided position (as FEN) on the engine, and play the specified moves if any. + Position { + position: Position, + moves: Option>, + }, + /// Start calculating the previously setup position. + Go, + /// Stop calculating on the setup position. + Stop, + + /// Quit the engine. + Quit, // todo: register, ponderhit +} diff --git a/crates/uci/src/lib.rs b/crates/uci/src/lib.rs new file mode 100644 index 0000000..590f598 --- /dev/null +++ b/crates/uci/src/lib.rs @@ -0,0 +1,15 @@ +//! [UCI] framework for chess engines. +//! +//! [UCI]: https://www.wbec-ridderkerk.nl/html/UCIProtocol.html + +use std::io::{BufRead, Write}; + +pub mod engine; +pub mod gui; + +struct Core { + stdin: Stdin, + stdout: Stdout, +} + +impl Core {} diff --git a/crates/uci/uif/Cargo.toml b/crates/uci/uif/Cargo.toml new file mode 100644 index 0000000..5ab5443 --- /dev/null +++ b/crates/uci/uif/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sealion_uci_uif" +edition.workspace = true +version.workspace = true +publish.workspace = true +license.workspace = true +authors.workspace = true + +[dependencies] +nom = "7" +thiserror = "1" diff --git a/crates/uci/uif/src/de.rs b/crates/uci/uif/src/de.rs new file mode 100644 index 0000000..5164388 --- /dev/null +++ b/crates/uci/uif/src/de.rs @@ -0,0 +1,56 @@ +use nom::error::Error as NomError; +use nom::Finish; + +#[derive(Debug, thiserror::Error)] +pub enum Error<'i> { + /// An invalid type was found while parsing an input field. + #[error("invalid type provided at {field}, expected {expected}")] + InvalidType { + field: &'static str, + expected: &'static str, + }, + /// Raw nom error. + #[error("nom parsing error: {0}")] + Nom(NomError<&'i str>), +} + +impl<'i> From> for Error<'i> { + #[inline] + fn from(value: NomError<&'i str>) -> Self { + Self::Nom(value) + } +} + +/// Trait describing a value that can be deserialized from the UCI input. +pub trait Deserialize: Sized { + /// Deserialize some segment of the UCI input. + /// + /// The `Ok` is in the form of `(O, I)`. + fn deserialize(input: &str) -> Result<(Self, &str), Error>; +} + +/// Base Type Implementations ////////////////////////////////// + +impl Deserialize for String { + fn deserialize(input: &str) -> Result<(Self, &str), Error> { + Ok((input.to_owned(), "")) + } +} + +macro_rules! impl_int_types { + ($($ty:ident),*) => { + $( + impl Deserialize for $ty { + fn deserialize(input: &str) -> Result<(Self, &str), Error> { + let (inp, out) = (nom::character::complete::$ty::<&str, NomError<&str>>)(input).finish()?; + Ok((out, inp)) + } + } + )* + }; +} + +impl_int_types! { + u8, u16, u32, u64, u128, + i8, i16, i32, i64, i128 +} diff --git a/crates/uci/uif/src/lib.rs b/crates/uci/uif/src/lib.rs new file mode 100644 index 0000000..2f0d8e4 --- /dev/null +++ b/crates/uci/uif/src/lib.rs @@ -0,0 +1,6 @@ +//! A library to help decoding the **U**CI **I**nput **F**ormat. +//! +//! There's no strict format for UCI commands, but this library exists to make it easy to write and +//! derive deserializers for various types that should work with the protocol. + +pub mod de;