diff --git a/board/src/moves/move.rs b/board/src/moves/move.rs index ece8f18..d952bec 100644 --- a/board/src/moves/move.rs +++ b/board/src/moves/move.rs @@ -58,4 +58,153 @@ impl Move { && 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"); + } }