rules: rework illegal moves detection

This commit is contained in:
dece 2020-06-24 22:19:09 +02:00
parent 54cfd43911
commit 47cc483d9e
6 changed files with 150 additions and 117 deletions

View file

@ -119,7 +119,7 @@ impl Analyzer {
} else { } else {
// If no best move could be found, checkmate is unavoidable; send the first legal move. // If no best move could be found, checkmate is unavoidable; send the first legal move.
self.log("Checkmate is unavoidable.".to_string()); self.log("Checkmate is unavoidable.".to_string());
let moves = rules::get_player_moves(&self.node.board, &self.node.game_state); let moves = rules::get_player_moves(&mut self.node.board, &mut self.node.game_state);
let m = if moves.len() > 0 { Some(moves[0].clone()) } else { None }; let m = if moves.len() > 0 { Some(moves[0].clone()) } else { None };
self.report_best_move(m); self.report_best_move(m);
} }

View file

@ -163,6 +163,7 @@ impl Engine {
_ => {} _ => {}
}; };
// Castling. // Castling.
self.node.game_state.castling = 0;
for c in fen.castling.chars() { for c in fen.castling.chars() {
match c { match c {
'K' => self.node.game_state.castling |= castling::CASTLE_WH_K, 'K' => self.node.game_state.castling |= castling::CASTLE_WH_K,

View file

@ -95,10 +95,10 @@ impl Move {
} else { } else {
board.move_square(self.dest, self.source); board.move_square(self.dest, self.source);
if let Some(piece) = self.promotion { if let Some(piece) = self.promotion {
board.set_piece(self.source, piece, PAWN) board.set_piece(self.source, piece, PAWN);
} }
if let Some(piece) = self.capture { if let Some(piece) = self.capture {
board.set_square(self.dest, opposite(game_state.color), piece); board.set_square(self.dest, game_state.color, piece);
} }
} }
game_state.castling = self.old_castles; game_state.castling = self.old_castles;

View file

@ -33,13 +33,13 @@ impl Node {
} }
/// Return player moves from this node. /// Return player moves from this node.
pub fn get_player_moves(&self) -> Vec<Move> { pub fn get_player_moves(&mut self) -> Vec<Move> {
rules::get_player_moves(&self.board, &self.game_state) rules::get_player_moves(&mut self.board, &mut self.game_state)
} }
/// Compute stats for both players for this node. /// Compute stats for both players for this node.
pub fn compute_stats(&self) -> (stats::BoardStats, stats::BoardStats) { pub fn compute_stats(&mut self) -> (stats::BoardStats, stats::BoardStats) {
stats::BoardStats::new_from(&self.board, &self.game_state) stats::BoardStats::new_from(&mut self.board, &mut self.game_state)
} }
} }

View file

@ -57,10 +57,9 @@ impl std::fmt::Display for GameState {
/// `pseudo_legal` as a collection of attacked squares instead of legal /// `pseudo_legal` as a collection of attacked squares instead of legal
/// move collection. /// move collection.
pub fn get_player_moves( pub fn get_player_moves(
board: &Board, board: &mut Board,
game_state: &GameState, game_state: &mut GameState,
) -> Vec<Move> { ) -> Vec<Move> {
let attacked_bb = board.get_full_rays(opposite(game_state.color)); // FIXME remove, doesn't w
let mut moves = Vec::with_capacity(32); let mut moves = Vec::with_capacity(32);
for r in 0..8 { for r in 0..8 {
for f in 0..8 { for f in 0..8 {
@ -70,7 +69,7 @@ pub fn get_player_moves(
} }
if board.get_color_on(square) == game_state.color { if board.get_color_on(square) == game_state.color {
moves.append( moves.append(
&mut get_piece_moves(board, game_state, square, game_state.color, attacked_bb) &mut get_piece_moves(board, game_state, square, game_state.color)
); );
} }
} }
@ -85,11 +84,10 @@ pub fn get_player_moves(
/// board but that would require an additional lookup and this function /// board but that would require an additional lookup and this function
/// is always called in a context where the piece color is known. /// is always called in a context where the piece color is known.
fn get_piece_moves( fn get_piece_moves(
board: &Board, board: &mut Board,
game_state: &GameState, game_state: &mut GameState,
square: Square, square: Square,
color: Color, color: Color,
attacked_bb: Bitboard,
) -> Vec<Move> { ) -> Vec<Move> {
let piece = board.get_piece_on(square); let piece = board.get_piece_on(square);
let mut moves = Vec::with_capacity(32); let mut moves = Vec::with_capacity(32);
@ -111,11 +109,10 @@ fn get_piece_moves(
square, square,
color, color,
piece, piece,
attacked_bb,
&mut moves &mut moves
); );
if piece == KING && sq_rank(square) == CASTLE_RANK_BY_COLOR[color] { if piece == KING && sq_rank(square) == CASTLE_RANK_BY_COLOR[color] {
get_king_castles(board, game_state, square, color, attacked_bb, &mut moves); get_king_castles(board, game_state, square, color, &mut moves);
} }
moves moves
} }
@ -126,20 +123,19 @@ fn get_piece_moves(
/// legal move. Does not take castle into account. Pawns that reach /// legal move. Does not take castle into account. Pawns that reach
/// the last rank are promoted as queens. /// the last rank are promoted as queens.
fn get_moves_from_bb( fn get_moves_from_bb(
board: &Board, board: &mut Board,
game_state: &GameState, game_state: &mut GameState,
bitboard: Bitboard, bitboard: Bitboard,
square: Square, square: Square,
color: Color, color: Color,
piece: Piece, piece: Piece,
attacked_bb: Bitboard,
moves: &mut Vec<Move> moves: &mut Vec<Move>
) { ) {
for ray_square in 0..NUM_SQUARES { for ray_square in 0..NUM_SQUARES {
if ray_square == square || bitboard & bit_pos(ray_square) == 0 { if ray_square == square || bitboard & bit_pos(ray_square) == 0 {
continue continue
} }
if let Some(mut m) = inspect_move(board, game_state, square, ray_square, attacked_bb) { if let Some(mut m) = inspect_move(board, game_state, square, ray_square) {
// Automatic queen promotion for pawns moving to the opposite rank. // Automatic queen promotion for pawns moving to the opposite rank.
if if
piece == PAWN piece == PAWN
@ -161,16 +157,21 @@ fn get_moves_from_bb(
/// `ray_square` is either empty or an enemy piece, but not a friend /// `ray_square` is either empty or an enemy piece, but not a friend
/// piece: they should have been filtered. /// piece: they should have been filtered.
/// ///
/// This function, in case a move is accepted, sets the `capture` field
/// if the target square hold a piece.
///
/// This function does not set promotions for pawns reaching last rank. /// This function does not set promotions for pawns reaching last rank.
fn inspect_move( fn inspect_move(
board: &Board, board: &mut Board,
game_state: &GameState, game_state: &mut GameState,
square: Square, square: Square,
ray_square: Square, ray_square: Square,
attacked_bb: Bitboard
) -> Option<Move> { ) -> Option<Move> {
let m = Move::new(square, ray_square); let mut m = Move::new(square, ray_square);
if !is_illegal(board, game_state, &m, attacked_bb) { if !is_illegal(board, game_state, &mut m) {
if !board.is_empty(ray_square) {
m.capture = Some(board.get_piece_on(ray_square))
}
Some(m) Some(m)
} else { } else {
None None
@ -179,20 +180,21 @@ fn inspect_move(
/// Check if a move is illegal. /// Check if a move is illegal.
fn is_illegal( fn is_illegal(
board: &Board, board: &mut Board,
game_state: &GameState, game_state: &mut GameState,
m: &Move, m: &mut Move,
attacked_bb: Bitboard
) -> bool { ) -> bool {
let color = game_state.color;
// A move is illegal if the king ends up in check. // A move is illegal if the king ends up in check.
let king_square = if board.get_piece_on(m.source) == KING { m.apply_to(board, game_state);
m.dest if let Some(king) = board.find_king(color) {
let attacked_bb = board.get_full_rays(opposite(color));
m.unmake(board, game_state);
attacked_bb & bit_pos(king) != 0
} else { } else {
if let Some(king) = board.find_king(game_state.color) { king } else { return false } m.unmake(board, game_state);
}; false
// FIXME implement unmake move }
let attacked_bb = board.get_full_rays(opposite(game_state.color));
attacked_bb & bit_pos(king_square) != 0
} }
/// Get possible castles. /// Get possible castles.
@ -212,7 +214,6 @@ fn get_king_castles(
game_state: &GameState, game_state: &GameState,
square: Square, square: Square,
color: Color, color: Color,
attacked_bb: Bitboard,
moves: &mut Vec<Move> moves: &mut Vec<Move>
) { ) {
let combined_bb = board.combined(); let combined_bb = board.combined();
@ -230,6 +231,7 @@ fn get_king_castles(
if (game_state.castling & castle_color_mask & castle_side_mask) != 0 { if (game_state.castling & castle_color_mask & castle_side_mask) != 0 {
// Check that squares in the king's path are not attacked (R4, R5, R6). // Check that squares in the king's path are not attacked (R4, R5, R6).
let castle_legality_path = CASTLE_LEGALITY_PATHS[color][castle_side_id]; let castle_legality_path = CASTLE_LEGALITY_PATHS[color][castle_side_id];
let attacked_bb = board.get_full_rays(opposite(game_state.color));
if attacked_bb & castle_legality_path != 0 { if attacked_bb & castle_legality_path != 0 {
continue continue
} }
@ -250,156 +252,166 @@ fn get_king_castles(
mod tests { mod tests {
use super::*; use super::*;
/// Like `get_piece_moves` but generate attacked bitboard.
fn get_legal_piece_moves(
board: &Board,
game_state: &GameState,
square: Square,
color: Color
) -> Vec<Move> {
let attacked_bb = board.get_full_rays(opposite(game_state.color));
get_piece_moves(board, game_state, square, color, attacked_bb)
}
#[test] #[test]
fn test_get_player_moves() { fn test_get_player_moves() {
let b = Board::new(); let mut b = Board::new();
let gs = GameState::new(); let mut gs = GameState::new();
// At first move, white has 16 pawn moves and 4 knight moves. // At first move, white has 16 pawn moves and 4 knight moves.
let moves = get_player_moves(&b, &gs); let moves = get_player_moves(&mut b, &mut gs);
assert_eq!(moves.len(), 20); assert_eq!(moves.len(), 20);
} }
#[test] #[test]
fn test_get_pawn_moves() { fn test_get_pawn_progress_moves() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
let gs = GameState::new(); let mut gs = GameState::new();
// Check that a pawn (here white queen's pawn) can move forward if the road is free. // Check that a pawn (here white queen's pawn) can move forward if the road is free.
b.set_square(D3, WHITE, PAWN); b.set_square(D3, WHITE, PAWN);
let moves = get_legal_piece_moves(&b, &gs, D3, WHITE); let moves = get_piece_moves(&mut b, &mut gs, D3, WHITE);
assert_eq!(moves.len(), 1); assert_eq!(moves.len(), 1);
assert!(moves.contains(&Move::new(D3, D4))); assert!(moves.iter().any(|m| m.source == D3 && m.dest == D4));
// Check that a pawn (here white king's pawn) can move 2 square forward on first move. // Check that a pawn (here white king's pawn) can move 2 square forward on first move.
b.set_square(E2, WHITE, PAWN); b.set_square(E2, WHITE, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
assert_eq!(moves.len(), 2); assert_eq!(moves.len(), 2);
assert!(moves.contains(&Move::new(E2, E3))); assert!(moves.iter().any(|m| m.source == E2 && m.dest == E3));
assert!(moves.contains(&Move::new(E2, E4))); assert!(moves.iter().any(|m| m.source == E2 && m.dest == E4));
// Check that a pawn cannot move forward if a piece is blocking its path. // Check that a pawn cannot move forward if a piece is blocking its path.
// 1. black pawn 2 square forward; only 1 square forward available from start pos. // 1. black pawn 2 square forward; only 1 square forward available from start pos.
b.set_square(E4, BLACK, PAWN); b.set_square(E4, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
assert_eq!(moves.len(), 1); assert_eq!(moves.len(), 1);
assert!(moves.contains(&Move::new(E2, E3))); assert!(moves.iter().any(|m| m.source == E2 && m.dest == E3));
// 2. black pawn 1 square forward; no square available. // 2. black pawn 1 square forward; no square available.
b.set_square(E3, BLACK, PAWN); b.set_square(E3, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
assert_eq!(moves.len(), 0); assert_eq!(moves.len(), 0);
// 3. remove the e4 black pawn; the white pawn should not be able to jump above e3 pawn. // 3. remove the e4 black pawn; the white pawn should not be able to jump above e3 pawn.
b.clear_square(E4, BLACK, PAWN); b.clear_square(E4, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
assert_eq!(moves.len(), 0); assert_eq!(moves.len(), 0);
}
#[test]
fn test_get_pawn_capture_moves() {
let mut b = Board::new_empty();
let mut gs = GameState::new();
// Check that a pawn can take a piece diagonally. // Check that a pawn can take a piece diagonally.
b.set_square(F3, BLACK, PAWN); b.set_square(E2, WHITE, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
assert_eq!(moves.len(), 1);
b.set_square(D3, BLACK, PAWN);
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE);
assert_eq!(moves.len(), 2); assert_eq!(moves.len(), 2);
b.set_square(F3, BLACK, PAWN);
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
assert_eq!(moves.len(), 3);
assert!(moves.iter().any(|m| m.source == E2 && m.dest == F3));
b.set_square(D3, BLACK, PAWN);
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
assert_eq!(moves.len(), 4);
assert!(moves.iter().any(|m| m.source == E2 && m.dest == F3));
assert!(moves.iter().any(|m| m.source == E2 && m.dest == D3));
}
#[test]
fn test_get_pawn_promotion_moves() {
let mut b = Board::new_empty();
let mut gs = GameState::new();
// Check that a pawn moving to the last rank leads to queen promotion. // Check that a pawn moving to the last rank leads to queen promotion.
// 1. by simply moving forward. // 1. by simply moving forward.
b.set_square(A7, WHITE, PAWN); b.set_square(A7, WHITE, PAWN);
let moves = get_legal_piece_moves(&b, &gs, A7, WHITE); let moves = get_piece_moves(&mut b, &mut gs, A7, WHITE);
assert_eq!(moves.len(), 1); assert_eq!(moves.len(), 1);
assert!(moves.contains(&Move::new_promotion(A7, A8, QUEEN))); let m = &moves[0];
assert_eq!(m.source, A7);
assert_eq!(m.dest, A8);
assert_eq!(m.promotion, Some(QUEEN));
} }
#[test] #[test]
fn test_get_bishop_moves() { fn test_get_bishop_moves() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
let gs = GameState::new(); let mut gs = GameState::new();
// A bishop has maximum range when it's in a center square. // A bishop has maximum range when it's in a center square.
b.set_square(D4, WHITE, BISHOP); b.set_square(D4, WHITE, BISHOP);
let moves = get_legal_piece_moves(&b, &gs, D4, WHITE); let moves = get_piece_moves(&mut b, &mut gs, D4, WHITE);
assert_eq!(moves.len(), 13); assert_eq!(moves.len(), 13);
// Going top-right. // Going top-right.
assert!(moves.contains(&Move::new(D4, E5))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == E5));
assert!(moves.contains(&Move::new(D4, F6))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == F6));
assert!(moves.contains(&Move::new(D4, G7))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == G7));
assert!(moves.contains(&Move::new(D4, H8))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == H8));
// Going bottom-right. // Going bottom-right.
assert!(moves.contains(&Move::new(D4, E3))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == E3));
assert!(moves.contains(&Move::new(D4, F2))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == F2));
assert!(moves.contains(&Move::new(D4, G1))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == G1));
// Going bottom-left. // Going bottom-left.
assert!(moves.contains(&Move::new(D4, C3))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == C3));
assert!(moves.contains(&Move::new(D4, B2))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == B2));
assert!(moves.contains(&Move::new(D4, A1))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == A1));
// Going top-left. // Going top-left.
assert!(moves.contains(&Move::new(D4, C5))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == C5));
assert!(moves.contains(&Move::new(D4, B6))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == B6));
assert!(moves.contains(&Move::new(D4, A7))); assert!(moves.iter().any(|m| m.source == D4 && m.dest == A7));
// When blocking commit to one square with friendly piece, lose 2 moves. // When blocking commit to one square with friendly piece, lose 2 moves.
b.set_square(B2, WHITE, PAWN); b.set_square(B2, WHITE, PAWN);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 11); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 11);
// When blocking commit to one square with enemy piece, lose only 1 move. // When blocking commit to one square with enemy piece, lose only 1 move.
b.set_square(B2, BLACK, PAWN); b.set_square(B2, BLACK, PAWN);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 12); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 12);
} }
#[test] #[test]
fn test_get_knight_moves() { fn test_get_knight_moves() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
let gs = GameState::new(); let mut gs = GameState::new();
// A knight never has blocked commit; if it's in the center of the board, it can have up to // A knight never has blocked commit; if it's in the center of the board, it can have up to
// 8 moves. // 8 moves.
b.set_square(D4, WHITE, KNIGHT); b.set_square(D4, WHITE, KNIGHT);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 8); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 8);
// If on a side if has only 4 moves. // If on a side if has only 4 moves.
b.set_square(A4, WHITE, KNIGHT); b.set_square(A4, WHITE, KNIGHT);
assert_eq!(get_legal_piece_moves(&b, &gs, A4, WHITE).len(), 4); assert_eq!(get_piece_moves(&mut b, &mut gs, A4, WHITE).len(), 4);
// And in a corner, only 2 moves. // And in a corner, only 2 moves.
b.set_square(A1, WHITE, KNIGHT); b.set_square(A1, WHITE, KNIGHT);
assert_eq!(get_legal_piece_moves(&b, &gs, A1, WHITE).len(), 2); assert_eq!(get_piece_moves(&mut b, &mut gs, A1, WHITE).len(), 2);
// Add 2 friendly pieces and it is totally blocked. // Add 2 friendly pieces and it is totally blocked.
b.set_square(B3, WHITE, PAWN); b.set_square(B3, WHITE, PAWN);
b.set_square(C2, WHITE, PAWN); b.set_square(C2, WHITE, PAWN);
assert_eq!(get_legal_piece_moves(&b, &gs, A1, WHITE).len(), 0); assert_eq!(get_piece_moves(&mut b, &mut gs, A1, WHITE).len(), 0);
} }
#[test] #[test]
fn test_get_rook_moves() { fn test_get_rook_moves() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
let gs = GameState::new(); let mut gs = GameState::new();
b.set_square(D4, WHITE, ROOK); b.set_square(D4, WHITE, ROOK);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 14); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 14);
b.set_square(D6, BLACK, PAWN); b.set_square(D6, BLACK, PAWN);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 12); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 12);
b.set_square(D6, WHITE, PAWN); b.set_square(D6, WHITE, PAWN);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 11); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 11);
} }
#[test] #[test]
fn test_get_queen_moves() { fn test_get_queen_moves() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
let gs = GameState::new(); let mut gs = GameState::new();
b.set_square(D4, WHITE, QUEEN); b.set_square(D4, WHITE, QUEEN);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 14 + 13); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 14 + 13);
} }
#[test] #[test]
@ -409,27 +421,47 @@ mod tests {
// 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();
b.set_square(D4, WHITE, KING); b.set_square(D4, WHITE, KING);
assert_eq!(get_legal_piece_moves(&b, &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);
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 7); assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 7);
// 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();
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);
assert_eq!(get_legal_piece_moves(&b, &gs, E1, WHITE).len(), 5 + 2); assert_eq!(get_piece_moves(&mut b, &mut gs, E1, WHITE).len(), 5 + 2);
// Castling works as well for black. // Castling works as well for black.
gs.color = BLACK; gs.color = BLACK;
b.set_square(E8, BLACK, KING); b.set_square(E8, BLACK, KING);
b.set_square(A8, BLACK, ROOK); b.set_square(A8, BLACK, ROOK);
b.set_square(H8, BLACK, ROOK); b.set_square(H8, BLACK, ROOK);
assert_eq!(get_legal_piece_moves(&b, &gs, E8, BLACK).len(), 5 + 2); assert_eq!(get_piece_moves(&mut b, &mut gs, E8, BLACK).len(), 5 + 2);
} }
#[test] #[test]
fn test_filter_illegal_moves() { fn test_is_illegal() {
let mut b = Board::new_empty();
let mut gs = GameState::new();
gs.castling = 0;
// Place white's king on first rank.
b.set_square(E1, WHITE, KING);
// Place black rook in second rank: king can only move left or right.
b.set_square(H2, BLACK, ROOK);
// Check that the king can't go to a rook controlled square.
assert!(is_illegal(&mut b, &mut gs, &mut Move::new(E1, E2)));
assert!(is_illegal(&mut b, &mut gs, &mut Move::new(E1, D2)));
assert!(is_illegal(&mut b, &mut gs, &mut Move::new(E1, F2)));
assert!(!is_illegal(&mut b, &mut gs, &mut Move::new(E1, D1)));
assert!(!is_illegal(&mut b, &mut gs, &mut Move::new(E1, F1)));
let all_wh_moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
assert_eq!(all_wh_moves.len(), 2);
}
#[test]
fn test_get_king_moves_legality() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
let mut gs = GameState::new(); let mut gs = GameState::new();
@ -440,7 +472,7 @@ mod tests {
// No castling available. // No castling available.
gs.castling = 0; gs.castling = 0;
// 5 moves in absolute but only 2 are legal. // 5 moves in absolute but only 2 are legal.
let all_wh_moves = get_legal_piece_moves(&b, &gs, E1, WHITE); let all_wh_moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
assert_eq!(all_wh_moves.len(), 2); assert_eq!(all_wh_moves.len(), 2);
} }
} }

View file

@ -31,12 +31,12 @@ impl BoardStats {
/// ///
/// The playing color will have its stats filled in the first /// The playing color will have its stats filled in the first
/// BoardStats object, its opponent in the second. /// BoardStats object, its opponent in the second.
pub fn new_from(board: &Board, game_state: &GameState) -> (BoardStats, BoardStats) { pub fn new_from(board: &mut Board, game_state: &mut GameState) -> (BoardStats, BoardStats) {
let mut stats = (BoardStats::new(), BoardStats::new()); let mut stats = (BoardStats::new(), BoardStats::new());
let mut gs = game_state.clone(); let mut gs = game_state.clone();
stats.0.compute(board, &gs); stats.0.compute(board, &mut gs);
gs.color = opposite(gs.color); gs.color = opposite(gs.color);
stats.1.compute(board, &gs); stats.1.compute(board, &mut gs);
stats stats
} }
@ -58,7 +58,7 @@ impl BoardStats {
/// ///
/// Only the current playing side stats are created, /// Only the current playing side stats are created,
/// prepare the game_state accordingly. /// prepare the game_state accordingly.
pub fn compute(&mut self, board: &Board, game_state: &GameState) { pub fn compute(&mut self, board: &mut Board, game_state: &mut GameState) {
self.reset(); self.reset();
let color = game_state.color; let color = game_state.color;
// Compute mobility for all pieces. // Compute mobility for all pieces.
@ -157,8 +157,8 @@ mod tests {
#[test] #[test]
fn test_compute_stats() { fn test_compute_stats() {
// Check that initial stats are correct. // Check that initial stats are correct.
let b = Board::new(); let mut b = Board::new();
let gs = GameState::new(); let mut gs = GameState::new();
let initial_stats = BoardStats { let initial_stats = BoardStats {
num_pawns: 8, num_pawns: 8,
num_bishops: 2, num_bishops: 2,
@ -171,7 +171,7 @@ mod tests {
num_isolated_pawns: 0, num_isolated_pawns: 0,
mobility: 20, mobility: 20,
}; };
let mut stats = BoardStats::new_from(&b, &gs); let mut stats = BoardStats::new_from(&mut b, &mut gs);
assert!(stats.0 == stats.1); assert!(stats.0 == stats.1);
assert!(stats.0 == initial_stats); assert!(stats.0 == initial_stats);
@ -179,15 +179,15 @@ mod tests {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
b.set_square(D4, WHITE, PAWN); b.set_square(D4, WHITE, PAWN);
b.set_square(D6, WHITE, PAWN); b.set_square(D6, WHITE, PAWN);
stats.0.compute(&b, &gs); stats.0.compute(&mut b, &mut gs);
assert_eq!(stats.0.num_doubled_pawns, 2); assert_eq!(stats.0.num_doubled_pawns, 2);
// Add a pawn on another file, no changes expected. // Add a pawn on another file, no changes expected.
b.set_square(E6, WHITE, PAWN); b.set_square(E6, WHITE, PAWN);
stats.0.compute(&b, &gs); stats.0.compute(&mut b, &mut gs);
assert_eq!(stats.0.num_doubled_pawns, 2); assert_eq!(stats.0.num_doubled_pawns, 2);
// Add a pawn backward in the d-file: there are now 3 doubled pawns. // Add a pawn backward in the d-file: there are now 3 doubled pawns.
b.set_square(D2, WHITE, PAWN); b.set_square(D2, WHITE, PAWN);
stats.0.compute(&b, &gs); stats.0.compute(&mut b, &mut gs);
assert_eq!(stats.0.num_doubled_pawns, 3); assert_eq!(stats.0.num_doubled_pawns, 3);
// Check that isolated and backward pawns are correctly counted. // Check that isolated and backward pawns are correctly counted.
@ -195,19 +195,19 @@ mod tests {
assert_eq!(stats.0.num_backward_pawns, 2); // A bit weird? assert_eq!(stats.0.num_backward_pawns, 2); // A bit weird?
// Protect d4 pawn with a friend in e3: it is not isolated nor backward anymore. // Protect d4 pawn with a friend in e3: it is not isolated nor backward anymore.
b.set_square(E3, WHITE, PAWN); b.set_square(E3, WHITE, PAWN);
stats.0.compute(&b, &gs); stats.0.compute(&mut b, &mut gs);
assert_eq!(stats.0.num_doubled_pawns, 5); assert_eq!(stats.0.num_doubled_pawns, 5);
assert_eq!(stats.0.num_isolated_pawns, 0); assert_eq!(stats.0.num_isolated_pawns, 0);
assert_eq!(stats.0.num_backward_pawns, 1); assert_eq!(stats.0.num_backward_pawns, 1);
// Add an adjacent friend to d2 pawn: no pawns are left isolated or backward. // Add an adjacent friend to d2 pawn: no pawns are left isolated or backward.
b.set_square(C2, WHITE, PAWN); b.set_square(C2, WHITE, PAWN);
stats.0.compute(&b, &gs); stats.0.compute(&mut b, &mut gs);
assert_eq!(stats.0.num_doubled_pawns, 5); assert_eq!(stats.0.num_doubled_pawns, 5);
assert_eq!(stats.0.num_isolated_pawns, 0); assert_eq!(stats.0.num_isolated_pawns, 0);
assert_eq!(stats.0.num_backward_pawns, 0); assert_eq!(stats.0.num_backward_pawns, 0);
// Add an isolated/backward white pawn in a far file. // Add an isolated/backward white pawn in a far file.
b.set_square(A2, WHITE, PAWN); b.set_square(A2, WHITE, PAWN);
stats.0.compute(&b, &gs); stats.0.compute(&mut b, &mut gs);
assert_eq!(stats.0.num_doubled_pawns, 5); assert_eq!(stats.0.num_doubled_pawns, 5);
assert_eq!(stats.0.num_isolated_pawns, 1); assert_eq!(stats.0.num_isolated_pawns, 1);
assert_eq!(stats.0.num_backward_pawns, 1); assert_eq!(stats.0.num_backward_pawns, 1);
@ -218,7 +218,7 @@ mod tests {
b.set_square(D4, WHITE, PAWN); b.set_square(D4, WHITE, PAWN);
b.set_square(E5, WHITE, PAWN); b.set_square(E5, WHITE, PAWN);
b.set_square(E3, WHITE, PAWN); b.set_square(E3, WHITE, PAWN);
stats.0.compute(&b, &gs); stats.0.compute(&mut b, &mut gs);
assert_eq!(stats.0.num_doubled_pawns, 2); assert_eq!(stats.0.num_doubled_pawns, 2);
assert_eq!(stats.0.num_isolated_pawns, 0); assert_eq!(stats.0.num_isolated_pawns, 0);
assert_eq!(stats.0.num_backward_pawns, 1); assert_eq!(stats.0.num_backward_pawns, 1);