#[allow(unused_imports)]
use alloc::string::ToString as _;
use alloc::{string::String, vec::Vec};
use core::{default::Default, fmt, str::FromStr, time::Duration};
use shakmaty::{fen::Fen, uci::UciMove};
use crate::parser::parse;
#[derive(Clone, Debug, PartialEq)]
pub struct ParseUciMessageError;
impl fmt::Display for ParseUciMessageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid UCI message")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseUciMessageError {}
#[must_use]
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum UciMessage {
Uci,
Debug(bool),
IsReady,
Register {
later: bool,
name: Option<String>,
code: Option<String>,
},
Position {
startpos: bool,
fen: Option<Fen>,
moves: Vec<UciMove>,
},
SetOption {
name: String,
value: Option<String>,
},
UciNewGame,
Stop,
PonderHit,
Quit,
Go {
time_control: Option<UciTimeControl>,
search_control: Option<UciSearchControl>,
},
Id {
name: Option<String>,
author: Option<String>,
},
UciOk,
ReadyOk,
BestMove {
best_move: UciMove,
ponder: Option<UciMove>,
},
CopyProtection(ProtectionState),
Registration(ProtectionState),
Option(UciOptionConfig),
Info(UciInfo),
}
impl UciMessage {
pub fn register_later() -> UciMessage {
UciMessage::Register {
later: true,
name: None,
code: None,
}
}
pub fn register_code(name: &str, code: &str) -> UciMessage {
UciMessage::Register {
later: false,
name: Some(String::from(name)),
code: Some(String::from(code)),
}
}
pub fn go() -> UciMessage {
UciMessage::Go {
search_control: None,
time_control: None,
}
}
pub fn go_ponder() -> UciMessage {
UciMessage::Go {
search_control: None,
time_control: Some(UciTimeControl::Ponder),
}
}
pub fn go_infinite() -> UciMessage {
UciMessage::Go {
search_control: None,
time_control: Some(UciTimeControl::Infinite),
}
}
pub fn go_movetime(milliseconds: Duration) -> UciMessage {
UciMessage::Go {
search_control: None,
time_control: Some(UciTimeControl::MoveTime(milliseconds)),
}
}
}
impl fmt::Display for UciMessage {
#[allow(clippy::too_many_lines)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let uci: String = match self {
UciMessage::Debug(on) => {
if *on {
String::from("debug on")
} else {
String::from("debug off")
}
}
UciMessage::Register { later, name, code } => {
let mut s = String::from("register");
if *later {
s += " later";
} else {
if let Some(n) = name {
s += format!(" name {}", *n).as_str();
}
if let Some(c) = code {
s += format!(" code {}", *c).as_str();
}
}
s
}
UciMessage::Position {
startpos,
fen,
moves,
} => {
let mut s = String::from("position ");
if *startpos {
s += "startpos";
} else if let Some(fen) = fen {
s += format!("fen {fen}").as_str();
}
if !moves.is_empty() {
s += " moves";
for m in moves {
s += format!(" {}", *m).as_str();
}
}
s
}
UciMessage::SetOption { name, value } => {
let mut s: String = format!("setoption name {name}");
if let Some(val) = value {
if val.is_empty() {
s += " value <empty>";
} else {
s += format!(" value {}", *val).as_str();
}
} else {
s += " value <empty>";
}
s
}
UciMessage::Go {
time_control,
search_control,
} => {
let mut s = String::from("go");
if let Some(tc) = time_control {
match tc {
UciTimeControl::Infinite => {
s += " infinite";
}
UciTimeControl::Ponder => {
s += " ponder";
}
UciTimeControl::MoveTime(duration) => {
s += format!(" movetime {}", duration.as_millis()).as_str();
}
UciTimeControl::TimeLeft {
white_time,
black_time,
white_increment,
black_increment,
moves_to_go,
} => {
if let Some(wt) = white_time {
s += format!(" wtime {}", wt.as_millis()).as_str();
}
if let Some(bt) = black_time {
s += format!(" btime {}", bt.as_millis()).as_str();
}
if let Some(wi) = white_increment {
s += format!(" winc {}", wi.as_millis()).as_str();
}
if let Some(bi) = black_increment {
s += format!(" binc {}", bi.as_millis()).as_str();
}
if let Some(mtg) = moves_to_go {
s += format!(" movestogo {}", *mtg).as_str();
}
}
}
}
if let Some(sc) = search_control {
if let Some(depth) = sc.depth {
s += format!(" depth {depth}").as_str();
}
if let Some(nodes) = sc.nodes {
s += format!(" nodes {nodes}").as_str();
}
if let Some(mate) = sc.mate {
s += format!(" mate {mate}").as_str();
}
if !sc.search_moves.is_empty() {
s += " searchmoves";
for m in &sc.search_moves {
s += format!(" {m}").as_str();
}
}
}
s
}
UciMessage::Uci => String::from("uci"),
UciMessage::IsReady => String::from("isready"),
UciMessage::UciNewGame => String::from("ucinewgame"),
UciMessage::Stop => String::from("stop"),
UciMessage::PonderHit => String::from("ponderhit"),
UciMessage::Quit => String::from("quit"),
UciMessage::Id { name, author } => {
let mut s = String::from("id ");
if let Some(n) = name {
s += format!("name {n}").as_str();
} else if let Some(a) = author {
s += format!("author {a}").as_str();
}
s
}
UciMessage::UciOk => String::from("uciok"),
UciMessage::ReadyOk => String::from("readyok"),
UciMessage::BestMove { best_move, ponder } => {
let mut s = format!("bestmove {}", *best_move);
if let Some(p) = ponder {
s += format!(" ponder {}", *p).as_str();
}
s
}
UciMessage::CopyProtection(cp_state) | UciMessage::Registration(cp_state) => {
let mut s = match self {
UciMessage::CopyProtection(..) => String::from("copyprotection "),
UciMessage::Registration(..) => String::from("registration "),
_ => unreachable!(),
};
match cp_state {
ProtectionState::Checking => s += "checking",
ProtectionState::Ok => s += "ok",
ProtectionState::Error => s += "error",
}
s
}
UciMessage::Option(config) => config.to_string(),
UciMessage::Info(info) => info.to_string(),
};
write!(f, "{uci}")
}
}
impl FromStr for UciMessage {
type Err = ParseUciMessageError;
fn from_str(line: &str) -> Result<UciMessage, ParseUciMessageError> {
match parse(line) {
Ok((_, msg)) => Ok(msg),
Err(_) => Err(ParseUciMessageError),
}
}
}
#[must_use]
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum UciTimeControl {
Ponder,
Infinite,
TimeLeft {
white_time: Option<Duration>,
black_time: Option<Duration>,
white_increment: Option<Duration>,
black_increment: Option<Duration>,
moves_to_go: Option<u8>,
},
MoveTime(Duration),
}
impl UciTimeControl {
pub fn time_left() -> UciTimeControl {
UciTimeControl::TimeLeft {
white_time: None,
black_time: None,
white_increment: None,
black_increment: None,
moves_to_go: None,
}
}
}
#[must_use]
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub struct UciSearchControl {
pub search_moves: Vec<UciMove>,
pub mate: Option<u8>,
pub depth: Option<u8>,
pub nodes: Option<u64>,
}
impl UciSearchControl {
pub fn depth(depth: u8) -> UciSearchControl {
UciSearchControl {
search_moves: vec![],
mate: None,
depth: Some(depth),
nodes: None,
}
}
pub fn mate(mate: u8) -> UciSearchControl {
UciSearchControl {
search_moves: vec![],
mate: Some(mate),
depth: None,
nodes: None,
}
}
pub fn nodes(nodes: u64) -> UciSearchControl {
UciSearchControl {
search_moves: vec![],
mate: None,
depth: None,
nodes: Some(nodes),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.search_moves.is_empty()
&& self.mate.is_none()
&& self.depth.is_none()
&& self.nodes.is_none()
}
}
impl Default for UciSearchControl {
fn default() -> Self {
UciSearchControl {
search_moves: vec![],
mate: None,
depth: None,
nodes: None,
}
}
}
#[must_use]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub enum ProtectionState {
Checking,
Ok,
Error,
}
#[must_use]
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum UciOptionConfig {
Check {
name: String,
default: Option<bool>,
},
Spin {
name: String,
default: Option<i64>,
min: Option<i64>,
max: Option<i64>,
},
Combo {
name: String,
default: Option<String>,
var: Vec<String>,
},
Button {
name: String,
},
String {
name: String,
default: Option<String>,
},
}
impl UciOptionConfig {
#[must_use]
pub fn get_name(&self) -> &str {
match self {
UciOptionConfig::Check { name, .. }
| UciOptionConfig::Spin { name, .. }
| UciOptionConfig::Combo { name, .. }
| UciOptionConfig::Button { name }
| UciOptionConfig::String { name, .. } => name.as_str(),
}
}
#[must_use]
pub fn get_type_str(&self) -> &'static str {
match self {
UciOptionConfig::Check { .. } => "check",
UciOptionConfig::Spin { .. } => "spin",
UciOptionConfig::Combo { .. } => "combo",
UciOptionConfig::Button { .. } => "button",
UciOptionConfig::String { .. } => "string",
}
}
}
impl fmt::Display for UciOptionConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = format!(
"option name {} type {}",
self.get_name(),
self.get_type_str()
);
match self {
UciOptionConfig::Check { default, .. } => {
if let Some(def) = default {
s += format!(" default {}", *def).as_str();
}
}
UciOptionConfig::Spin {
default, min, max, ..
} => {
if let Some(def) = default {
s += format!(" default {}", *def).as_str();
}
if let Some(m) = min {
s += format!(" min {}", *m).as_str();
}
if let Some(m) = max {
s += format!(" max {}", *m).as_str();
}
}
UciOptionConfig::Combo { default, var, .. } => {
if let Some(def) = default {
s += format!(" default {}", *def).as_str();
}
for v in var {
s += format!(" var {}", *v).as_str();
}
}
UciOptionConfig::String { default, .. } => {
if let Some(def) = default {
s += format!(" default {}", *def).as_str();
}
}
UciOptionConfig::Button { .. } => {
}
}
write!(f, "{s}")
}
}
#[must_use]
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default)]
pub struct UciInfo {
pub depth: Option<u8>,
pub sel_depth: Option<u8>,
pub time: Option<Duration>,
pub nodes: Option<u64>,
pub pv: Vec<UciMove>,
pub multi_pv: Option<u16>,
pub score: Option<UciInfoScore>,
pub curr_move: Option<UciMove>,
pub curr_move_num: Option<u16>,
pub hash_full: Option<u16>,
pub nps: Option<u64>,
pub tb_hits: Option<u64>,
pub sb_hits: Option<u64>,
pub cpu_load: Option<u16>,
pub string: Option<String>,
pub refutation: Vec<UciMove>,
pub curr_line: Vec<UciInfoCurrLine>,
}
impl fmt::Display for UciInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::from("info");
if let Some(depth) = self.depth {
s += format!(" depth {depth}").as_str();
}
if let Some(sel_depth) = self.sel_depth {
s += format!(" seldepth {sel_depth}").as_str();
}
if let Some(time) = self.time {
s += format!(" time {}", time.as_millis()).as_str();
}
if let Some(nodes) = self.nodes {
s += format!(" nodes {nodes}").as_str();
}
if !self.pv.is_empty() {
s += " pv";
for m in &self.pv {
s += format!(" {m}").as_str();
}
}
if !self.refutation.is_empty() {
s += " refutation";
for m in &self.refutation {
s += format!(" {m}").as_str();
}
}
if let Some(multi_pv) = self.multi_pv {
s += format!(" multipv {multi_pv}").as_str();
}
if let Some(score) = &self.score {
s += format!(" score {score}").as_str();
}
if let Some(curr_move) = &self.curr_move {
s += format!(" currmove {curr_move}").as_str();
}
if let Some(curr_move_num) = self.curr_move_num {
s += format!(" currmovenumber {curr_move_num}").as_str();
}
if let Some(hash_full) = self.hash_full {
s += format!(" hashfull {hash_full}").as_str();
}
if let Some(nps) = self.nps {
s += format!(" nps {nps}").as_str();
}
if let Some(tb_hits) = self.tb_hits {
s += format!(" tbhits {tb_hits}").as_str();
}
if let Some(sb_hits) = self.sb_hits {
s += format!(" sbhits {sb_hits}").as_str();
}
if let Some(cpu_load) = self.cpu_load {
s += format!(" cpuload {cpu_load}").as_str();
}
if let Some(string) = &self.string {
s += format!(" string {string}").as_str();
}
for c in &self.curr_line {
s += format!(" currline {c}").as_str();
}
write!(f, "{s}")
}
}
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub struct UciInfoCurrLine {
pub cpu_nr: Option<u16>,
pub moves: Vec<UciMove>,
}
impl fmt::Display for UciInfoCurrLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::new();
if let Some(c) = self.cpu_nr {
s += format!(" cpunr {c}").as_str();
}
if !self.moves.is_empty() {
for m in &self.moves {
s += format!(" {m}").as_str();
}
}
write!(f, "{}", s.trim())
}
}
#[must_use]
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default)]
pub struct UciInfoScore {
pub cp: Option<i32>,
pub mate: Option<i8>,
pub lower_bound: bool,
pub upper_bound: bool,
}
impl UciInfoScore {
pub fn from_centipawns(cp: i32) -> UciInfoScore {
UciInfoScore {
cp: Some(cp),
..Default::default()
}
}
}
impl fmt::Display for UciInfoScore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::new();
if let Some(c) = self.cp {
s += format!(" cp {c}").as_str();
}
if let Some(m) = self.mate {
s += format!(" mate {m}").as_str();
}
if self.lower_bound {
s += " lowerbound";
} else if self.upper_bound {
s += " upperbound";
}
write!(f, "{}", s.trim())
}
}