chess_uci/chess_uci/src/uci_command.rs

236 lines
7.4 KiB
Rust

use itertools::join;
use std::fmt;
use std::{str::FromStr, time::Duration};
use chess::{Action, Board, ChessMove};
#[derive(Clone, Default, Debug)]
pub struct Id {
name: Option<String>,
author: Option<String>,
}
impl Id {
pub fn update(&mut self, id: &Id) {
if let Some(name) = &id.name {
self.name = Some(name.clone());
}
if let Some(author) = &id.author {
self.author = Some(author.clone());
}
}
pub fn update_from_str(&mut self, name: Option<&str>, author: Option<&str>) {
if let Some(name) = name {
self.name = Some(name.to_string());
}
if let Some(author) = author {
self.author = Some(author.to_string());
}
}
pub fn name(&self) -> Option<String> {
self.name.clone()
}
pub fn author(&self) -> Option<String> {
self.author.clone()
}
}
#[derive(Debug, Clone)]
pub struct Info {}
#[derive(Debug, Clone)]
pub enum Opt {
UCILimitStrength { value: bool },
UCIElo { value: u32 },
SlowMover { value: u32 },
}
impl Opt {
pub fn name(&self) -> &str {
match self {
Opt::UCILimitStrength { .. } => "UCI_LimitStrength",
Opt::UCIElo { .. } => "UCI_Elo",
Opt::SlowMover { .. } => "Slow Mover",
}
}
}
impl fmt::Display for Opt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Opt::UCILimitStrength { value } => write!(f, "{value}"),
Opt::UCIElo { value } => write!(f, "{value}"),
Opt::SlowMover { value } => write!(f, "{value}"),
}
}
}
#[derive(Clone, Debug)]
pub enum EngineCommand {
Id {
id: Id,
},
UciOk,
ReadyOk,
BestMove {
best_move: Option<ChessMove>,
ponder: Option<ChessMove>,
},
CopyProtection, // unimplemented
Registration, // unimplemented
Info {
infos: Vec<Info>,
},
Opt {
options: Vec<Opt>,
},
}
#[derive(Debug)]
pub enum GuiCommand {
Uci,
Debug {
mode: bool,
},
IsReady,
SetOption {
option: Opt,
},
Register, // unimplemented
UciNewGame,
Position {
position: Option<Board>,
moves: Vec<Action>,
},
Go {
wtime: Option<Duration>,
wincr: Option<Duration>,
btime: Option<Duration>,
bincr: Option<Duration>,
},
Stop,
PonderHit,
Quit,
}
pub fn parse(message: &mut str) -> Result<EngineCommand, &'static str> {
let mut message_iter = message.split_whitespace();
match message_iter.next() {
None => Err("No command provided"),
Some("id") => match message_iter.collect::<Vec<&str>>().as_slice() {
[] => Err("Empty id command"),
["name"] => Err("Empty id name command"),
["author"] => Err("Empty id author command"),
["name", tail @ ..] => Ok(EngineCommand::Id {
id: {
let mut id = Id::default();
id.update_from_str(Some(&join(tail, " ")), None);
id
},
}),
["author", tail @ ..] => Ok(EngineCommand::Id {
id: {
let mut id = Id::default();
id.update_from_str(None, Some(&join(tail, " ")));
id
},
}),
_ => Err("Invalid id subcommand"),
},
Some("uciok") => Ok(EngineCommand::UciOk),
Some("readyok") => unimplemented!(),
Some("bestmove") => match message_iter.collect::<Vec<&str>>().as_slice() {
[] => Err("Empty bestmove command"),
["(none)"] => Ok(EngineCommand::BestMove { best_move: None, ponder: None }),
[chessmove] => Ok(EngineCommand::BestMove {
best_move: Some(
ChessMove::from_str(chessmove).expect(&format!("chessmove {:?} is invalid", chessmove))),
ponder: None,
}),
[_, "ponder"] => Err("Empty ponder in bestmove command"),
[chessmove, "ponder", chess_ponder] => Ok(EngineCommand::BestMove {
best_move: Some(ChessMove::from_str(chessmove).expect(&format!("chessmove {:?} is invalid", chessmove))),
ponder: Some(
ChessMove::from_str(chess_ponder).expect(&format!("ponder {:?} is invalid", chessmove))),
}),
_ => Err("Invalid chessmove subcommand"),
},
Some("copyprotection") => unimplemented!(),
Some("registration") => unimplemented!(),
Some("info") => Ok(EngineCommand::Info { infos: Vec::new() }), //todo!("parse info lines")
Some("option") => Ok(EngineCommand::Opt {
options: Vec::new(),
}), // todo!("Parse options lines")
Some(_) => Err("Unknown command provided"),
}
}
impl fmt::Display for GuiCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GuiCommand::Uci => writeln!(f, "uci"),
GuiCommand::UciNewGame => writeln!(f, "ucinewgame"),
GuiCommand::Position { position, moves } => {
writeln!(
f,
"position {} moves {}",
match position {
None => "startpos".to_string(),
Some(board) if *board == Board::default() => "startpos".to_string(),
Some(board) => "fen ".to_string() + &board.to_string(),
},
join(
moves
.iter()
.map(|x| if let Action::MakeMove(chessmove) = x {
" ".to_string() + &chessmove.to_string()
} else {
"".to_string()
}),
""
)
)
}
GuiCommand::SetOption { option } => {
writeln!(f, "setoption name {} value {}", option.name(), option)
}
GuiCommand::Go {
wtime,
wincr,
btime,
bincr,
} => {
let wtime = if let Some(wtime) = wtime {
format!(" wtime {}", wtime.as_millis())
} else {
"".to_string()
};
let btime = if let Some(btime) = btime {
format!(" btime {}", btime.as_millis())
} else {
"".to_string()
};
let wincr = if let Some(wincr) = wincr {
format!(" wincr {}", wincr.as_millis())
} else {
"".to_string()
};
let bincr = if let Some(bincr) = bincr {
format!(" bincr {}", bincr.as_millis())
} else {
"".to_string()
};
writeln!(f, "go{}{}{}{}", wtime, btime, wincr, bincr)
}
GuiCommand::Stop => writeln!(f, "stop"),
GuiCommand::PonderHit => writeln!(f, "ponderhit"),
GuiCommand::Quit => writeln!(f, "quit"),
GuiCommand::Register | GuiCommand::Debug { .. } | GuiCommand::IsReady => {
unimplemented!("{:?}", self)
}
}
}
}