diff --git a/moves/src/moves.rs b/moves/src/moves.rs index 30293ec..f489ad6 100644 --- a/moves/src/moves.rs +++ b/moves/src/moves.rs @@ -13,45 +13,52 @@ use std::fmt; #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Move(pub(crate) u16); +fn origin_bits(square: Square) -> u16 { + (square as u16) << 4 +} + +fn target_bits(square: Square) -> u16 { + (square as u16) << 10 +} + impl Move { #[must_use] pub fn quiet(origin: Square, target: Square) -> Self { - let origin_bits = (origin as u16) << 4; - let target_bits = (target as u16) << 10; - Move(origin_bits | target_bits) + Move(origin_bits(origin) | target_bits(target)) } #[must_use] pub fn double_push(origin: Square, target: Square) -> Self { - let origin_bits = (origin as u16) << 4; - let target_bits = (target as u16) << 10; let flag_bits = Kind::DoublePush as u16; - Move(origin_bits | target_bits | flag_bits) + Move(origin_bits(origin) | target_bits(target) | flag_bits) } #[must_use] pub fn capture(origin: Square, target: Square) -> Self { - let origin_bits = (origin as u16) << 4; - let target_bits = (target as u16) << 10; let flag_bits = Kind::Capture as u16; - Move(origin_bits | target_bits | flag_bits) + Move(origin_bits(origin) | target_bits(target) | flag_bits) } #[must_use] pub fn en_passant_capture(origin: Square, target: Square) -> Self { - let origin_bits = (origin as u16) << 4; - let target_bits = (target as u16) << 10; let flag_bits = Kind::EnPassantCapture as u16; - Move(origin_bits | target_bits | flag_bits) + Move(origin_bits(origin) | target_bits(target) | flag_bits) } #[must_use] pub fn promotion(origin: Square, target: Square, shape: PromotionShape) -> Self { - let origin_bits = (origin as u16) << 4; - let target_bits = (target as u16) << 10; let flag_bits = Kind::Promotion as u16; let shape_bits = shape as u16; - Move(origin_bits | target_bits | flag_bits | shape_bits) + Move(origin_bits(origin) | target_bits(target) | flag_bits | shape_bits) + } + + #[must_use] + pub fn castle(origin: Square, target: Square, wing: Wing) -> Self { + let flag_bits = match wing { + Wing::KingSide => Kind::KingSideCastle, + Wing::QueenSide => Kind::QueenSideCastle, + } as u16; + Move(origin_bits(origin) | target_bits(target) | flag_bits) } } @@ -102,7 +109,7 @@ impl Move { } #[must_use] - pub fn castle(&self) -> Option { + pub fn castle_wing(&self) -> Option { match self.flags() { 0b0010 => Some(Wing::KingSide), 0b0011 => Some(Wing::QueenSide), @@ -168,7 +175,7 @@ const QUEENSIDE_CASTLE_STR: &str = "0-0-0"; impl fmt::Display for Move { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(castle) = self.castle() { + if let Some(castle) = self.castle_wing() { return match castle { Wing::KingSide => write!(f, "{KINGSIDE_CASTLE_STR}"), Wing::QueenSide => write!(f, "{QUEENSIDE_CASTLE_STR}"), diff --git a/moves/tests/flags.rs b/moves/tests/flags.rs index ecf194a..f4d7530 100644 --- a/moves/tests/flags.rs +++ b/moves/tests/flags.rs @@ -78,7 +78,7 @@ fn move_flags_promotion() -> TestResult { .build()?; assert!(ply.is_promotion()); - assert_eq!(ply.promotion(), Some(Shape::Queen)); + assert_eq!(ply.promotion_shape(), Some(Shape::Queen)); Ok(()) } @@ -93,7 +93,7 @@ fn move_flags_capture_promotion() -> TestResult { assert!(ply.is_capture()); assert!(ply.is_promotion()); - assert_eq!(ply.promotion(), Some(Shape::Queen)); + assert_eq!(ply.promotion_shape(), Some(Shape::Queen)); Ok(()) } diff --git a/position/src/movement.rs b/position/src/movement.rs index 1d26e4b..712efd1 100644 --- a/position/src/movement.rs +++ b/position/src/movement.rs @@ -4,17 +4,17 @@ //! of squares a piece can move to. For all pieces except pawns, the Movement //! set is equal to the Sight set. -use crate::sight::Sight; +use crate::{sight::Sight, Position}; use chessfriend_bitboard::BitBoard; -use chessfriend_board::Board; -use chessfriend_core::{Color, Piece, Rank, Shape, Square}; +use chessfriend_core::{Color, Piece, Rank, Shape, Square, Wing}; pub trait Movement { - fn movement(&self, square: Square, board: &Board) -> BitBoard; + fn movement(&self, square: Square, position: &Position) -> BitBoard; } impl Movement for Piece { - fn movement(&self, square: Square, board: &Board) -> BitBoard { + fn movement(&self, square: Square, position: &Position) -> BitBoard { + let board = &position.board; let opposing_occupancy = board.opposing_occupancy(self.color); match self.shape { @@ -25,6 +25,25 @@ impl Movement for Piece { let pushes = pawn_pushes(square.into(), self.color, board.occupancy()); sight | pushes } + Shape::King => { + let kingside_target_square = + if position.active_color_can_castle(Wing::KingSide).is_ok() { + let parameters = board.castling_parameters(Wing::KingSide); + parameters.target.king.into() + } else { + BitBoard::empty() + }; + + let queenside_target_square = + if position.active_color_can_castle(Wing::QueenSide).is_ok() { + let parameters = board.castling_parameters(Wing::QueenSide); + parameters.target.king.into() + } else { + BitBoard::empty() + }; + + self.sight(square, board) | kingside_target_square | queenside_target_square + } _ => self.sight(square, board), } } diff --git a/position/src/position/make_move.rs b/position/src/position/make_move.rs index 8c0597c..fcefc24 100644 --- a/position/src/position/make_move.rs +++ b/position/src/position/make_move.rs @@ -81,7 +81,7 @@ impl Position { return self.make_capture_move(ply); } - if let Some(wing) = ply.castle() { + if let Some(wing) = ply.castle_wing() { return self.make_castle_move(wing); } @@ -265,7 +265,7 @@ impl Position { // Pawns can see squares they can't move to. So, calculating valid // squares requires a concept that includes Sight, but adds pawn pushes. // In ChessFriend, that concept is Movement. - let movement = active_piece.movement(origin_square, &self.board); + let movement = active_piece.movement(origin_square, self); if !movement.contains(target_square) { return Err(MakeMoveError::NoMove { piece: active_piece, @@ -408,4 +408,50 @@ mod tests { Ok(()) } + + #[test] + fn make_white_kingside_castle() -> MakeMoveResult { + let mut pos = test_position![ + White Rook on H1, + White King on E1, + ]; + + let ply = Move::castle(Square::E1, Square::G1, Wing::KingSide); + pos.make_move(ply, ValidateMove::Yes)?; + + assert_eq!(pos.board.active_color, Color::Black); + assert_eq!(pos.get_piece(Square::E1), None); + assert_eq!(pos.get_piece(Square::H1), None); + assert_eq!(pos.get_piece(Square::G1), Some(piece!(White King))); + assert_eq!(pos.get_piece(Square::F1), Some(piece!(White Rook))); + assert!(!pos + .board + .castling_rights + .color_has_right(Color::White, Wing::KingSide)); + + Ok(()) + } + + #[test] + fn make_white_queenside_castle() -> MakeMoveResult { + let mut pos = test_position![ + White King on E1, + White Rook on A1, + ]; + + let ply = Move::castle(Square::E1, Square::C1, Wing::QueenSide); + pos.make_move(ply, ValidateMove::Yes)?; + + assert_eq!(pos.board.active_color, Color::Black); + assert_eq!(pos.get_piece(Square::E1), None); + assert_eq!(pos.get_piece(Square::A1), None); + assert_eq!(pos.get_piece(Square::C1), Some(piece!(White King))); + assert_eq!(pos.get_piece(Square::D1), Some(piece!(White Rook))); + assert!(!pos + .board + .castling_rights + .color_has_right(Color::White, Wing::QueenSide)); + + Ok(()) + } } diff --git a/position/src/position/position.rs b/position/src/position/position.rs index 7ea543b..7b8c352 100644 --- a/position/src/position/position.rs +++ b/position/src/position/position.rs @@ -73,7 +73,7 @@ impl Position { pub fn movement(&self, square: Square) -> BitBoard { if let Some(piece) = self.get_piece(square) { - piece.movement(square, &self.board) + piece.movement(square, self) } else { BitBoard::empty() }