extern crate alloc;
use core::fmt::{Display, Formatter};
use shakmaty::uci::UciMove;
use crate::dev_macros::{from_str_parts, impl_message, message_from_impl};
use crate::OptionReplaceIf;
use super::{pointers, traits};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BestMove {
Other,
Normal(NormalBestMove),
}
impl BestMove {
pub const fn normal(&self) -> Option<&NormalBestMove> {
match self {
Self::Other => None,
Self::Normal(n) => Some(n),
}
}
pub const fn take_normal(self) -> Option<NormalBestMove> {
match self {
Self::Other => None,
Self::Normal(n) => Some(n),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NormalBestMove {
pub r#move: UciMove,
pub ponder: Option<UciMove>,
}
impl From<NormalBestMove> for BestMove {
fn from(value: NormalBestMove) -> Self {
Self::Normal(value)
}
}
impl From<NormalBestMove> for crate::Message<'_> {
fn from(value: NormalBestMove) -> Self {
Self::Engine(super::Message::BestMove(value.into()))
}
}
impl From<NormalBestMove> for super::Message<'_> {
fn from(value: NormalBestMove) -> Self {
Self::BestMove(value.into())
}
}
impl_message!(BestMove);
message_from_impl!(engine BestMove);
from_str_parts!(impl BestMove for parts -> Self {
let mut r#move = None;
let mut ponder_encountered = false;
let mut ponder = None;
for part in parts {
if ponder_encountered {
ponder.replace_if(part.parse().ok());
} else if part.trim() == "ponder" {
ponder_encountered = true;
} else {
r#move.replace_if(part.parse().ok());
}
}
r#move.map_or(Self::Other, |r#move| Self::Normal(NormalBestMove { r#move, ponder }))
});
impl Display for BestMove {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str("bestmove")?;
match &self {
Self::Other => Ok(()),
Self::Normal(best_move) => {
write!(f, " {}", best_move.r#move)?;
best_move.ponder.as_ref().map_or(Ok(()), |ponder| write!(f, " ponder {ponder}"))
}
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use alloc::string::ToString;
use shakmaty::uci::UciMove;
use crate::engine::{BestMove, NormalBestMove};
use crate::dev_macros::{assert_from_str_message, assert_message_to_from_str, assert_message_to_str};
#[test]
fn to_from_str() {
let m: BestMove = NormalBestMove {
r#move: UciMove::from_ascii(b"e2e4").unwrap(),
ponder: Some(UciMove::from_ascii(b"c7c5").unwrap()),
}.into();
assert_message_to_from_str!(engine m, "bestmove e2e4 ponder c7c5");
let m: BestMove = NormalBestMove {
r#move: UciMove::from_ascii(b"d2d4").unwrap(),
ponder: Some(UciMove::from_ascii(b"c7c5").unwrap()),
}.into();
assert_from_str_message!(engine "bestmove oops d2d4 ponder c7c5 ignorethis", Ok(m.clone()));
assert_message_to_str!(engine m, "bestmove d2d4 ponder c7c5");
}
#[test]
fn to_from_str_bad_value() {
let m: BestMove = NormalBestMove {
r#move: UciMove::from_ascii(b"e2e4").unwrap(),
ponder: Some(UciMove::from_ascii(b"c7c5").unwrap()),
}.into();
assert_from_str_message!(engine "bestmove junk e2e4 ponder c7c5\n", Ok(m.clone()));
assert_message_to_str!(engine m, "bestmove e2e4 ponder c7c5");
}
#[test]
fn to_from_str_other() {
let m: BestMove = BestMove::Other;
assert_from_str_message!(engine "bestmove (none)\n", Ok(m.clone()));
assert_message_to_str!(engine m, "bestmove");
}
}