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`.
|
/// 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) {
|
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;
|
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; }
|
let piece = board.get_piece_on(self.source);
|
||||||
else if self.source == E8 { game_state.castling &= !CASTLE_BL_MASK; }
|
// Handle king castling.
|
||||||
// Same for rooks.
|
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; }
|
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 == 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 == A8 || self.dest == A8 { game_state.castling &= !CASTLE_BL_Q; }
|
||||||
else if self.source == H8 || self.dest == H8 { game_state.castling &= !CASTLE_BL_K; }
|
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`.
|
// Finally, switch to the opposing player in the game state.
|
||||||
pub fn apply_to_board(&mut self, board: &mut Board) {
|
game_state.color = opposite(game_state.color);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmake a move.
|
/// Unmake a move.
|
||||||
pub fn unmake(&self, board: &mut Board, game_state: &mut GameState) {
|
pub fn unmake(&self, board: &mut Board, game_state: &mut GameState) {
|
||||||
if let Some(castle) = self.get_castle() {
|
// Always restore previous castle options.
|
||||||
match castle {
|
game_state.castling = self.old_castles;
|
||||||
CASTLE_WH_K => { board.move_square(G1, E1); board.move_square(F1, H1); }
|
// If the move is a castle, unmake it properly.
|
||||||
CASTLE_WH_Q => { board.move_square(C1, E1); board.move_square(D1, A1); }
|
let piece = board.get_piece_on(self.dest);
|
||||||
CASTLE_BL_K => { board.move_square(G8, E8); board.move_square(F8, H8); }
|
if piece == KING {
|
||||||
CASTLE_BL_Q => { board.move_square(C8, E8); board.move_square(D8, A8); }
|
if let Some(castle) = self.get_castle() {
|
||||||
_ => { panic!("Invalid castle.") }
|
match castle {
|
||||||
}
|
CASTLE_WH_K => { board.move_square(G1, E1); board.move_square(F1, H1); }
|
||||||
} else {
|
CASTLE_WH_Q => { board.move_square(C1, E1); board.move_square(D1, A1); }
|
||||||
board.move_square(self.dest, self.source);
|
CASTLE_BL_K => { board.move_square(G8, E8); board.move_square(F8, H8); }
|
||||||
if let Some(piece) = self.promotion {
|
CASTLE_BL_Q => { board.move_square(C8, E8); board.move_square(D8, A8); }
|
||||||
board.set_piece(self.source, piece, PAWN);
|
_ => { panic!("Invalid castle.") }
|
||||||
}
|
}
|
||||||
if let Some(piece) = self.capture {
|
game_state.color = opposite(game_state.color);
|
||||||
board.set_square(self.dest, game_state.color, piece);
|
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);
|
game_state.color = opposite(game_state.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,13 +217,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_to_board() {
|
fn test_apply_to_board() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// Put 2 enemy knights on board.
|
// Put 2 enemy knights on board.
|
||||||
b.set_square(D4, WHITE, KNIGHT);
|
b.set_square(D4, WHITE, KNIGHT);
|
||||||
b.set_square(F4, BLACK, KNIGHT);
|
b.set_square(F4, BLACK, KNIGHT);
|
||||||
// Move white knight in a position attacked by black knight.
|
// Move white knight in a position attacked by black knight.
|
||||||
let mut m = Move::new(D4, E6);
|
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!(b.is_empty(D4));
|
||||||
assert_eq!(b.get_color_on(E6), WHITE);
|
assert_eq!(b.get_color_on(E6), WHITE);
|
||||||
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
||||||
|
@ -192,7 +232,7 @@ mod tests {
|
||||||
assert!(m.capture.is_none());
|
assert!(m.capture.is_none());
|
||||||
// Sack it with black knight
|
// Sack it with black knight
|
||||||
let mut m = Move::new(F4, E6);
|
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_color_on(E6), BLACK);
|
||||||
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
||||||
assert_eq!(count_bits(b.combined()), 1);
|
assert_eq!(count_bits(b.combined()), 1);
|
||||||
|
@ -242,6 +282,7 @@ mod tests {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
let mut gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
|
// On unmaking a move, the white pawn is back to its original square.
|
||||||
b.set_square(D4, WHITE, PAWN);
|
b.set_square(D4, WHITE, PAWN);
|
||||||
let mut m = Move::new(D4, D5);
|
let mut m = Move::new(D4, D5);
|
||||||
m.apply_to(&mut b, &mut gs);
|
m.apply_to(&mut b, &mut gs);
|
||||||
|
@ -249,6 +290,21 @@ mod tests {
|
||||||
assert!(b.is_empty(D5));
|
assert!(b.is_empty(D5));
|
||||||
assert_eq!(b.get_color_on(D4), WHITE);
|
assert_eq!(b.get_color_on(D4), WHITE);
|
||||||
assert_eq!(b.get_piece_on(D4), PAWN);
|
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]
|
#[test]
|
||||||
|
|
87
src/rules.rs
87
src/rules.rs
|
@ -416,10 +416,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_king_moves() {
|
fn test_get_king_moves() {
|
||||||
let mut gs = GameState::new();
|
|
||||||
|
|
||||||
// King can move 1 square in any direction.
|
// King can move 1 square in any direction.
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
b.set_square(D4, WHITE, KING);
|
b.set_square(D4, WHITE, KING);
|
||||||
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 8);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 8);
|
||||||
b.set_square(E5, WHITE, PAWN);
|
b.set_square(E5, WHITE, PAWN);
|
||||||
|
@ -427,6 +426,7 @@ mod tests {
|
||||||
|
|
||||||
// If castling is available, other moves are possible: 5 moves + 2 castles.
|
// If castling is available, other moves are possible: 5 moves + 2 castles.
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
b.set_square(E1, WHITE, KING);
|
b.set_square(E1, WHITE, KING);
|
||||||
b.set_square(A1, WHITE, ROOK);
|
b.set_square(A1, WHITE, ROOK);
|
||||||
b.set_square(H1, 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);
|
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]
|
#[test]
|
||||||
fn test_is_illegal() {
|
fn test_is_illegal() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
|
|
|
@ -298,6 +298,9 @@ impl Uci {
|
||||||
// UCI command parsers
|
// UCI command parsers
|
||||||
|
|
||||||
/// Parse an UCI command.
|
/// 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 {
|
fn parse_command(s: &str) -> UciCmd {
|
||||||
if s.len() == 0 {
|
if s.len() == 0 {
|
||||||
return UciCmd::Unknown("Empty command.".to_string());
|
return UciCmd::Unknown("Empty command.".to_string());
|
||||||
|
@ -309,7 +312,7 @@ fn parse_command(s: &str) -> UciCmd {
|
||||||
"isready" => UciCmd::IsReady,
|
"isready" => UciCmd::IsReady,
|
||||||
"ucinewgame" => UciCmd::UciNewGame,
|
"ucinewgame" => UciCmd::UciNewGame,
|
||||||
"stop" => UciCmd::Stop,
|
"stop" => UciCmd::Stop,
|
||||||
"position" => parse_position_command(&fields[1..]),
|
"position" | "p" => parse_position_command(&fields[1..]),
|
||||||
"go" => parse_go_command(&fields[1..]),
|
"go" => parse_go_command(&fields[1..]),
|
||||||
"quit" => UciCmd::Quit,
|
"quit" => UciCmd::Quit,
|
||||||
"vatunode" => UciCmd::VatuNode,
|
"vatunode" => UciCmd::VatuNode,
|
||||||
|
|
Reference in a new issue