use core::num::NonZeroU16;
use crate::{c_compat::OptionSquare, Piece, Square, ToUsi};
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum Move {
Normal {
from: Square,
to: Square,
promote: bool,
},
Drop {
piece: Piece,
to: Square,
},
}
impl Move {
pub fn from(self) -> Option<Square> {
match self {
Move::Normal { from, .. } => Some(from),
Move::Drop { .. } => None,
}
}
pub fn to(self) -> Square {
match self {
Move::Normal { to, .. } => to,
Move::Drop { to, .. } => to,
}
}
pub fn is_promoting(self) -> bool {
matches!(self, Move::Normal { promote: true, .. })
}
#[inline]
pub fn is_drop(self) -> bool {
matches!(self, Move::Drop { .. })
}
}
#[cfg(feature = "ord")]
#[cfg_attr(docsrs, doc(cfg(feature = "ord")))]
impl core::cmp::PartialOrd for Move {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[cfg(feature = "ord")]
#[cfg_attr(docsrs, doc(cfg(feature = "ord")))]
impl core::cmp::Ord for Move {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (*self, *other) {
(Move::Normal { .. }, Move::Drop { .. }) => core::cmp::Ordering::Less,
(Move::Drop { .. }, Move::Normal { .. }) => core::cmp::Ordering::Greater,
(
Move::Normal {
from: from1,
to: to1,
promote: promote1,
},
Move::Normal {
from: from2,
to: to2,
promote: promote2,
},
) => (from1, to1, promote1).cmp(&(from2, to2, promote2)),
(
Move::Drop {
piece: piece1,
to: to1,
},
Move::Drop {
piece: piece2,
to: to2,
},
) => (piece1, to1).cmp(&(piece2, to2)),
}
}
}
#[cfg(feature = "hash")]
#[cfg_attr(docsrs, doc(cfg(feature = "hash")))]
impl core::hash::Hash for Move {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match *self {
Move::Normal { from, to, promote } => {
from.hash(state);
to.hash(state);
promote.hash(state);
}
Move::Drop { piece, to } => {
piece.hash(state);
to.hash(state);
}
}
}
}
impl ToUsi for Move {
fn to_usi<W: core::fmt::Write>(&self, sink: &mut W) -> core::fmt::Result {
match *self {
Move::Normal { from, to, promote } => {
from.to_usi(sink)?;
to.to_usi(sink)?;
if promote {
unsafe { crate::common::write_ascii_byte(sink, b'+') }?;
}
}
Move::Drop { piece, to } => {
let piece_kind = piece.piece_kind();
piece_kind.to_usi(sink)?;
unsafe { crate::common::write_ascii_byte(sink, b'*') }?;
to.to_usi(sink)?;
}
}
Ok(())
}
}
#[repr(transparent)]
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub struct CompactMove(NonZeroU16);
impl From<Move> for CompactMove {
fn from(mv: Move) -> Self {
let value = match mv {
Move::Normal { from, to, promote } => {
(promote as u16) << 15 | (from.index() as u16) << 8 | to.index() as u16
}
Move::Drop { piece, to } => (piece.as_u8() as u16) << 8 | 128 | to.index() as u16,
};
Self(unsafe { NonZeroU16::new_unchecked(value) })
}
}
impl From<CompactMove> for Move {
fn from(mv: CompactMove) -> Self {
let to = mv.to();
let inner = mv.0.get();
if mv.is_drop() {
let piece = (inner >> 8) as u8;
let piece = unsafe { Piece::from_u8_unchecked(piece) };
Move::Drop { piece, to }
} else {
let from = ((inner >> 8) & 127) as u8;
let from = unsafe { Square::from_u8_unchecked(from) };
let promote = (inner & 32768) != 0;
Move::Normal { from, to, promote }
}
}
}
impl CompactMove {
#[export_name = "CompactMove_normal"]
pub extern "C" fn normal(from: Square, to: Square, promote: bool) -> Self {
let value = (promote as u16) << 15 | (from.index() as u16) << 8 | to.index() as u16;
Self(unsafe { NonZeroU16::new_unchecked(value) })
}
#[export_name = "CompactMove_drop"]
pub extern "C" fn drop(piece: Piece, to: Square) -> Self {
let value = (piece.as_u8() as u16) << 8 | 128 | to.index() as u16;
Self(unsafe { NonZeroU16::new_unchecked(value) })
}
pub fn from(self) -> Option<Square> {
let inner = self.0.get();
if self.is_drop() {
None
} else {
let from = ((inner >> 8) & 127) as u8;
Some(unsafe { Square::from_u8_unchecked(from) })
}
}
#[no_mangle]
pub extern "C" fn CompactMove_from(self) -> OptionSquare {
self.from().into()
}
#[export_name = "CompactMove_to"]
pub extern "C" fn to(self) -> Square {
let to = (self.0.get() & 127) as u8;
unsafe { Square::from_u8_unchecked(to) }
}
#[export_name = "CompactMove_is_promoting"]
pub extern "C" fn is_promoting(self) -> bool {
(self.0.get() & 32768) != 0
}
#[export_name = "CompactMove_is_drop"]
#[inline]
pub extern "C" fn is_drop(self) -> bool {
(self.0.get() & 128) != 0
}
}
impl_ord_for_single_field!(CompactMove);
impl_hash_for_single_field!(CompactMove);
impl ToUsi for CompactMove {
fn to_usi<W: core::fmt::Write>(&self, sink: &mut W) -> core::fmt::Result {
<Move as From<CompactMove>>::from(*self).to_usi(sink)
}
}
#[repr(transparent)]
#[derive(Eq, PartialEq, Clone, Copy, Debug, Default)]
pub struct OptionCompactMove(u16);
impl From<Option<CompactMove>> for OptionCompactMove {
#[inline(always)]
fn from(arg: Option<CompactMove>) -> Self {
Self(match arg {
Some(result) => result.0.get(),
None => 0,
})
}
}
impl From<OptionCompactMove> for Option<CompactMove> {
#[inline(always)]
fn from(arg: OptionCompactMove) -> Self {
Some(CompactMove(NonZeroU16::new(arg.0)?))
}
}
impl_ord_for_single_field!(OptionCompactMove);
impl_hash_for_single_field!(OptionCompactMove);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_into_works() {
for from in 1..=81 {
let from = unsafe { Square::from_u8_unchecked(from) };
for to in 1..=81 {
let to = unsafe { Square::from_u8_unchecked(to) };
for &promote in &[false, true] {
let mv = Move::Normal { from, to, promote };
let compact: CompactMove = mv.into();
let mv2: Move = compact.into();
assert_eq!(mv, mv2);
}
}
}
for piece in Piece::all() {
for to in 1..=81 {
let to = unsafe { Square::from_u8_unchecked(to) };
let mv = Move::Drop { piece, to };
let compact: CompactMove = mv.into();
let mv2: Move = compact.into();
assert_eq!(mv, mv2);
}
}
}
#[test]
fn normal_works() {
for from in Square::all() {
for to in Square::all() {
for promote in [false, true] {
let cmv = CompactMove::normal(from, to, promote);
assert_eq!(
<CompactMove as From<Move>>::from(Move::Normal { from, to, promote }),
cmv,
);
assert_eq!(cmv.from(), Some(from));
assert_eq!(cmv.to(), to);
assert_eq!(cmv.is_promoting(), promote);
assert!(!cmv.is_drop());
}
}
}
}
#[test]
fn drop_works() {
for piece in Piece::all() {
for to in Square::all() {
let cmv = CompactMove::drop(piece, to);
assert_eq!(
<CompactMove as From<Move>>::from(Move::Drop { piece, to }),
cmv,
);
assert_eq!(cmv.from(), None);
assert_eq!(cmv.to(), to);
assert!(!cmv.is_promoting());
assert!(cmv.is_drop());
}
}
}
}