chessfriend/moves/src/moves.rs

159 lines
3.7 KiB
Rust
Raw Normal View History

// Eryn Wells <eryn@erynwells.me>
use crate::builder::Builder;
use crate::defs::Kind;
use chessfriend_core::{Rank, Shape, Square, Wing};
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`.
///
#[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()
}
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 {
(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!(),
});
}
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]
pub fn is_quiet(&self) -> bool {
2024-02-09 20:00:47 -08:00
self.flags() == Kind::Quiet as u16
}
2025-05-08 17:37:51 -07:00
#[must_use]
pub fn is_double_push(&self) -> bool {
2024-02-09 20:00:47 -08:00
self.flags() == Kind::DoublePush as u16
}
2025-05-08 17:37:51 -07:00
#[must_use]
pub fn is_castle(&self) -> bool {
(self.0 & 0b0010) != 0
}
2025-05-08 17:37:51 -07:00
#[must_use]
pub fn castle(&self) -> Option<Wing> {
match self.flags() {
0b0010 => Some(Wing::KingSide),
0b0011 => Some(Wing::QueenSide),
_ => None,
}
}
2025-05-08 17:37:51 -07:00
#[must_use]
pub fn is_capture(&self) -> bool {
(self.0 & Kind::Capture as u16) != 0
}
2025-05-08 17:37:51 -07:00
#[must_use]
pub fn is_en_passant(&self) -> bool {
self.flags() == Kind::EnPassantCapture as u16
}
2025-05-08 17:37:51 -07:00
#[must_use]
pub fn is_promotion(&self) -> bool {
(self.0 & Kind::Promotion as u16) != 0
}
2025-05-08 17:37:51 -07:00
#[must_use]
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
}
2025-05-08 17:37:51 -07:00
impl Move {
#[inline]
2025-05-08 17:37:51 -07:00
fn flags(self) -> u16 {
self.0 & 0b1111
}
#[inline]
2025-05-08 17:37:51 -07:00
fn special(self) -> u16 {
self.0 & 0b11
}
2025-05-08 17:37:51 -07:00
}
2025-05-08 17:37:51 -07:00
impl Move {
fn transfer_char(self) -> char {
if self.is_capture() || self.is_en_passant() {
'x'
} else {
'-'
}
}
}
const KINGSIDE_CASTLE_STR: &str = "0-0";
const QUEENSIDE_CASTLE_STR: &str = "0-0-0";
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 {
Wing::KingSide => write!(f, "{KINGSIDE_CASTLE_STR}"),
Wing::QueenSide => write!(f, "{QUEENSIDE_CASTLE_STR}"),
2025-05-08 17:37:51 -07:00
};
}
2025-05-08 17:37:51 -07:00
let origin = self.origin_square();
let target = self.target_square();
let transfer_char = self.transfer_char();
2025-05-08 17:37:51 -07:00
write!(f, "{origin}{transfer_char}{target}")?;
if let Some(promotion) = self.promotion() {
2025-05-08 17:37:51 -07:00
write!(f, "={promotion}")?;
} else if self.is_en_passant() {
write!(f, " e.p.")?;
}
Ok(())
}
}
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()
}
}