From 47cc483d9ee4be7ac51eebce8bb78c71e2788b53 Mon Sep 17 00:00:00 2001 From: dece Date: Wed, 24 Jun 2020 22:19:09 +0200 Subject: [PATCH] rules: rework illegal moves detection --- src/analysis.rs | 2 +- src/engine.rs | 1 + src/movement.rs | 4 +- src/node.rs | 8 +- src/rules.rs | 224 +++++++++++++++++++++++++++--------------------- src/stats.rs | 28 +++--- 6 files changed, 150 insertions(+), 117 deletions(-) diff --git a/src/analysis.rs b/src/analysis.rs index b0cbdaa..b80b99f 100644 --- a/src/analysis.rs +++ b/src/analysis.rs @@ -119,7 +119,7 @@ impl Analyzer { } else { // If no best move could be found, checkmate is unavoidable; send the first legal move. 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 }; self.report_best_move(m); } diff --git a/src/engine.rs b/src/engine.rs index 2419118..79822e3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -163,6 +163,7 @@ impl Engine { _ => {} }; // Castling. + self.node.game_state.castling = 0; for c in fen.castling.chars() { match c { 'K' => self.node.game_state.castling |= castling::CASTLE_WH_K, diff --git a/src/movement.rs b/src/movement.rs index 44e6548..5d24478 100644 --- a/src/movement.rs +++ b/src/movement.rs @@ -95,10 +95,10 @@ impl Move { } else { board.move_square(self.dest, self.source); 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 { - 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; diff --git a/src/node.rs b/src/node.rs index 468c3f3..2d152e8 100644 --- a/src/node.rs +++ b/src/node.rs @@ -33,13 +33,13 @@ impl Node { } /// Return player moves from this node. - pub fn get_player_moves(&self) -> Vec { - rules::get_player_moves(&self.board, &self.game_state) + pub fn get_player_moves(&mut self) -> Vec { + rules::get_player_moves(&mut self.board, &mut self.game_state) } /// Compute stats for both players for this node. - pub fn compute_stats(&self) -> (stats::BoardStats, stats::BoardStats) { - stats::BoardStats::new_from(&self.board, &self.game_state) + pub fn compute_stats(&mut self) -> (stats::BoardStats, stats::BoardStats) { + stats::BoardStats::new_from(&mut self.board, &mut self.game_state) } } diff --git a/src/rules.rs b/src/rules.rs index 956b3cb..bdd7f2e 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -57,10 +57,9 @@ impl std::fmt::Display for GameState { /// `pseudo_legal` as a collection of attacked squares instead of legal /// move collection. pub fn get_player_moves( - board: &Board, - game_state: &GameState, + board: &mut Board, + game_state: &mut GameState, ) -> Vec { - let attacked_bb = board.get_full_rays(opposite(game_state.color)); // FIXME remove, doesn't w let mut moves = Vec::with_capacity(32); for r 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 { 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 /// is always called in a context where the piece color is known. fn get_piece_moves( - board: &Board, - game_state: &GameState, + board: &mut Board, + game_state: &mut GameState, square: Square, color: Color, - attacked_bb: Bitboard, ) -> Vec { let piece = board.get_piece_on(square); let mut moves = Vec::with_capacity(32); @@ -111,11 +109,10 @@ fn get_piece_moves( square, color, piece, - attacked_bb, &mut moves ); 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 } @@ -126,20 +123,19 @@ fn get_piece_moves( /// legal move. Does not take castle into account. Pawns that reach /// the last rank are promoted as queens. fn get_moves_from_bb( - board: &Board, - game_state: &GameState, + board: &mut Board, + game_state: &mut GameState, bitboard: Bitboard, square: Square, color: Color, piece: Piece, - attacked_bb: Bitboard, moves: &mut Vec ) { for ray_square in 0..NUM_SQUARES { if ray_square == square || bitboard & bit_pos(ray_square) == 0 { 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. if piece == PAWN @@ -161,16 +157,21 @@ fn get_moves_from_bb( /// `ray_square` is either empty or an enemy piece, but not a friend /// 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. fn inspect_move( - board: &Board, - game_state: &GameState, + board: &mut Board, + game_state: &mut GameState, square: Square, ray_square: Square, - attacked_bb: Bitboard ) -> Option { - let m = Move::new(square, ray_square); - if !is_illegal(board, game_state, &m, attacked_bb) { + let mut m = Move::new(square, ray_square); + 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) } else { None @@ -179,20 +180,21 @@ fn inspect_move( /// Check if a move is illegal. fn is_illegal( - board: &Board, - game_state: &GameState, - m: &Move, - attacked_bb: Bitboard + board: &mut Board, + game_state: &mut GameState, + m: &mut Move, ) -> bool { + let color = game_state.color; // A move is illegal if the king ends up in check. - let king_square = if board.get_piece_on(m.source) == KING { - m.dest + m.apply_to(board, game_state); + 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 { - if let Some(king) = board.find_king(game_state.color) { king } else { return false } - }; - // FIXME implement unmake move - let attacked_bb = board.get_full_rays(opposite(game_state.color)); - attacked_bb & bit_pos(king_square) != 0 + m.unmake(board, game_state); + false + } } /// Get possible castles. @@ -212,7 +214,6 @@ fn get_king_castles( game_state: &GameState, square: Square, color: Color, - attacked_bb: Bitboard, moves: &mut Vec ) { let combined_bb = board.combined(); @@ -230,6 +231,7 @@ fn get_king_castles( 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). 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 { continue } @@ -250,156 +252,166 @@ fn get_king_castles( mod tests { 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 { - let attacked_bb = board.get_full_rays(opposite(game_state.color)); - get_piece_moves(board, game_state, square, color, attacked_bb) - } - #[test] fn test_get_player_moves() { - let b = Board::new(); - let gs = GameState::new(); + let mut b = Board::new(); + let mut gs = GameState::new(); // 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); } #[test] - fn test_get_pawn_moves() { + fn test_get_pawn_progress_moves() { 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. 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!(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. 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!(moves.contains(&Move::new(E2, E3))); - assert!(moves.contains(&Move::new(E2, E4))); + assert!(moves.iter().any(|m| m.source == E2 && m.dest == E3)); + 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. // 1. black pawn 2 square forward; only 1 square forward available from start pos. 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!(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. 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); // 3. remove the e4 black pawn; the white pawn should not be able to jump above e3 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); + } + + #[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. - b.set_square(F3, BLACK, PAWN); - let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); - assert_eq!(moves.len(), 1); - b.set_square(D3, BLACK, PAWN); - let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); + b.set_square(E2, WHITE, PAWN); + let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE); 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. // 1. by simply moving forward. 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!(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] fn test_get_bishop_moves() { 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. 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); // Going top-right. - assert!(moves.contains(&Move::new(D4, E5))); - assert!(moves.contains(&Move::new(D4, F6))); - assert!(moves.contains(&Move::new(D4, G7))); - assert!(moves.contains(&Move::new(D4, H8))); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == E5)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == F6)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == G7)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == H8)); // Going bottom-right. - assert!(moves.contains(&Move::new(D4, E3))); - assert!(moves.contains(&Move::new(D4, F2))); - assert!(moves.contains(&Move::new(D4, G1))); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == E3)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == F2)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == G1)); // Going bottom-left. - assert!(moves.contains(&Move::new(D4, C3))); - assert!(moves.contains(&Move::new(D4, B2))); - assert!(moves.contains(&Move::new(D4, A1))); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == C3)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == B2)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == A1)); // Going top-left. - assert!(moves.contains(&Move::new(D4, C5))); - assert!(moves.contains(&Move::new(D4, B6))); - assert!(moves.contains(&Move::new(D4, A7))); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == C5)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == B6)); + assert!(moves.iter().any(|m| m.source == D4 && m.dest == A7)); // When blocking commit to one square with friendly piece, lose 2 moves. 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. 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] fn test_get_knight_moves() { 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 // 8 moves. 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. 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. 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. b.set_square(B3, 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] fn test_get_rook_moves() { let mut b = Board::new_empty(); - let gs = GameState::new(); + let mut gs = GameState::new(); 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); - 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); - 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] fn test_get_queen_moves() { let mut b = Board::new_empty(); - let gs = GameState::new(); + let mut gs = GameState::new(); 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] @@ -409,27 +421,47 @@ mod tests { // King can move 1 square in any direction. let mut b = Board::new_empty(); 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); - 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. let mut b = Board::new_empty(); b.set_square(E1, WHITE, KING); b.set_square(A1, 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. gs.color = BLACK; b.set_square(E8, BLACK, KING); b.set_square(A8, 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] - 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 gs = GameState::new(); @@ -440,7 +472,7 @@ mod tests { // No castling available. gs.castling = 0; // 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); } } diff --git a/src/stats.rs b/src/stats.rs index 2b46671..88830fe 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -31,12 +31,12 @@ impl BoardStats { /// /// The playing color will have its stats filled in the first /// 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 gs = game_state.clone(); - stats.0.compute(board, &gs); + stats.0.compute(board, &mut gs); gs.color = opposite(gs.color); - stats.1.compute(board, &gs); + stats.1.compute(board, &mut gs); stats } @@ -58,7 +58,7 @@ impl BoardStats { /// /// Only the current playing side stats are created, /// 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(); let color = game_state.color; // Compute mobility for all pieces. @@ -157,8 +157,8 @@ mod tests { #[test] fn test_compute_stats() { // Check that initial stats are correct. - let b = Board::new(); - let gs = GameState::new(); + let mut b = Board::new(); + let mut gs = GameState::new(); let initial_stats = BoardStats { num_pawns: 8, num_bishops: 2, @@ -171,7 +171,7 @@ mod tests { num_isolated_pawns: 0, 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 == initial_stats); @@ -179,15 +179,15 @@ mod tests { let mut b = Board::new_empty(); b.set_square(D4, 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); // Add a pawn on another file, no changes expected. 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); // Add a pawn backward in the d-file: there are now 3 doubled pawns. 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); // 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? // Protect d4 pawn with a friend in e3: it is not isolated nor backward anymore. 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_isolated_pawns, 0); assert_eq!(stats.0.num_backward_pawns, 1); // Add an adjacent friend to d2 pawn: no pawns are left isolated or backward. 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_isolated_pawns, 0); assert_eq!(stats.0.num_backward_pawns, 0); // Add an isolated/backward white pawn in a far file. 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_isolated_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(E5, 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_isolated_pawns, 0); assert_eq!(stats.0.num_backward_pawns, 1);