movement: thoroughly test unmake with castling
This commit is contained in:
parent
47cc483d9e
commit
53a96f8787
154
src/movement.rs
154
src/movement.rs
|
@ -42,66 +42,105 @@ impl Move {
|
|||
}
|
||||
|
||||
/// Apply this move to `board` and `game_state`.
|
||||
///
|
||||
/// Set automatic queen promotion for pawns, register captured
|
||||
/// pieces and castle options.
|
||||
pub fn apply_to(&mut self, board: &mut Board, game_state: &mut GameState) {
|
||||
// Save current castling options to unmake later.
|
||||
self.old_castles = game_state.castling;
|
||||
// If a king moves, remove it from castling options.
|
||||
if self.source == E1 { game_state.castling &= !CASTLE_WH_MASK; }
|
||||
else if self.source == E8 { game_state.castling &= !CASTLE_BL_MASK; }
|
||||
// Same for rooks.
|
||||
|
||||
let piece = board.get_piece_on(self.source);
|
||||
// Handle king castling.
|
||||
if piece == KING {
|
||||
if let Some(castle) = self.get_castle() {
|
||||
match castle {
|
||||
CASTLE_WH_K => {
|
||||
board.move_square(E1, G1);
|
||||
board.move_square(H1, F1);
|
||||
game_state.castling &= !CASTLE_WH_MASK;
|
||||
}
|
||||
CASTLE_WH_Q => {
|
||||
board.move_square(E1, C1);
|
||||
board.move_square(A1, D1);
|
||||
game_state.castling &= !CASTLE_WH_MASK;
|
||||
}
|
||||
CASTLE_BL_K => {
|
||||
board.move_square(E8, G8);
|
||||
board.move_square(H8, F8);
|
||||
game_state.castling &= !CASTLE_BL_MASK;
|
||||
}
|
||||
CASTLE_BL_Q => {
|
||||
board.move_square(E8, C8);
|
||||
board.move_square(A8, D8);
|
||||
game_state.castling &= !CASTLE_BL_MASK;
|
||||
}
|
||||
_ => { panic!("Invalid castle.") }
|
||||
}
|
||||
game_state.color = opposite(game_state.color);
|
||||
return
|
||||
} else {
|
||||
// If the king moved from starting square, remove it from castling options.
|
||||
if self.source == E1 { game_state.castling &= !CASTLE_WH_MASK; }
|
||||
else if self.source == E8 { game_state.castling &= !CASTLE_BL_MASK; }
|
||||
}
|
||||
}
|
||||
// Record captured piece if any.
|
||||
if !board.is_empty(self.dest) {
|
||||
self.capture = Some(board.get_piece_on(self.dest));
|
||||
}
|
||||
|
||||
// Move the piece.
|
||||
board.move_square(self.source, self.dest);
|
||||
|
||||
// Apply promotion if any.
|
||||
if let Some(piece) = self.promotion {
|
||||
board.set_piece(self.dest, PAWN, piece);
|
||||
}
|
||||
|
||||
// If a rook moved, remove the castle side.
|
||||
if self.source == A1 || self.dest == A1 { game_state.castling &= !CASTLE_WH_Q; }
|
||||
else if self.source == H1 || self.dest == H1 { game_state.castling &= !CASTLE_WH_K; }
|
||||
else if self.source == A8 || self.dest == A8 { game_state.castling &= !CASTLE_BL_Q; }
|
||||
else if self.source == H8 || self.dest == H8 { game_state.castling &= !CASTLE_BL_K; }
|
||||
// Update board and game state.
|
||||
self.apply_to_board(board);
|
||||
game_state.color = opposite(game_state.color);
|
||||
}
|
||||
|
||||
/// Apply the move into `board`.
|
||||
pub fn apply_to_board(&mut self, board: &mut Board) {
|
||||
let piece = board.get_piece_on(self.source);
|
||||
// If a king is castling, apply special move.
|
||||
if piece == KING {
|
||||
if let Some(castle) = self.get_castle() {
|
||||
match castle {
|
||||
CASTLE_WH_K => { board.move_square(E1, G1); board.move_square(H1, F1); }
|
||||
CASTLE_WH_Q => { board.move_square(E1, C1); board.move_square(A1, D1); }
|
||||
CASTLE_BL_K => { board.move_square(E8, G8); board.move_square(H8, F8); }
|
||||
CASTLE_BL_Q => { board.move_square(E8, C8); board.move_square(A8, D8); }
|
||||
_ => { panic!("Invalid castle.") }
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if !board.is_empty(self.dest) {
|
||||
self.capture = Some(board.get_piece_on(self.dest));
|
||||
}
|
||||
board.move_square(self.source, self.dest);
|
||||
if let Some(piece) = self.promotion {
|
||||
board.set_piece(self.dest, PAWN, piece);
|
||||
}
|
||||
// Finally, switch to the opposing player in the game state.
|
||||
game_state.color = opposite(game_state.color);
|
||||
}
|
||||
|
||||
/// Unmake a move.
|
||||
pub fn unmake(&self, board: &mut Board, game_state: &mut GameState) {
|
||||
if let Some(castle) = self.get_castle() {
|
||||
match castle {
|
||||
CASTLE_WH_K => { board.move_square(G1, E1); board.move_square(F1, H1); }
|
||||
CASTLE_WH_Q => { board.move_square(C1, E1); board.move_square(D1, A1); }
|
||||
CASTLE_BL_K => { board.move_square(G8, E8); board.move_square(F8, H8); }
|
||||
CASTLE_BL_Q => { board.move_square(C8, E8); board.move_square(D8, A8); }
|
||||
_ => { panic!("Invalid castle.") }
|
||||
}
|
||||
} else {
|
||||
board.move_square(self.dest, self.source);
|
||||
if let Some(piece) = self.promotion {
|
||||
board.set_piece(self.source, piece, PAWN);
|
||||
}
|
||||
if let Some(piece) = self.capture {
|
||||
board.set_square(self.dest, game_state.color, piece);
|
||||
// Always restore previous castle options.
|
||||
game_state.castling = self.old_castles;
|
||||
// If the move is a castle, unmake it properly.
|
||||
let piece = board.get_piece_on(self.dest);
|
||||
if piece == KING {
|
||||
if let Some(castle) = self.get_castle() {
|
||||
match castle {
|
||||
CASTLE_WH_K => { board.move_square(G1, E1); board.move_square(F1, H1); }
|
||||
CASTLE_WH_Q => { board.move_square(C1, E1); board.move_square(D1, A1); }
|
||||
CASTLE_BL_K => { board.move_square(G8, E8); board.move_square(F8, H8); }
|
||||
CASTLE_BL_Q => { board.move_square(C8, E8); board.move_square(D8, A8); }
|
||||
_ => { panic!("Invalid castle.") }
|
||||
}
|
||||
game_state.color = opposite(game_state.color);
|
||||
return
|
||||
}
|
||||
}
|
||||
game_state.castling = self.old_castles;
|
||||
|
||||
// Move the piece back.
|
||||
board.move_square(self.dest, self.source);
|
||||
|
||||
// Cancel the promotion.
|
||||
if let Some(piece) = self.promotion {
|
||||
board.set_piece(self.source, piece, PAWN);
|
||||
}
|
||||
|
||||
// Restore captured piece.
|
||||
if let Some(piece) = self.capture {
|
||||
board.set_square(self.dest, game_state.color, piece);
|
||||
}
|
||||
|
||||
// And switch back to previous player.
|
||||
game_state.color = opposite(game_state.color);
|
||||
}
|
||||
|
||||
|
@ -178,13 +217,14 @@ mod tests {
|
|||
#[test]
|
||||
fn test_apply_to_board() {
|
||||
let mut b = Board::new_empty();
|
||||
let mut gs = GameState::new();
|
||||
|
||||
// Put 2 enemy knights on board.
|
||||
b.set_square(D4, WHITE, KNIGHT);
|
||||
b.set_square(F4, BLACK, KNIGHT);
|
||||
// Move white knight in a position attacked by black knight.
|
||||
let mut m = Move::new(D4, E6);
|
||||
m.apply_to_board(&mut b);
|
||||
m.apply_to(&mut b, &mut gs);
|
||||
assert!(b.is_empty(D4));
|
||||
assert_eq!(b.get_color_on(E6), WHITE);
|
||||
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
||||
|
@ -192,7 +232,7 @@ mod tests {
|
|||
assert!(m.capture.is_none());
|
||||
// Sack it with black knight
|
||||
let mut m = Move::new(F4, E6);
|
||||
m.apply_to_board(&mut b);
|
||||
m.apply_to(&mut b, &mut gs);
|
||||
assert_eq!(b.get_color_on(E6), BLACK);
|
||||
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
||||
assert_eq!(count_bits(b.combined()), 1);
|
||||
|
@ -242,6 +282,7 @@ mod tests {
|
|||
let mut b = Board::new_empty();
|
||||
let mut gs = GameState::new();
|
||||
|
||||
// On unmaking a move, the white pawn is back to its original square.
|
||||
b.set_square(D4, WHITE, PAWN);
|
||||
let mut m = Move::new(D4, D5);
|
||||
m.apply_to(&mut b, &mut gs);
|
||||
|
@ -249,6 +290,21 @@ mod tests {
|
|||
assert!(b.is_empty(D5));
|
||||
assert_eq!(b.get_color_on(D4), WHITE);
|
||||
assert_eq!(b.get_piece_on(D4), PAWN);
|
||||
|
||||
// Castle options should be properly unmade.
|
||||
b.set_square(E1, WHITE, KING);
|
||||
b.set_square(H1, WHITE, ROOK);
|
||||
let mut m = Move::new(E1, G1);
|
||||
m.apply_to(&mut b, &mut gs);
|
||||
assert!(!b.is_empty(G1));
|
||||
assert!(!b.is_empty(F1));
|
||||
assert_eq!(gs.castling, CASTLE_MASK ^ CASTLE_WH_MASK);
|
||||
m.unmake(&mut b, &mut gs);
|
||||
assert!(b.is_empty(G1));
|
||||
assert!(b.is_empty(F1));
|
||||
assert_eq!(b.get_piece_on(E1), KING);
|
||||
assert_eq!(b.get_piece_on(H1), ROOK);
|
||||
assert_eq!(gs.castling, CASTLE_MASK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
87
src/rules.rs
87
src/rules.rs
|
@ -416,10 +416,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_get_king_moves() {
|
||||
let mut gs = GameState::new();
|
||||
|
||||
// King can move 1 square in any direction.
|
||||
let mut b = Board::new_empty();
|
||||
let mut gs = GameState::new();
|
||||
b.set_square(D4, WHITE, KING);
|
||||
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 8);
|
||||
b.set_square(E5, WHITE, PAWN);
|
||||
|
@ -427,6 +426,7 @@ mod tests {
|
|||
|
||||
// If castling is available, other moves are possible: 5 moves + 2 castles.
|
||||
let mut b = Board::new_empty();
|
||||
let mut gs = GameState::new();
|
||||
b.set_square(E1, WHITE, KING);
|
||||
b.set_square(A1, WHITE, ROOK);
|
||||
b.set_square(H1, WHITE, ROOK);
|
||||
|
@ -440,6 +440,89 @@ mod tests {
|
|||
assert_eq!(get_piece_moves(&mut b, &mut gs, E8, BLACK).len(), 5 + 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_king_castles() {
|
||||
// Rule 1 (king/rook on initial rank) is not checked here.
|
||||
let mut b = Board::new_empty();
|
||||
let mut gs = GameState::new();
|
||||
b.set_square(E1, WHITE, KING);
|
||||
b.set_square(A1, WHITE, ROOK);
|
||||
b.set_square(H1, WHITE, ROOK);
|
||||
b.set_square(E8, BLACK, KING);
|
||||
b.set_square(A8, BLACK, ROOK);
|
||||
b.set_square(H8, BLACK, ROOK);
|
||||
|
||||
// 2. Neither the king nor the chosen rook has previously moved.
|
||||
gs.color = WHITE;
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_WH_Q)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_BL_K)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_BL_Q)));
|
||||
gs.color = BLACK;
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E8, BLACK);
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_Q)));
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_BL_K)));
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_BL_Q)));
|
||||
// Either the king moved and lost all castles...
|
||||
gs.color = WHITE;
|
||||
gs.castling = CASTLE_MASK ^ CASTLE_WH_MASK;
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_Q)));
|
||||
// Or a rook moves and the player loses only one castle.
|
||||
gs.castling = CASTLE_MASK ^ CASTLE_WH_Q;
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_Q)));
|
||||
|
||||
// 3. There are no pieces between the king and the chosen rook.
|
||||
gs.castling = CASTLE_MASK;
|
||||
b.set_square(C1, BLACK, KNIGHT); // Black knight blocking white queen-side castle.
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_Q)));
|
||||
gs.color = BLACK;
|
||||
b.set_square(G8, WHITE, KNIGHT); // White knight blocking black king-side castle.
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E8, BLACK);
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_BL_K)));
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_BL_Q)));
|
||||
|
||||
b.clear_square(C1, BLACK, KNIGHT);
|
||||
b.clear_square(G8, WHITE, KNIGHT);
|
||||
|
||||
// 4. The king is not currently in check.
|
||||
gs.color = WHITE;
|
||||
b.set_square(F2, BLACK, BISHOP); // Check white king.
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_Q)));
|
||||
gs.color = BLACK;
|
||||
b.set_square(H5, WHITE, BISHOP); // Check black king.
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E8, BLACK);
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_BL_K)));
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_BL_Q)));
|
||||
|
||||
b.clear_square(F2, BLACK, BISHOP);
|
||||
b.clear_square(H5, WHITE, BISHOP);
|
||||
|
||||
// 5. The king does not pass through a square that is attacked by an enemy piece.
|
||||
gs.color = WHITE;
|
||||
b.set_square(F8, BLACK, QUEEN); // Black queen protects f-file against king-side castle.
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_WH_Q)));
|
||||
|
||||
b.clear_square(F8, BLACK, QUEEN);
|
||||
|
||||
// 6. The king does not end up in check.
|
||||
b.set_square(G8, BLACK, QUEEN);
|
||||
let moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||
assert!(moves.iter().all(|m| m.get_castle() != Some(CASTLE_WH_K)));
|
||||
assert!(moves.iter().any(|m| m.get_castle() == Some(CASTLE_WH_Q)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_illegal() {
|
||||
let mut b = Board::new_empty();
|
||||
|
|
|
@ -298,6 +298,9 @@ impl Uci {
|
|||
// UCI command parsers
|
||||
|
||||
/// Parse an UCI command.
|
||||
///
|
||||
/// Handle main UCI commands: position, go, etc. The command "p" is an
|
||||
/// alias to "position".
|
||||
fn parse_command(s: &str) -> UciCmd {
|
||||
if s.len() == 0 {
|
||||
return UciCmd::Unknown("Empty command.".to_string());
|
||||
|
@ -309,7 +312,7 @@ fn parse_command(s: &str) -> UciCmd {
|
|||
"isready" => UciCmd::IsReady,
|
||||
"ucinewgame" => UciCmd::UciNewGame,
|
||||
"stop" => UciCmd::Stop,
|
||||
"position" => parse_position_command(&fields[1..]),
|
||||
"position" | "p" => parse_position_command(&fields[1..]),
|
||||
"go" => parse_go_command(&fields[1..]),
|
||||
"quit" => UciCmd::Quit,
|
||||
"vatunode" => UciCmd::VatuNode,
|
||||
|
|
Reference in a new issue