From 9a4fa827f987ea56041fc1afaa3172a4e89fcc52 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 21 May 2025 09:51:16 -0700 Subject: [PATCH] [position] Add two new negative tests for making pawn moves - Ensure you cannot move a pawn to the last rank without a promotion move. - Ensure a pawn cannot make an illegal move, and that the board state remains as it was before the move was attempted. --- position/src/position/make_move.rs | 59 +++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/position/src/position/make_move.rs b/position/src/position/make_move.rs index fcefc24..e8502e9 100644 --- a/position/src/position/make_move.rs +++ b/position/src/position/make_move.rs @@ -1,8 +1,8 @@ // Eryn Wells use crate::{movement::Movement, Position}; -use chessfriend_board::{en_passant, CastleParameters, PlacePieceError, PlacePieceStrategy}; -use chessfriend_core::{Color, Piece, Rank, Shape, Square, Wing}; +use chessfriend_board::{PlacePieceError, PlacePieceStrategy}; +use chessfriend_core::{Color, Piece, Rank, Square, Wing}; use chessfriend_moves::Move; use thiserror::Error; @@ -55,6 +55,9 @@ pub enum MakeMoveError { #[error("cannot promote on {0}")] InvalidPromotion(Square), + + #[error("move to {0} requires promotion")] + PromotionRequired(Square), } pub enum UnmakeMoveError {} @@ -70,7 +73,7 @@ impl Position { self.validate_move(ply, validate)?; if ply.is_quiet() { - return self.make_quiet_move(ply.origin_square(), ply.target_square()); + return self.make_quiet_move(ply); } if ply.is_double_push() { @@ -98,13 +101,17 @@ impl Position { } impl Position { - fn make_quiet_move(&mut self, origin: Square, target: Square) -> MakeMoveResult { + fn make_quiet_move(&mut self, ply: Move) -> MakeMoveResult { + let origin = ply.origin_square(); + let piece = self - .board - .remove_piece(origin) + .get_piece(origin) .ok_or(MakeMoveError::NoPiece(origin))?; - self.place_active_piece(piece, target)?; + let target = ply.target_square(); + self.place_piece_for_move(piece, target)?; + + self.remove_piece(origin); self.advance_clocks(HalfMoveClock::Advance); @@ -119,7 +126,7 @@ impl Position { .ok_or(MakeMoveError::NoPiece(origin))?; let target = ply.target_square(); - self.place_active_piece(piece, target)?; + self.place_piece_for_move(piece, target)?; self.board.en_passant_target = match target.rank() { Rank::FOUR => Some(Square::from_file_rank(target.file(), Rank::THREE)), @@ -223,7 +230,11 @@ impl Position { self.get_piece(square).ok_or(MakeMoveError::NoPiece(square)) } - fn place_active_piece(&mut self, piece: Piece, square: Square) -> MakeMoveResult { + fn place_piece_for_move(&mut self, piece: Piece, square: Square) -> MakeMoveResult { + if piece.is_pawn() && square.rank().is_promotable_rank() { + return Err(MakeMoveError::PromotionRequired(square)); + } + self.place_piece(piece, square, PlacePieceStrategy::PreserveExisting) .map_err(MakeMoveError::PlacePieceError) } @@ -340,6 +351,20 @@ mod tests { Ok(()) } + #[test] + fn make_invalid_quiet_pawn_move() { + let mut pos = test_position!(White Pawn on C2); + + let ply = Move::quiet(Square::C2, Square::D2); + let result = pos.make_move(ply, ValidateMove::Yes); + + assert!(result.is_err()); + assert_eq!(pos.get_piece(Square::C2), Some(piece!(White Pawn))); + assert_eq!(pos.get_piece(Square::D2), None); + assert_eq!(pos.board.active_color, Color::White); + assert_eq!(pos.board.half_move_clock, 0); + } + #[test] fn make_capture_move() { let mut pos = test_position![ @@ -391,6 +416,22 @@ mod tests { Ok(()) } + #[test] + fn make_last_rank_quiet_move_without_promotion() { + let mut pos = test_position!( + White Pawn on A7 + ); + + let ply = Move::quiet(Square::A7, Square::A8); + let result = pos.make_move(ply, ValidateMove::Yes); + + assert!(result.is_err()); + assert_eq!(pos.board.active_color, Color::White); + assert_eq!(pos.get_piece(Square::A7), Some(piece!(White Pawn))); + assert_eq!(pos.get_piece(Square::A8), None); + assert_eq!(pos.board.half_move_clock, 0); + } + #[test] fn make_promotion_move() -> MakeMoveResult { let mut pos = test_position![