chessfriend/board/src/move.rs

402 lines
12 KiB
Rust

// Eryn Wells <eryn@erynwells.me>
use crate::{
piece::{Piece, PlacedPiece, Shape},
position::BoardSide,
square::Rank,
Square,
};
use std::fmt;
#[repr(u16)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Castle {
KingSide = 0b10,
QueenSide = 0b11,
}
impl From<BoardSide> for Castle {
fn from(value: BoardSide) -> Self {
match value {
BoardSide::King => Castle::KingSide,
BoardSide::Queen => Castle::QueenSide,
}
}
}
#[repr(u16)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PromotableShape {
Knight = 0b00,
Bishop = 0b01,
Rook = 0b10,
Queen = 0b11,
}
impl TryFrom<Shape> for PromotableShape {
type Error = ();
fn try_from(value: Shape) -> Result<Self, Self::Error> {
match value {
Shape::Knight => Ok(PromotableShape::Knight),
Shape::Bishop => Ok(PromotableShape::Bishop),
Shape::Rook => Ok(PromotableShape::Rook),
Shape::Queen => Ok(PromotableShape::Queen),
_ => Err(()),
}
}
}
#[repr(u16)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Kind {
Quiet = 0b00,
DoublePush = 0b01,
Castle(Castle),
Capture(PlacedPiece) = 0b0100,
EnPassantCapture(PlacedPiece) = 0b0101,
Promotion(PromotableShape) = 0b1000,
CapturePromotion(PlacedPiece, PromotableShape) = 0b1100,
}
impl Kind {
fn bits(&self) -> u16 {
match self {
Self::Promotion(shape) => self.discriminant() | *shape as u16,
Self::CapturePromotion(_, shape) => self.discriminant() | *shape as u16,
Self::Castle(castle) => *castle as u16,
_ => self.discriminant(),
}
}
/// Return the discriminant value. This implementation is copied from the Rust docs.
/// See https://doc.rust-lang.org/std/mem/fn.discriminant.html
fn discriminant(&self) -> u16 {
unsafe { *<*const _>::from(self).cast::<u16>() }
}
}
impl Default for Kind {
fn default() -> Self {
Self::Quiet
}
}
/// A single player's move. In chess parlance, this is a "ply".
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Move(u16);
impl Move {
pub fn from_square(&self) -> Square {
((self.0 >> 4) & 0b111111).try_into().unwrap()
}
pub fn to_square(&self) -> Square {
(self.0 >> 10).try_into().unwrap()
}
pub fn is_quiet(&self) -> bool {
self.special() == 0b00
}
pub fn is_double_push(&self) -> bool {
self.special() == 0b01
}
pub fn is_castle(&self) -> bool {
self.castle().is_some()
}
pub fn castle(&self) -> Option<Castle> {
match self.special() {
0b0010 => Some(Castle::KingSide),
0b0011 => Some(Castle::QueenSide),
_ => None,
}
}
pub fn is_capture(&self) -> bool {
(self.0 & 0b0100) != 0
}
pub fn is_en_passant(&self) -> bool {
(self.0 & 0b0101) != 0
}
pub fn is_promotion(&self) -> bool {
(self.0 & 0b1000) != 0
}
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!(),
})
}
#[inline]
fn special(&self) -> u16 {
self.0 & 0b11
}
}
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()
}
}
#[derive(Clone, Debug)]
pub struct MoveBuilder {
piece: Piece,
from: Square,
to: Square,
kind: Kind,
}
impl MoveBuilder {
pub fn new(piece: Piece, from: Square, to: Square) -> Self {
let kind = match piece.shape() {
Shape::Pawn => {
let is_white_double_push = from.rank() == Rank::Two && to.rank() == Rank::Four;
let is_black_double_push = from.rank() == Rank::Seven && to.rank() == Rank::Five;
if is_white_double_push || is_black_double_push {
Kind::DoublePush
} else {
Kind::Quiet
}
}
_ => Kind::Quiet,
};
Self {
piece,
from,
to,
kind,
}
}
pub fn castle(mut self, castle: Castle) -> Self {
self.kind = Kind::Castle(castle);
self
}
pub fn capturing(mut self, captured_piece: PlacedPiece) -> Self {
self.kind = match self.kind {
Kind::Promotion(shape) => Kind::CapturePromotion(captured_piece, shape),
_ => Kind::Capture(captured_piece),
};
self
}
pub fn promoting_to(mut self, shape: Shape) -> Self {
if let Some(shape) = PromotableShape::try_from(shape).ok() {
self.kind = match self.kind {
Kind::Capture(piece) => Kind::CapturePromotion(piece, shape),
Kind::CapturePromotion(piece, _) => Kind::CapturePromotion(piece, shape),
_ => Kind::Promotion(shape),
};
}
self
}
pub fn build(&self) -> Move {
Move(
self.kind.bits()
| ((self.from as u16 & 0b111111) << 4)
| ((self.to as u16 & 0b111111) << 10),
)
}
}
mod move_formatter {
use super::{Castle, Move};
use crate::{piece::Shape, Position};
use std::fmt;
enum Style {
Short,
Long,
}
impl Default for Style {
fn default() -> Self {
Style::Long
}
}
pub(crate) struct AlgebraicMoveFormatter<'m, 'pos> {
position: &'pos Position,
r#move: &'m Move,
style: Style,
}
impl<'pos, 'm> AlgebraicMoveFormatter<'m, 'pos> {
pub(crate) fn new(
mv: &'m Move,
position: &'pos Position,
) -> AlgebraicMoveFormatter<'m, 'pos> {
AlgebraicMoveFormatter {
position,
r#move: mv,
style: Style::default(),
}
}
fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
fn fmt_kingside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0-0")
}
fn fmt_queenside_castle(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0-0-0")
}
fn fmt_short(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
unimplemented!()
}
fn fmt_long(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Figure out how to write the short algebraic form, where a
// disambiguating coordiate is specified when two of the same piece
// cam move to the same square.
// TODO: Write better pawn moves.
let mv = self.r#move;
let from_square = mv.from_square();
let to_square = mv.to_square();
let piece = self
.position
.piece_on_square(from_square)
.expect(&format!("No piece on {}", from_square));
if piece.shape() != Shape::Pawn {
write!(f, "{}", piece.shape())?;
}
write!(
f,
"{}{}{}",
from_square,
if mv.is_capture() { 'x' } else { '-' },
to_square,
)?;
if let Some(promotion) = mv.promotion() {
write!(f, "={}", promotion)?;
}
// TODO: Write check (+) and checkmate (#) symbols
Ok(())
}
}
impl<'pos, 'mv> fmt::Display for AlgebraicMoveFormatter<'mv, 'pos> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mv = self.r#move;
match mv.castle() {
Some(Castle::KingSide) => return self.fmt_kingside_castle(f),
Some(Castle::QueenSide) => return self.fmt_queenside_castle(f),
_ => {}
}
match self.style {
Style::Short => self.fmt_short(f),
Style::Long => self.fmt_long(f),
}
}
}
#[cfg(test)]
mod tests {
use super::{AlgebraicMoveFormatter, Style};
use crate::{piece, position};
macro_rules! chess_move {
($color:ident $shape:ident $from_square:ident - $to_square:ident) => {
$crate::MoveBuilder::new(
$crate::piece::Piece::new(
$crate::piece::Color::$color,
$crate::piece::Shape::$shape,
),
$crate::Square::$from_square,
$crate::Square::$to_square,
)
.build()
};
($color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident) => {
$crate::MoveBuilder::new(
$crate::piece::Piece::new(
$crate::piece::Color::$color,
$crate::piece::Shape::$shape,
),
$crate::Square::$from_square,
$crate::Square::$to_square,
)
.capturing($crate::piece::PlacedPiece::new(
$crate::piece::Piece::new(
$crate::piece::Color::$captured_color,
$crate::piece::Shape::$captured_shape,
),
$crate::Square::$to_square,
))
.build()
};
}
macro_rules! test_algebraic_formatter {
($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident, $output:expr) => {
#[test]
fn $test_name() {
let pos = position![
$color $shape on $from_square,
$captured_color $captured_shape on $to_square,
];
let mv = chess_move!(
$color $shape $from_square x $to_square,
$captured_color $captured_shape
);
println!("{:?}", &mv);
let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style);
assert_eq!(format!("{}", formatter), $output);
}
};
($test_name:ident, $style:ident, $color:ident $shape:ident $from_square:ident - $to_square:ident, $output:expr) => {
#[test]
fn $test_name() {
let pos = position![
$color $shape on $from_square,
];
let mv = chess_move!($color $shape $from_square-$to_square);
println!("{:?}", &mv);
let formatter = AlgebraicMoveFormatter::new(&mv, &pos).style(Style::$style);
assert_eq!(format!("{}", formatter), $output);
}
};
}
test_algebraic_formatter!(long_pawn_move, Long, White Pawn E4 - E5, "e4-e5");
test_algebraic_formatter!(long_bishop_move, Long, White Bishop A4 - D7, "Ba4-d7");
test_algebraic_formatter!(long_bishop_capture, Long, White Bishop A2 x E6, Black Knight, "Ba2xe6");
}
}