From 177a4e32da9e58714fd0a6bf5ba05ae88bc9a79c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 16 Jan 2024 18:03:27 -0800 Subject: [PATCH] [board] Implement a u16 based Move Replace building a Move with the Move struct itself with a MoveBuilder that builds a Move and returns it. Update the tests and move formatter. --- board/src/lib.rs | 3 +- board/src/move.rs | 395 ++++++++++++++++++++++++++++++++++++++ board/src/moves/mod.rs | 2 - board/src/moves/move.rs | 210 -------------------- board/src/position/mod.rs | 1 + 5 files changed, 398 insertions(+), 213 deletions(-) create mode 100644 board/src/move.rs delete mode 100644 board/src/moves/move.rs diff --git a/board/src/lib.rs b/board/src/lib.rs index cea348e..442935a 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -4,6 +4,7 @@ mod bitboard; mod display; mod moves; +mod r#move; #[macro_use] pub mod piece; #[macro_use] @@ -11,8 +12,8 @@ pub mod position; mod sight; mod square; -pub use moves::Move; pub use position::Position; +pub use r#move::{Move, MoveBuilder}; pub use square::{File, Rank, Square}; pub(crate) use bitboard::BitBoard; diff --git a/board/src/move.rs b/board/src/move.rs new file mode 100644 index 0000000..2f74331 --- /dev/null +++ b/board/src/move.rs @@ -0,0 +1,395 @@ +// Eryn Wells + +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 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 for PromotableShape { + type Error = (); + + fn try_from(value: Shape) -> Result { + 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::() } + } +} + +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 { + 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 { + 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() + } +} + +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, + } + + 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::Short, + } + } + + 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"); + } +} diff --git a/board/src/moves/mod.rs b/board/src/moves/mod.rs index d395991..44e2f7c 100644 --- a/board/src/moves/mod.rs +++ b/board/src/moves/mod.rs @@ -3,7 +3,6 @@ mod bishop; mod king; mod knight; -mod r#move; mod move_generator; mod move_set; mod pawn; @@ -11,7 +10,6 @@ mod queen; mod rook; pub use move_generator::Moves; -pub use r#move::Move; pub(self) use move_set::MoveSet; diff --git a/board/src/moves/move.rs b/board/src/moves/move.rs deleted file mode 100644 index d952bec..0000000 --- a/board/src/moves/move.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Eryn Wells - -use crate::{ - piece::{Piece, PlacedPiece, Shape}, - position::BoardSide, - Square, -}; - -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub struct Move { - piece: Piece, - from: Square, - to: Square, - capturing: Option, - promoting_to: Option, -} - -impl Move { - pub fn new(piece: Piece, from: Square, to: Square) -> Move { - Move { - piece, - from, - to, - capturing: None, - promoting_to: None, - } - } - - pub fn capturing(mut self, piece: PlacedPiece) -> Move { - self.capturing = Some(piece); - self - } - - pub fn promoting_to(mut self, shape: Shape) -> Move { - self.promoting_to = Some(shape); - self - } - - pub fn is_castle(&self) -> bool { - let color = self.piece.color(); - self.piece.shape() == Shape::King - && self.from == Square::KING_STARTING_SQUARES[color as usize] - && Square::KING_CASTLE_TARGET_SQUARES[color as usize].contains(&self.to) - } - - pub fn is_kingside_castle(&self) -> bool { - let color = self.piece.color(); - self.piece.shape() == Shape::King - && self.from == Square::KING_STARTING_SQUARES[color as usize] - && self.to - == Square::KING_CASTLE_TARGET_SQUARES[color as usize][BoardSide::King as usize] - } - - pub fn is_queenside_castle(&self) -> bool { - let color = self.piece.color(); - self.piece.shape() == Shape::King - && self.from == Square::KING_STARTING_SQUARES[color as usize] - && self.to - == Square::KING_CASTLE_TARGET_SQUARES[color as usize][BoardSide::Queen as usize] - } - - pub fn is_capture(&self) -> bool { - self.capturing.is_some() - } - - pub fn is_promotion(&self) -> bool { - self.promoting_to.is_some() - } -} - -mod move_formatter { - use super::Move; - use crate::{piece::Shape, Position}; - use std::fmt; - - enum Style { - Short, - Long, - } - - pub(crate) struct AlgebraicMoveFormatter<'m> { - r#move: &'m Move, - style: Style, - } - - impl<'pos, 'm> AlgebraicMoveFormatter<'m> { - pub(crate) fn new(mv: &'m Move) -> AlgebraicMoveFormatter<'m> { - AlgebraicMoveFormatter { - r#move: mv, - style: Style::Short, - } - } - - 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 shape = mv.piece.shape(); - if shape != Shape::Pawn { - write!(f, "{}", shape)?; - } - - write!( - f, - "{}{}{}", - mv.from, - if mv.is_capture() { 'x' } else { '-' }, - mv.to, - )?; - - if let Some(promoting_to) = mv.promoting_to { - write!(f, "={}", promoting_to)?; - } - - // TODO: Write check (+) and checkmate (#) symbols - - Ok(()) - } - } - - impl<'pos, 'mv> fmt::Display for AlgebraicMoveFormatter<'mv> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.r#move.is_kingside_castle() { - return self.fmt_kingside_castle(f); - } else if self.r#move.is_queenside_castle() { - 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}; - - macro_rules! chess_move { - ($color:ident $shape:ident $from_square:ident - $to_square:ident) => { - crate::Move::new( - crate::piece::Piece::new(crate::piece::Color::$color, crate::piece::Shape::$shape), - crate::Square::$from_square, - crate::Square::$to_square, - ) - }; - ($color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident) => { - chess_move!($color $shape $from_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, - )) - }; - } - - 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 mv = chess_move!( - $color $shape $from_square x $to_square, - $captured_color $captured_shape - ); - - let formatter = AlgebraicMoveFormatter::new(&mv).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 mv = chess_move!($color $shape $from_square-$to_square); - - let formatter = AlgebraicMoveFormatter::new(&mv).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"); - } -} diff --git a/board/src/position/mod.rs b/board/src/position/mod.rs index 650959d..6899faa 100644 --- a/board/src/position/mod.rs +++ b/board/src/position/mod.rs @@ -3,6 +3,7 @@ mod diagram_formatter; mod flags; mod pieces; +#[macro_use] mod position; pub use diagram_formatter::DiagramFormatter;