2024-02-03 15:17:02 -08:00
|
|
|
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
2025-05-19 14:19:05 -07:00
|
|
|
use crate::builder::Builder;
|
2024-04-26 09:50:42 -04:00
|
|
|
use crate::defs::Kind;
|
2025-05-19 16:50:30 -07:00
|
|
|
use chessfriend_core::{Rank, Shape, Square, Wing};
|
2024-02-03 15:17:02 -08:00
|
|
|
use std::fmt;
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
/// A single player's move. In game theory parlance, this is a "ply".
|
|
|
|
///
|
|
|
|
/// ## TODO
|
|
|
|
///
|
|
|
|
/// - Rename this class `Ply`.
|
|
|
|
///
|
2024-02-03 15:17:02 -08:00
|
|
|
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
|
|
|
pub struct Move(pub(crate) u16);
|
|
|
|
|
|
|
|
impl Move {
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
|
|
|
#[allow(clippy::missing_panics_doc)]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub fn origin_square(&self) -> Square {
|
2025-05-08 17:37:51 -07:00
|
|
|
((self.0 >> 4) & 0b111_111).try_into().unwrap()
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
|
|
|
#[allow(clippy::missing_panics_doc)]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub fn target_square(&self) -> Square {
|
2024-02-03 15:17:02 -08:00
|
|
|
(self.0 >> 10).try_into().unwrap()
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-09 20:00:47 -08:00
|
|
|
pub fn capture_square(&self) -> Option<Square> {
|
|
|
|
if self.is_en_passant() {
|
|
|
|
let target_square = self.target_square();
|
|
|
|
return Some(match target_square.rank() {
|
|
|
|
Rank::THREE => Square::from_file_rank(target_square.file(), Rank::FOUR),
|
|
|
|
Rank::SIX => Square::from_file_rank(target_square.file(), Rank::FIVE),
|
|
|
|
_ => unreachable!(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-02-13 11:04:21 -07:00
|
|
|
if self.is_capture() {
|
|
|
|
return Some(self.target_square());
|
|
|
|
}
|
|
|
|
|
2024-02-09 20:00:47 -08:00
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:02 -08:00
|
|
|
pub fn is_quiet(&self) -> bool {
|
2024-02-09 20:00:47 -08:00
|
|
|
self.flags() == Kind::Quiet as u16
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:02 -08:00
|
|
|
pub fn is_double_push(&self) -> bool {
|
2024-02-09 20:00:47 -08:00
|
|
|
self.flags() == Kind::DoublePush as u16
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:02 -08:00
|
|
|
pub fn is_castle(&self) -> bool {
|
2025-05-19 14:19:05 -07:00
|
|
|
(self.0 & 0b0010) != 0
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2025-05-19 16:50:30 -07:00
|
|
|
pub fn castle(&self) -> Option<Wing> {
|
2024-02-03 15:17:02 -08:00
|
|
|
match self.flags() {
|
2025-05-19 16:50:30 -07:00
|
|
|
0b0010 => Some(Wing::KingSide),
|
|
|
|
0b0011 => Some(Wing::QueenSide),
|
2024-02-03 15:17:02 -08:00
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:02 -08:00
|
|
|
pub fn is_capture(&self) -> bool {
|
2025-05-19 14:19:05 -07:00
|
|
|
(self.0 & Kind::Capture as u16) != 0
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:02 -08:00
|
|
|
pub fn is_en_passant(&self) -> bool {
|
2025-05-19 16:50:30 -07:00
|
|
|
self.flags() == Kind::EnPassantCapture as u16
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:02 -08:00
|
|
|
pub fn is_promotion(&self) -> bool {
|
2025-05-19 14:19:05 -07:00
|
|
|
(self.0 & Kind::Promotion as u16) != 0
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[must_use]
|
2024-02-03 15:17:02 -08:00
|
|
|
pub fn promotion(&self) -> Option<Shape> {
|
|
|
|
if !self.is_promotion() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(match self.special() {
|
|
|
|
0b00 => Shape::Knight,
|
|
|
|
0b01 => Shape::Bishop,
|
|
|
|
0b10 => Shape::Rook,
|
|
|
|
0b11 => Shape::Queen,
|
|
|
|
_ => unreachable!(),
|
|
|
|
})
|
|
|
|
}
|
2025-05-08 17:37:51 -07:00
|
|
|
}
|
2024-02-03 15:17:02 -08:00
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
impl Move {
|
2024-02-03 15:17:02 -08:00
|
|
|
#[inline]
|
2025-05-08 17:37:51 -07:00
|
|
|
fn flags(self) -> u16 {
|
2024-02-03 15:17:02 -08:00
|
|
|
self.0 & 0b1111
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2025-05-08 17:37:51 -07:00
|
|
|
fn special(self) -> u16 {
|
2024-02-03 15:17:02 -08:00
|
|
|
self.0 & 0b11
|
|
|
|
}
|
2025-05-08 17:37:51 -07:00
|
|
|
}
|
2024-02-10 18:33:29 -07:00
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
impl Move {
|
2025-05-19 14:19:05 -07:00
|
|
|
fn transfer_char(self) -> char {
|
2024-02-10 18:33:29 -07:00
|
|
|
if self.is_capture() || self.is_en_passant() {
|
|
|
|
'x'
|
|
|
|
} else {
|
|
|
|
'-'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-19 14:19:05 -07:00
|
|
|
const KINGSIDE_CASTLE_STR: &str = "0-0";
|
|
|
|
const QUEENSIDE_CASTLE_STR: &str = "0-0-0";
|
|
|
|
|
2024-02-10 18:33:29 -07:00
|
|
|
impl fmt::Display for Move {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
if let Some(castle) = self.castle() {
|
2025-05-08 17:37:51 -07:00
|
|
|
return match castle {
|
2025-05-19 16:50:30 -07:00
|
|
|
Wing::KingSide => write!(f, "{KINGSIDE_CASTLE_STR}"),
|
|
|
|
Wing::QueenSide => write!(f, "{QUEENSIDE_CASTLE_STR}"),
|
2025-05-08 17:37:51 -07:00
|
|
|
};
|
2024-02-10 18:33:29 -07:00
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
let origin = self.origin_square();
|
|
|
|
let target = self.target_square();
|
2025-05-19 14:19:05 -07:00
|
|
|
let transfer_char = self.transfer_char();
|
2025-05-08 17:37:51 -07:00
|
|
|
write!(f, "{origin}{transfer_char}{target}")?;
|
2024-02-10 18:33:29 -07:00
|
|
|
|
|
|
|
if let Some(promotion) = self.promotion() {
|
2025-05-08 17:37:51 -07:00
|
|
|
write!(f, "={promotion}")?;
|
2024-02-10 18:33:29 -07:00
|
|
|
} else if self.is_en_passant() {
|
|
|
|
write!(f, " e.p.")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-02-03 15:17:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for Move {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.debug_tuple("Move")
|
|
|
|
.field(&format_args!("{:08b}", &self.0))
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|