diff --git a/position/src/move.rs b/position/src/move.rs deleted file mode 100644 index 32bc399..0000000 --- a/position/src/move.rs +++ /dev/null @@ -1,602 +0,0 @@ -// Eryn Wells - -use chessfriend_core::{Piece, PlacedPiece, Rank, Shape, Square}; -use std::fmt; - -pub use castle::Castle; -pub(crate) use move_formatter::AlgebraicMoveFormatter; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum MakeMoveError { - PlayerOutOfTurn, - NoPiece, - NoCapturedPiece, - NoLegalMoves, - IllegalCastle, - IllegalSquare(Square), -} - -mod castle { - use chessfriend_bitboard::BitBoard; - use chessfriend_core::{Color, Square}; - - #[repr(u16)] - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - pub enum Castle { - KingSide = 0b10, - QueenSide = 0b11, - } - - pub(crate) struct CastlingParameters { - clear_squares: BitBoard, - check_squares: BitBoard, - } - - #[derive(Debug)] - pub(crate) struct Squares { - pub king: Square, - pub rook: Square, - } - - impl Castle { - pub const ALL: [Castle; 2] = [Castle::KingSide, Castle::QueenSide]; - - const STARTING_SQUARES: [[Squares; 2]; 2] = [ - [ - Squares { - king: Square::E1, - rook: Square::H1, - }, - Squares { - king: Square::E1, - rook: Square::A1, - }, - ], - [ - Squares { - king: Square::E8, - rook: Square::H8, - }, - Squares { - king: Square::E8, - rook: Square::A8, - }, - ], - ]; - - const TARGET_SQUARES: [[Squares; 2]; 2] = [ - [ - Squares { - king: Square::G1, - rook: Square::F1, - }, - Squares { - king: Square::C1, - rook: Square::D1, - }, - ], - [ - Squares { - king: Square::G8, - rook: Square::F8, - }, - Squares { - king: Square::C8, - rook: Square::D8, - }, - ], - ]; - - pub(crate) fn starting_squares(&self, color: Color) -> &'static Squares { - &Castle::STARTING_SQUARES[color as usize][self.into_index()] - } - - pub(crate) fn target_squares(&self, color: Color) -> &'static Squares { - &Castle::TARGET_SQUARES[color as usize][self.into_index()] - } - - pub(crate) fn into_index(&self) -> usize { - match self { - Castle::KingSide => 0, - Castle::QueenSide => 1, - } - } - - pub(crate) fn parameters(&self) -> CastlingParameters { - match self { - Castle::KingSide => CastlingParameters { - clear_squares: BitBoard::new(0b01100000), - check_squares: BitBoard::new(0b01110000), - }, - Castle::QueenSide => CastlingParameters { - clear_squares: BitBoard::new(0b00001110), - check_squares: BitBoard::new(0b00011100), - }, - } - } - } - - impl CastlingParameters { - pub fn clear_squares(&self) -> &BitBoard { - &self.clear_squares - } - - pub fn check_squares(&self) -> &BitBoard { - &self.check_squares - } - } -} - -#[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, Copy, 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.flags() == Kind::Quiet.discriminant() - } - - pub fn is_double_push(&self) -> bool { - self.flags() == Kind::DoublePush.discriminant() - } - - pub fn is_castle(&self) -> bool { - self.castle().is_some() - } - - pub fn castle(&self) -> Option { - match self.flags() { - 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.flags() == 0b0101 - } - - 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 flags(&self) -> u16 { - self.0 & 0b1111 - } - - #[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 from_rank = from.rank(); - let to_rank = to.rank(); - 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 capturing_en_passant(mut self, captured_piece: PlacedPiece) -> Self { - self.kind = Kind::EnPassantCapture(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::Position; - use chessfriend_core::Shape; - 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::position; - use chessfriend_core::piece; - - macro_rules! chess_move { - ($color:ident $shape:ident $from_square:ident - $to_square:ident) => { - $crate::MoveBuilder::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape, - ), - chessfriend_core::Square::$from_square, - chessfriend_core::Square::$to_square, - ) - .build() - }; - ($color:ident $shape:ident $from_square:ident x $to_square:ident, $captured_color:ident $captured_shape:ident) => { - $crate::MoveBuilder::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$color, - chessfriend_core::Shape::$shape, - ), - chessfriend_core::Square::$from_square, - chessfriend_core::Square::$to_square, - ) - .capturing(chessfriend_core::PlacedPiece::new( - chessfriend_core::Piece::new( - chessfriend_core::Color::$captured_color, - chessfriend_core::Shape::$captured_shape, - ), - chessfriend_core::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"); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use chessfriend_core::piece; - - macro_rules! assert_flag { - ($move:expr, $left:expr, $right:expr, $desc:expr) => { - assert_eq!($left, $right, "{:?} -> {}", $move, stringify!($desc)) - }; - } - - macro_rules! assert_flags { - ($move:expr, $quiet:expr, $double_push:expr, $en_passant:expr, $capture:expr, $castle:expr, $promotion:expr) => { - assert_flag!($move, $move.is_quiet(), $quiet, "is_quiet"); - assert_flag!( - $move, - $move.is_double_push(), - $double_push, - "is_double_push" - ); - assert_flag!($move, $move.is_en_passant(), $en_passant, "is_en_passant"); - assert_flag!($move, $move.is_capture(), $capture, "is_capture"); - assert_flag!($move, $move.is_castle(), $castle, "is_castle"); - assert_flag!($move, $move.is_promotion(), $promotion, "is_promotion"); - }; - } - - #[test] - fn move_flags_quiet() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::A5).build(); - assert_flags!(mv, true, false, false, false, false, false); - } - - #[test] - fn move_flags_double_push() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::C2, Square::C4).build(); - assert_flags!(mv, false, true, false, false, false, false); - } - - #[test] - fn move_flags_capture() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::A4, Square::B5) - .capturing(piece!(Black Bishop on B5)) - .build(); - assert_flags!(mv, false, false, false, true, false, false); - } - - #[test] - fn move_flags_en_passant_capture() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::A5, Square::B6) - .capturing_en_passant(piece!(Black Pawn on B5)) - .build(); - assert_flags!(mv, false, false, true, true, false, false); - } - - #[test] - fn move_flags_promotion() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::H8) - .promoting_to(Shape::Queen) - .build(); - assert_flags!(mv, false, false, false, false, false, true); - assert_eq!(mv.promotion(), Some(Shape::Queen)); - } - - #[test] - fn move_flags_capture_promotion() { - let mv = MoveBuilder::new(piece!(White Pawn), Square::H7, Square::G8) - .capturing(piece!(Black Knight on G8)) - .promoting_to(Shape::Queen) - .build(); - assert_flags!(mv, false, false, false, true, false, true); - assert_eq!(mv.promotion(), Some(Shape::Queen)); - } - - #[test] - fn move_flags_castle() { - let mv = MoveBuilder::new(piece!(Black King), Square::E8, Square::G8) - .castle(Castle::KingSide) - .build(); - assert_flags!(mv, false, false, false, false, true, false); - } -}