[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.
This commit is contained in:
Eryn Wells 2025-05-21 09:51:16 -07:00
parent 10ba21f7e3
commit 9a4fa827f9

View file

@ -1,8 +1,8 @@
// Eryn Wells <eryn@erynwells.me> // Eryn Wells <eryn@erynwells.me>
use crate::{movement::Movement, Position}; use crate::{movement::Movement, Position};
use chessfriend_board::{en_passant, CastleParameters, PlacePieceError, PlacePieceStrategy}; use chessfriend_board::{PlacePieceError, PlacePieceStrategy};
use chessfriend_core::{Color, Piece, Rank, Shape, Square, Wing}; use chessfriend_core::{Color, Piece, Rank, Square, Wing};
use chessfriend_moves::Move; use chessfriend_moves::Move;
use thiserror::Error; use thiserror::Error;
@ -55,6 +55,9 @@ pub enum MakeMoveError {
#[error("cannot promote on {0}")] #[error("cannot promote on {0}")]
InvalidPromotion(Square), InvalidPromotion(Square),
#[error("move to {0} requires promotion")]
PromotionRequired(Square),
} }
pub enum UnmakeMoveError {} pub enum UnmakeMoveError {}
@ -70,7 +73,7 @@ impl Position {
self.validate_move(ply, validate)?; self.validate_move(ply, validate)?;
if ply.is_quiet() { 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() { if ply.is_double_push() {
@ -98,13 +101,17 @@ impl Position {
} }
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 let piece = self
.board .get_piece(origin)
.remove_piece(origin)
.ok_or(MakeMoveError::NoPiece(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); self.advance_clocks(HalfMoveClock::Advance);
@ -119,7 +126,7 @@ impl Position {
.ok_or(MakeMoveError::NoPiece(origin))?; .ok_or(MakeMoveError::NoPiece(origin))?;
let target = ply.target_square(); 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() { self.board.en_passant_target = match target.rank() {
Rank::FOUR => Some(Square::from_file_rank(target.file(), Rank::THREE)), 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)) 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) self.place_piece(piece, square, PlacePieceStrategy::PreserveExisting)
.map_err(MakeMoveError::PlacePieceError) .map_err(MakeMoveError::PlacePieceError)
} }
@ -340,6 +351,20 @@ mod tests {
Ok(()) 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] #[test]
fn make_capture_move() { fn make_capture_move() {
let mut pos = test_position![ let mut pos = test_position![
@ -391,6 +416,22 @@ mod tests {
Ok(()) 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] #[test]
fn make_promotion_move() -> MakeMoveResult { fn make_promotion_move() -> MakeMoveResult {
let mut pos = test_position![ let mut pos = test_position![