// 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() } } #[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"); } }