From 97444db39c93eaeeac346cb7ae083f826616d839 Mon Sep 17 00:00:00 2001 From: dece Date: Mon, 22 Jun 2020 21:33:23 +0200 Subject: [PATCH] rules: rework move generation for bitboards --- src/board.rs | 56 ++++++ src/castling.rs | 62 +++++-- src/rules.rs | 483 +++++++++++++----------------------------------- 3 files changed, 233 insertions(+), 368 deletions(-) diff --git a/src/board.rs b/src/board.rs index 1f4f80a..d4da242 100644 --- a/src/board.rs +++ b/src/board.rs @@ -311,6 +311,33 @@ impl Board { None } + /// Get capture rays for all pieces of `color`. + /// + /// This function is used to find illegal moves for opposite color. + /// + /// This add move rays of all piece types, pawns being a special + /// case: their diagonal capture are all added even though no enemy + /// piece is on the target square. + pub fn get_rays(&self, color: Color) -> Bitboard { + let mut ray_bb = 0; + let color_bb = self.by_color(color); + for square in 0..NUM_SQUARES { + if color_bb & bit_pos(square) == 0 { + continue + } + ray_bb |= match self.get_piece_on(square) { + PAWN => self.get_pawn_protections(square, color), + BISHOP => self.get_bishop_rays(square, color), + KNIGHT => self.get_knight_rays(square, color), + ROOK => self.get_rook_rays(square, color), + QUEEN => self.get_queen_rays(square, color), + KING => self.get_king_rays(square, color), + _ => { panic!("No piece on square {} but color {} bit is set.", square, color) } + }; + } + ray_bb + } + /// Get pawn progress: only forward moves. pub fn get_pawn_progresses(&self, square: Square, color: Color) -> Bitboard { let mut progress_bb = PAWN_PROGRESSES[color][square as usize] & !self.combined(); @@ -329,10 +356,21 @@ impl Board { } /// Get pawn captures: only moves capturing enemy pieces. + /// + /// If a pawn is not currently attacking any piece, the bitboard + /// will be empty. pub const fn get_pawn_captures(&self, square: Square, color: Color) -> Bitboard { PAWN_CAPTURES[color][square as usize] & self.by_color(opposite(color)) } + /// Get pawn capture bitboard, without considering enemy pieces. + /// + /// Both possible diagonals will be set, even if a friendly piece + /// occupies one. + pub const fn get_pawn_protections(&self, square: Square, color: Color) -> Bitboard { + PAWN_CAPTURES[color][square as usize] + } + /// Get bishop rays: moves and captures bitboard. pub fn get_bishop_rays(&self, square: Square, color: Color) -> Bitboard { self.get_blockable_rays(square, color, &[(1, 1), (1, -1), (-1, -1), (-1, 1)]) @@ -550,6 +588,13 @@ mod tests { assert_eq!(b.find_king(BLACK), Some(E8)); } + #[test] + fn test_get_rays() { + let b = Board::new(); + assert_eq!(count_bits(b.get_rays(WHITE)), 8); + assert_eq!(count_bits(b.get_rays(BLACK)), 8); + } + #[test] fn test_get_pawn_progresses() { let mut b = Board::new_empty(); @@ -603,6 +648,17 @@ mod tests { assert_eq!(count_bits(b.get_pawn_captures(C2, WHITE)), 2); } + #[test] + fn test_get_pawn_protections() { + let mut b = Board::new_empty(); + + // A pawn not on a border file or rank always protect 2 squares. + b.set_square(B2, WHITE, PAWN); + assert_eq!(count_bits(b.get_pawn_protections(B2, WHITE)), 2); + b.set_square(A2, WHITE, PAWN); + assert_eq!(count_bits(b.get_pawn_protections(A2, WHITE)), 1); + } + #[test] fn test_get_bishop_rays() { let mut b = Board::new_empty(); diff --git a/src/castling.rs b/src/castling.rs index 0dbb8e6..12b0ed3 100644 --- a/src/castling.rs +++ b/src/castling.rs @@ -1,21 +1,53 @@ //! Castling flags. +use crate::board::{Bitboard, RANK_1, RANK_8}; + pub type Castle = u8; -pub const CASTLING_WH_K: Castle = 0b00000001; -pub const CASTLING_WH_Q: Castle = 0b00000010; -pub const CASTLING_WH_MASK: Castle = 0b00000011; -pub const CASTLING_BL_K: Castle = 0b00000100; -pub const CASTLING_BL_Q: Castle = 0b00001000; -pub const CASTLING_BL_MASK: Castle = 0b00001100; -pub const CASTLING_K_MASK: Castle = 0b00000101; -pub const CASTLING_Q_MASK: Castle = 0b00001010; -pub const CASTLING_MASK: Castle = 0b00001111; +pub const CASTLE_WH_K: Castle = 0b00000001; +pub const CASTLE_WH_Q: Castle = 0b00000010; +pub const CASTLE_WH_MASK: Castle = 0b00000011; +pub const CASTLE_BL_K: Castle = 0b00000100; +pub const CASTLE_BL_Q: Castle = 0b00001000; +pub const CASTLE_BL_MASK: Castle = 0b00001100; +pub const CASTLE_K_MASK: Castle = 0b00000101; +pub const CASTLE_Q_MASK: Castle = 0b00001010; +pub const CASTLE_MASK: Castle = 0b00001111; -/// Castling sides parameters. +/// Index castling masks with their color. +pub const CASTLE_MASK_BY_COLOR: [Castle; 2] = [CASTLE_WH_MASK, CASTLE_BL_MASK]; + +/// Index castling ranks with their color. +pub const CASTLE_RANK_BY_COLOR: [i8; 2] = [RANK_1, RANK_8]; + +pub const CASTLE_SIDE_K: usize = 0; +pub const CASTLE_SIDE_Q: usize = 1; +pub const NUM_CASTLE_SIDES: usize = 2; + +/// Index castling sides using CASTLE_SIDE_K and CASTLE_SIDE_Q. +pub const CASTLE_SIDES: [Castle; 2] = [CASTLE_K_MASK, CASTLE_Q_MASK]; + +/// Castle paths that must not be under attack, by color and side. /// -/// For both sides, the 3-uple contains files that should be empty -/// and not attacked, an optional file that should be empty for -/// queen-side, and the castling side-mask. -pub const CASTLING_SIDES: [([i8; 2], Option, Castle); 2] = - [([5i8, 6i8], None, CASTLING_K_MASK), ([3i8, 2i8], Some(1i8), CASTLING_Q_MASK)]; +/// This includes the original king position, its target square and +/// the square in between. +pub const CASTLE_LEGALITY_PATHS: [[Bitboard; 2]; 2] = [ + [ + 0b00000000_00000001_00000001_00000001_00000000_00000000_00000000_00000000, // White Kside. + 0b00000000_00000000_00000000_00000001_00000001_00000001_00000000_00000000, // White Qside. + ], [ + 0b00000000_10000000_10000000_10000000_00000000_00000000_00000000_00000000, // Black Kside. + 0b00000000_00000000_00000000_10000000_10000000_10000000_00000000_00000000, // Black Qside. + ] +]; + +/// Castle paths that must be empty. +pub const CASTLE_MOVE_PATHS: [[Bitboard; 2]; 2] = [ + [ + 0b00000000_00000001_00000001_00000000_00000000_00000000_00000000_00000000, // White Kside. + 0b00000000_00000000_00000000_00000000_00000001_00000001_00000001_00000000, // White Qside. + ], [ + 0b00000000_10000000_10000000_00000000_00000000_00000000_00000000_00000000, // Black Kside. + 0b00000000_00000000_00000000_00000000_10000000_10000000_10000000_00000000, // Black Qside. + ] +]; diff --git a/src/rules.rs b/src/rules.rs index 5d2c23e..795bbe7 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -5,9 +5,6 @@ use crate::castling::*; use crate::fen; use crate::movement::Move; -pub const POS_MIN: i8 = 0; -pub const POS_MAX: i8 = 7; - /// Characteristics of the state of a game. /// /// It does not include various parameters such as clocks that are @@ -31,7 +28,7 @@ impl GameState { pub const fn new() -> GameState { GameState { color: WHITE, - castling: CASTLING_MASK, + castling: CASTLE_MASK, en_passant: None, halfmove: 0, fullmove: 1, @@ -62,8 +59,8 @@ impl std::fmt::Display for GameState { pub fn get_player_moves( board: &Board, game_state: &GameState, - pseudo_legal: bool, ) -> Vec { + let attacked_bb = board.get_rays(opposite(game_state.color)); let mut moves = Vec::with_capacity(32); for r in 0..8 { for f in 0..8 { @@ -73,7 +70,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, pseudo_legal) + &mut get_piece_moves(board, game_state, square, game_state.color, attacked_bb) ); } } @@ -92,252 +89,33 @@ fn get_piece_moves( game_state: &GameState, square: Square, color: Color, - pseudo_legal: bool, -) -> Vec { - match board.get_piece_on(square) { - PAWN => get_pawn_moves(board, game_state, square, color, pseudo_legal), - BISHOP => get_bishop_moves(board, game_state, square, color, pseudo_legal), - KNIGHT => get_knight_moves(board, game_state, square, color, pseudo_legal), - ROOK => get_rook_moves(board, game_state, square, color, pseudo_legal), - QUEEN => get_queen_moves(board, game_state, square, color, pseudo_legal), - KING => get_king_moves(board, game_state, square, color, pseudo_legal), - _ => { panic!("No piece on square.") }, - } -} - -// fn get_pawn_moves( -// board: &Board, -// game_state: &GameState, -// square: Square, -// color: Color, -// pseudo_legal: bool, -// ) -> Vec { -// let (f, r) = (sq_file(square), sq_rank(square)); -// let mut moves = vec!(); -// // Direction: positive for white, negative for black. -// let dir = if color == WHITE { 1 } else { -1 }; -// // Check 1 or 2 square forward. -// let move_len = if (color == WHITE && r == 1) || (color == BLACK && r == 6) { 2 } else { 1 }; -// for i in 1..=move_len { -// let forward_r = r + dir * i; -// if dir > 0 && forward_r > POS_MAX { -// return moves -// } -// if dir < 0 && forward_r < POS_MIN { -// return moves -// } -// let forward: Square = sq(f, forward_r); -// // If forward square is empty (and we are not jumping over an occupied square), add it. -// if board.is_empty(forward) && (i == 1 || board.is_empty(sq(f, forward_r - dir))) { -// let mut m = Move::new(square, forward); -// // Pawns that get to the opposite rank automatically promote as queens. -// if (dir > 0 && forward_r == POS_MAX) || (dir < 0 && forward_r == POS_MIN) { -// m.promotion = Some(QUEEN) -// } -// if pseudo_legal || !is_illegal(board, game_state, &m) { -// moves.push(m); -// } -// } -// // Check diagonals for pieces to attack. -// if i == 1 { -// // First diagonal. -// if f - 1 >= POS_MIN { -// let diag = sq(f - 1, forward_r); -// if !board.is_empty(diag) { -// let diag_color = board.get_color_on(diag); -// if let Some(m) = get_capture_move(color, square, diag_color, diag, true) { -// if pseudo_legal || !is_illegal(board, game_state, &m) { -// moves.push(m); -// } -// } - -// } -// } -// // Second diagonal. -// if f + 1 <= POS_MAX { -// let diag = sq(f + 1, forward_r); -// if !board.is_empty(diag) { -// let diag_color = board.get_color_on(diag); -// if let Some(m) = get_capture_move(color, square, diag_color, diag, true) { -// if pseudo_legal || !is_illegal(board, game_state, &m) { -// moves.push(m); -// } -// } -// } -// } -// } -// // TODO en passant -// } -// moves -// } - -fn get_pawn_moves( - board: &Board, - game_state: &GameState, - square: Square, - color: Color, - pseudo_legal: bool, + attacked_bb: Bitboard, ) -> Vec { + let piece = board.get_piece_on(square); + let mut moves = Vec::with_capacity(32); get_moves_from_bb( board, game_state, - board.get_pawn_progresses(square, color) | board.get_pawn_captures(square, color), - square, - color, - PAWN, - pseudo_legal - ) - // TODO en passant -} - -fn get_bishop_moves( - board: &Board, - game_state: &GameState, - square: Square, - color: Color, - pseudo_legal: bool, -) -> Vec { - get_moves_from_bb( - board, - game_state, - board.get_bishop_rays(square, color), - square, - color, - BISHOP, - pseudo_legal - ) -} - -fn get_knight_moves( - board: &Board, - game_state: &GameState, - square: Square, - color: Color, - pseudo_legal: bool, -) -> Vec { - get_moves_from_bb( - board, - game_state, - board.get_knight_rays(square, color), - square, - color, - KNIGHT, - pseudo_legal - ) -} - -fn get_rook_moves( - board: &Board, - game_state: &GameState, - square: Square, - color: Color, - pseudo_legal: bool, -) -> Vec { - get_moves_from_bb( - board, - game_state, - board.get_rook_rays(square, color), - square, - color, - ROOK, - pseudo_legal - ) -} - -fn get_queen_moves( - board: &Board, - game_state: &GameState, - square: Square, - color: Color, - pseudo_legal: bool, -) -> Vec { - get_moves_from_bb( - board, - game_state, - board.get_queen_rays(square, color), - square, - color, - QUEEN, - pseudo_legal - ) -} - -fn get_king_moves( - board: &Board, - game_state: &GameState, - square: Square, - color: Color, - pseudo_legal: bool, -) -> Vec { - let mut moves = get_moves_from_bb( - board, - game_state, - board.get_king_rays(square, color), - square, - color, - KING, - pseudo_legal - ); - - // Stop here for pseudo legal moves as castling is not considered along with them. - if pseudo_legal { - return moves - } - - // Castling. Here are the rules that should ALL be respected: - // 1. The king and the chosen rook are on the player's first rank. - // 2. Neither the king nor the chosen rook has previously moved. - // 3. There are no pieces between the king and the chosen rook. - // 4. The king is not currently in check. - // 5. The king does not pass through a square that is attacked by an enemy piece. - // 6. The king does not end up in check. - - // First get the required castling rank and color mask for the player. - let (castling_rank, castling_color_mask) = if game_state.color == WHITE { - (0, CASTLING_WH_MASK) - } else { - (7, CASTLING_BL_MASK) - }; - - let r = sq_rank(square); - // Check for castling if the king is on its castling rank (R1) - // and is not in check (R4). - if - r == castling_rank && - !is_attacked(board, game_state, square) - { - // Check for both castling sides. - for (path_files, opt_empty_file, castling_side_mask) in CASTLING_SIDES.iter() { - // Check for castling availability for this color and side. - if (game_state.castling & castling_color_mask & castling_side_mask) != 0 { - // Check that squares in the king's path are empty and not attacked (R3.1, R5, R6). - let mut path_is_clear = true; - for path_f in path_files { - let path_square = sq(*path_f, castling_rank); - if - !board.is_empty(path_square) - || is_illegal(board, game_state, &Move::new(square, path_square)) { - path_is_clear = false; - break; - } - } - if !path_is_clear { - continue; - } - // Check that rook jumps over an empty square on queen-side (R3.2). - if let Some(rook_path_f) = opt_empty_file { - let rook_path_square = sq(*rook_path_f, castling_rank); - if !board.is_empty(rook_path_square) { - continue; - } - } - let castle = castling_side_mask & castling_color_mask; - let m = Move::get_castle_move(castle); - if pseudo_legal || !is_illegal(board, game_state, &m) { - moves.push(m); - } + match piece { + PAWN => { + board.get_pawn_progresses(square, color) + | board.get_pawn_captures(square, color) } - } + KING => board.get_king_rays(square, color), + BISHOP => board.get_bishop_rays(square, color), + KNIGHT => board.get_knight_rays(square, color), + ROOK => board.get_rook_rays(square, color), + QUEEN => board.get_queen_rays(square, color), + _ => { panic!("Invalid piece.") } + }, + 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); } moves } @@ -345,8 +123,8 @@ fn get_king_moves( /// Get moves from this ray bitboard. /// /// Inspect all moves from the bitboard and produce a Move for each -/// legal move, or all moves if `pseudo_legal` is true. Pawns that -/// reach the last rank are promoted as queens. +/// 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, @@ -354,14 +132,14 @@ fn get_moves_from_bb( square: Square, color: Color, piece: Piece, - pseudo_legal: bool -) -> Vec { - let mut moves = Vec::with_capacity(count_bits(bitboard).into()); + 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, pseudo_legal) { + if let Some(mut m) = inspect_move(board, game_state, square, ray_square, attacked_bb) { // Automatic queen promotion for pawns moving to the opposite rank. if piece == PAWN @@ -375,14 +153,13 @@ fn get_moves_from_bb( moves.push(m); } } - moves } /// Accept or ignore a move from `square` to `ray_square`. /// -/// This function checks that the move is legal, unless `pseudo_legal` -/// is true. It assumes that `ray_square` is either empty or an enemy -/// piece, but not a friend piece: they should have been filtered. +/// This function checks that the move is legal. It assumes that +/// `ray_square` is either empty or an enemy piece, but not a friend +/// piece: they should have been filtered. /// /// This function does not set promotions for pawns reaching last rank. fn inspect_move( @@ -390,91 +167,105 @@ fn inspect_move( game_state: &GameState, square: Square, ray_square: Square, - pseudo_legal: bool + attacked_bb: Bitboard ) -> Option { let m = Move::new(square, ray_square); - if pseudo_legal || !is_illegal(board, game_state, &m) { + if !is_illegal(board, game_state, &m, attacked_bb) { Some(m) } else { None } } -/// Return a move from `square1` to `square2` if colors are opposite. -fn get_capture_move( - color1: Color, - square1: Square, - color2: Color, - square2: Square, - is_pawn: bool, -) -> Option { - if color2 == opposite(color1) { - // Automatic queen promotion for pawns moving to the opposite rank. - Some(if - is_pawn - && (color1 == WHITE && sq_rank(square2) == POS_MAX) - || (color1 == BLACK && sq_rank(square2) == POS_MIN) - { - Move::new_promotion(square1, square2, QUEEN) - } else { - Move::new(square1, square2) - }) - } else { - None - } -} - /// Check if a move is illegal. -fn is_illegal(board: &Board, game_state: &GameState, m: &Move) -> bool { - if let Some(mut king_square) = board.find_king(game_state.color) { - let mut hypothetic_board = board.clone(); - m.apply_to_board(&mut hypothetic_board); - // A move is illegal if the king ends up in check. - // If king moves, use its new position. - if m.source == king_square { - king_square = m.dest - } - // Check if the move makes the player king in check. - if is_attacked(&hypothetic_board, &game_state, king_square) { - return true - } - } - false +fn is_illegal( + board: &Board, + game_state: &GameState, + m: &Move, + attacked_bb: Bitboard +) -> bool { + // A move is illegal if the king ends up in check. + let king_square = if board.get_piece_on(m.source) == KING { + m.dest + } else { + if let Some(king) = board.find_king(game_state.color) { king } else { return false } + }; + attacked_bb & bit_pos(king_square) != 0 } -/// Return true if the piece on `square` is attacked. +/// Get possible castles. /// -/// Check all possible enemy moves and return true when one of them -/// ends up attacking the position. +/// Here are the rules that should ALL be respected: +/// 1. The king and the chosen rook are on the player's first rank. +/// 2. Neither the king nor the chosen rook has previously moved. +/// 3. There are no pieces between the king and the chosen rook. +/// 4. The king is not currently in check. +/// 5. The king does not pass through a square that is attacked by an enemy piece. +/// 6. The king does not end up in check. /// -/// Beware that the game state must be coherent with the analysed -/// square, i.e. if the piece on `square` is white, the game state -/// should tell that it is white turn. If `square` is empty, simply -/// check if it is getting attacked by the opposite player. -fn is_attacked(board: &Board, game_state: &GameState, square: Square) -> bool { - let mut enemy_game_state = game_state.clone(); - enemy_game_state.color = opposite(game_state.color); - // Do not attempt to commit moves, just check for attacked squares. - let enemy_moves = get_player_moves(board, &enemy_game_state, true); - for m in enemy_moves.iter() { - if square == m.dest { - return true +/// Rule 1 is NOT checked by this method to avoid creating empty vecs. +/// Check it in the caller. +fn get_king_castles( + board: &Board, + game_state: &GameState, + square: Square, + color: Color, + attacked_bb: Bitboard, + moves: &mut Vec +) { + let combined_bb = board.combined(); + + // First get the required castling rank and color mask for the player. + let castle_rank = CASTLE_RANK_BY_COLOR[color]; + let castle_color_mask = CASTLE_MASK_BY_COLOR[color]; + + // Check for castling if the king is on its castling rank (R1) + if sq_rank(square) == castle_rank { + // Check for both castling sides. + for castle_side_id in 0..NUM_CASTLE_SIDES { + let castle_side_mask = CASTLE_SIDES[castle_side_id]; + // Check for castling availability for this color and side (R2). + 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]; + if attacked_bb & castle_legality_path != 0 { + continue + } + + // Check that squares in both the king and rook's path are empty. + let castle_move_path = CASTLE_MOVE_PATHS[color][castle_side_id]; + if combined_bb & castle_move_path != 0 { + continue + } + + moves.push(Move::get_castle_move(castle_side_mask & castle_color_mask)); + } } } - false } #[cfg(test)] 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_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(); // At first move, white has 16 pawn moves and 4 knight moves. - let moves = get_player_moves(&b, &gs, false); + let moves = get_player_moves(&b, &gs); assert_eq!(moves.len(), 20); } @@ -485,12 +276,12 @@ mod tests { // 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_piece_moves(&b, &gs, D3, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, D3, WHITE); assert!(moves.len() == 1 && moves.contains(&Move::new(D3, 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_piece_moves(&b, &gs, E2, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); assert_eq!(moves.len(), 2); assert!(moves.contains(&Move::new(E2, E3))); assert!(moves.contains(&Move::new(E2, E4))); @@ -498,23 +289,23 @@ mod tests { // 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_piece_moves(&b, &gs, E2, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); assert!(moves.len() == 1 && moves.contains(&Move::new(E2, E3))); // 2. black pawn 1 square forward; no square available. b.set_square(E3, BLACK, PAWN); - let moves = get_piece_moves(&b, &gs, E2, WHITE, false); + let moves = get_legal_piece_moves(&b, &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_piece_moves(&b, &gs, E2, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); assert_eq!(moves.len(), 0); // Check that a pawn can take a piece diagonally. b.set_square(F3, BLACK, PAWN); - let moves = get_piece_moves(&b, &gs, E2, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); assert!(moves.len() == 1 && moves.contains(&Move::new(E2, F3))); b.set_square(D3, BLACK, PAWN); - let moves = get_piece_moves(&b, &gs, E2, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, E2, WHITE); assert_eq!(moves.len(), 2); assert!(moves.contains( &Move::new(E2, F3) )); assert!(moves.contains( &Move::new(E2, D3) )); @@ -522,7 +313,7 @@ mod tests { // 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_piece_moves(&b, &gs, A7, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, A7, WHITE); assert!(moves.len() == 1 && moves.contains(&Move::new_promotion(A7, A8, QUEEN))); } @@ -533,7 +324,7 @@ mod tests { // A bishop has maximum range when it's in a center square. b.set_square(D4, WHITE, BISHOP); - let moves = get_piece_moves(&b, &gs, D4, WHITE, false); + let moves = get_legal_piece_moves(&b, &gs, D4, WHITE); assert_eq!(moves.len(), 13); // Going top-right. assert!(moves.contains(&Move::new(D4, E5))); @@ -555,11 +346,11 @@ mod tests { // When blocking commit to one square with friendly piece, lose 2 moves. b.set_square(B2, WHITE, PAWN); - assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 11); + assert_eq!(get_legal_piece_moves(&b, &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_piece_moves(&b, &gs, D4, WHITE, false).len(), 12); + assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 12); } #[test] @@ -570,20 +361,20 @@ mod tests { // 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_piece_moves(&b, &gs, D4, WHITE, false).len(), 8); + assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 8); // If on a side if has only 4 moves. b.set_square(A4, WHITE, KNIGHT); - assert_eq!(get_piece_moves(&b, &gs, A4, WHITE, false).len(), 4); + assert_eq!(get_legal_piece_moves(&b, &gs, A4, WHITE).len(), 4); // And in a corner, only 2 moves. b.set_square(A1, WHITE, KNIGHT); - assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, false).len(), 2); + assert_eq!(get_legal_piece_moves(&b, &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_piece_moves(&b, &gs, A1, WHITE, false).len(), 0); + assert_eq!(get_legal_piece_moves(&b, &gs, A1, WHITE).len(), 0); } #[test] @@ -592,11 +383,11 @@ mod tests { let gs = GameState::new(); b.set_square(D4, WHITE, ROOK); - assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 14); + assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 14); b.set_square(D6, BLACK, PAWN); - assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 12); + assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 12); b.set_square(D6, WHITE, PAWN); - assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 11); + assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 11); } #[test] @@ -605,7 +396,7 @@ mod tests { let gs = GameState::new(); b.set_square(D4, WHITE, QUEEN); - assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 14 + 13); + assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 14 + 13); } #[test] @@ -615,23 +406,23 @@ 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_piece_moves(&b, &gs, D4, WHITE, false).len(), 8); + assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 8); b.set_square(E5, WHITE, PAWN); - assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 7); + assert_eq!(get_legal_piece_moves(&b, &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_piece_moves(&b, &gs, E1, WHITE, false).len(), 5 + 2); + assert_eq!(get_legal_piece_moves(&b, &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_piece_moves(&b, &gs, E8, BLACK, false).len(), 5 + 2); + assert_eq!(get_legal_piece_moves(&b, &gs, E8, BLACK).len(), 5 + 2); } #[test] @@ -646,21 +437,7 @@ mod tests { // No castling available. gs.castling = 0; // 5 moves in absolute but only 2 are legal. - let all_wh_moves = get_piece_moves(&b, &gs, E1, WHITE, false); + let all_wh_moves = get_legal_piece_moves(&b, &gs, E1, WHITE); assert_eq!(all_wh_moves.len(), 2); } - - #[test] - fn test_is_attacked() { - let mut b = Board::new_empty(); - let gs = GameState::new(); - - // Place a black rook in white pawn's file. - b.set_square(D4, WHITE, PAWN); - b.set_square(D6, BLACK, ROOK); - assert!(is_attacked(&b, &gs, D4)); - // Move the rook on another file, no more attack. - Move::new(D6, E6).apply_to_board(&mut b); - assert!(!is_attacked(&b, &gs, D4)); - } }