148 lines
3.5 KiB
Rust
148 lines
3.5 KiB
Rust
// 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),
|
|
}
|