rules: rework move generation for bitboards

This commit is contained in:
dece 2020-06-22 21:33:23 +02:00
parent 5a6893acc7
commit 97444db39c
3 changed files with 233 additions and 368 deletions

View file

@ -311,6 +311,33 @@ impl Board {
None 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. /// Get pawn progress: only forward moves.
pub fn get_pawn_progresses(&self, square: Square, color: Color) -> Bitboard { pub fn get_pawn_progresses(&self, square: Square, color: Color) -> Bitboard {
let mut progress_bb = PAWN_PROGRESSES[color][square as usize] & !self.combined(); 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. /// 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 { pub const fn get_pawn_captures(&self, square: Square, color: Color) -> Bitboard {
PAWN_CAPTURES[color][square as usize] & self.by_color(opposite(color)) 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. /// Get bishop rays: moves and captures bitboard.
pub fn get_bishop_rays(&self, square: Square, color: Color) -> 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)]) 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)); 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] #[test]
fn test_get_pawn_progresses() { fn test_get_pawn_progresses() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();
@ -603,6 +648,17 @@ mod tests {
assert_eq!(count_bits(b.get_pawn_captures(C2, WHITE)), 2); 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] #[test]
fn test_get_bishop_rays() { fn test_get_bishop_rays() {
let mut b = Board::new_empty(); let mut b = Board::new_empty();

View file

@ -1,21 +1,53 @@
//! Castling flags. //! Castling flags.
use crate::board::{Bitboard, RANK_1, RANK_8};
pub type Castle = u8; pub type Castle = u8;
pub const CASTLING_WH_K: Castle = 0b00000001; pub const CASTLE_WH_K: Castle = 0b00000001;
pub const CASTLING_WH_Q: Castle = 0b00000010; pub const CASTLE_WH_Q: Castle = 0b00000010;
pub const CASTLING_WH_MASK: Castle = 0b00000011; pub const CASTLE_WH_MASK: Castle = 0b00000011;
pub const CASTLING_BL_K: Castle = 0b00000100; pub const CASTLE_BL_K: Castle = 0b00000100;
pub const CASTLING_BL_Q: Castle = 0b00001000; pub const CASTLE_BL_Q: Castle = 0b00001000;
pub const CASTLING_BL_MASK: Castle = 0b00001100; pub const CASTLE_BL_MASK: Castle = 0b00001100;
pub const CASTLING_K_MASK: Castle = 0b00000101; pub const CASTLE_K_MASK: Castle = 0b00000101;
pub const CASTLING_Q_MASK: Castle = 0b00001010; pub const CASTLE_Q_MASK: Castle = 0b00001010;
pub const CASTLING_MASK: Castle = 0b00001111; 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 /// This includes the original king position, its target square and
/// and not attacked, an optional file that should be empty for /// the square in between.
/// queen-side, and the castling side-mask. pub const CASTLE_LEGALITY_PATHS: [[Bitboard; 2]; 2] = [
pub const CASTLING_SIDES: [([i8; 2], Option<i8>, Castle); 2] = [
[([5i8, 6i8], None, CASTLING_K_MASK), ([3i8, 2i8], Some(1i8), CASTLING_Q_MASK)]; 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.
]
];

View file

@ -5,9 +5,6 @@ use crate::castling::*;
use crate::fen; use crate::fen;
use crate::movement::Move; use crate::movement::Move;
pub const POS_MIN: i8 = 0;
pub const POS_MAX: i8 = 7;
/// Characteristics of the state of a game. /// Characteristics of the state of a game.
/// ///
/// It does not include various parameters such as clocks that are /// It does not include various parameters such as clocks that are
@ -31,7 +28,7 @@ impl GameState {
pub const fn new() -> GameState { pub const fn new() -> GameState {
GameState { GameState {
color: WHITE, color: WHITE,
castling: CASTLING_MASK, castling: CASTLE_MASK,
en_passant: None, en_passant: None,
halfmove: 0, halfmove: 0,
fullmove: 1, fullmove: 1,
@ -62,8 +59,8 @@ impl std::fmt::Display for GameState {
pub fn get_player_moves( pub fn get_player_moves(
board: &Board, board: &Board,
game_state: &GameState, game_state: &GameState,
pseudo_legal: bool,
) -> Vec<Move> { ) -> Vec<Move> {
let attacked_bb = board.get_rays(opposite(game_state.color));
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 {
@ -73,7 +70,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, 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, game_state: &GameState,
square: Square, square: Square,
color: Color, color: Color,
pseudo_legal: bool, attacked_bb: Bitboard,
) -> Vec<Move> {
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<Move> {
// 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,
) -> Vec<Move> { ) -> Vec<Move> {
let piece = board.get_piece_on(square);
let mut moves = Vec::with_capacity(32);
get_moves_from_bb( get_moves_from_bb(
board, board,
game_state, game_state,
board.get_pawn_progresses(square, color) | board.get_pawn_captures(square, color), match piece {
square, PAWN => {
color, board.get_pawn_progresses(square, color)
PAWN, | board.get_pawn_captures(square, color)
pseudo_legal
)
// TODO en passant
} }
KING => board.get_king_rays(square, color),
fn get_bishop_moves( BISHOP => board.get_bishop_rays(square, color),
board: &Board, KNIGHT => board.get_knight_rays(square, color),
game_state: &GameState, ROOK => board.get_rook_rays(square, color),
square: Square, QUEEN => board.get_queen_rays(square, color),
color: Color, _ => { panic!("Invalid piece.") }
pseudo_legal: bool, },
) -> Vec<Move> {
get_moves_from_bb(
board,
game_state,
board.get_bishop_rays(square, color),
square, square,
color, color,
BISHOP, piece,
pseudo_legal attacked_bb,
) &mut moves
}
fn get_knight_moves(
board: &Board,
game_state: &GameState,
square: Square,
color: Color,
pseudo_legal: bool,
) -> Vec<Move> {
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<Move> {
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<Move> {
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<Move> {
let mut moves = get_moves_from_bb(
board,
game_state,
board.get_king_rays(square, color),
square,
color,
KING,
pseudo_legal
); );
if piece == KING && sq_rank(square) == CASTLE_RANK_BY_COLOR[color] {
// Stop here for pseudo legal moves as castling is not considered along with them. get_king_castles(board, game_state, square, color, attacked_bb, &mut moves);
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);
}
}
}
} }
moves moves
} }
@ -345,8 +123,8 @@ fn get_king_moves(
/// Get moves from this ray bitboard. /// Get moves from this ray bitboard.
/// ///
/// Inspect all moves from the bitboard and produce a Move for each /// Inspect all moves from the bitboard and produce a Move for each
/// legal move, or all moves if `pseudo_legal` is true. Pawns that /// legal move. Does not take castle into account. Pawns that reach
/// 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: &Board,
game_state: &GameState, game_state: &GameState,
@ -354,14 +132,14 @@ fn get_moves_from_bb(
square: Square, square: Square,
color: Color, color: Color,
piece: Piece, piece: Piece,
pseudo_legal: bool attacked_bb: Bitboard,
) -> Vec<Move> { moves: &mut Vec<Move>
let mut moves = Vec::with_capacity(count_bits(bitboard).into()); ) {
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, 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. // Automatic queen promotion for pawns moving to the opposite rank.
if if
piece == PAWN piece == PAWN
@ -375,14 +153,13 @@ fn get_moves_from_bb(
moves.push(m); moves.push(m);
} }
} }
moves
} }
/// Accept or ignore a move from `square` to `ray_square`. /// Accept or ignore a move from `square` to `ray_square`.
/// ///
/// This function checks that the move is legal, unless `pseudo_legal` /// This function checks that the move is legal. It assumes that
/// is true. It assumes that `ray_square` is either empty or an enemy /// `ray_square` is either empty or an enemy piece, but not a friend
/// piece, but not a friend piece: they should have been filtered. /// piece: they should have been filtered.
/// ///
/// 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(
@ -390,91 +167,105 @@ fn inspect_move(
game_state: &GameState, game_state: &GameState,
square: Square, square: Square,
ray_square: Square, ray_square: Square,
pseudo_legal: bool attacked_bb: Bitboard
) -> Option<Move> { ) -> Option<Move> {
let m = Move::new(square, ray_square); 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) Some(m)
} else { } else {
None 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<Move> {
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. /// Check if a move is illegal.
fn is_illegal(board: &Board, game_state: &GameState, m: &Move) -> bool { fn is_illegal(
if let Some(mut king_square) = board.find_king(game_state.color) { board: &Board,
let mut hypothetic_board = board.clone(); game_state: &GameState,
m.apply_to_board(&mut hypothetic_board); m: &Move,
attacked_bb: Bitboard
) -> bool {
// A move is illegal if the king ends up in check. // A move is illegal if the king ends up in check.
// If king moves, use its new position. let king_square = if board.get_piece_on(m.source) == KING {
if m.source == king_square { m.dest
king_square = m.dest } else {
} if let Some(king) = board.find_king(game_state.color) { king } else { return false }
// Check if the move makes the player king in check. };
if is_attacked(&hypothetic_board, &game_state, king_square) { attacked_bb & bit_pos(king_square) != 0
return true
}
}
false
} }
/// Return true if the piece on `square` is attacked. /// Get possible castles.
/// ///
/// Check all possible enemy moves and return true when one of them /// Here are the rules that should ALL be respected:
/// ends up attacking the position. /// 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 /// Rule 1 is NOT checked by this method to avoid creating empty vecs.
/// square, i.e. if the piece on `square` is white, the game state /// Check it in the caller.
/// should tell that it is white turn. If `square` is empty, simply fn get_king_castles(
/// check if it is getting attacked by the opposite player. board: &Board,
fn is_attacked(board: &Board, game_state: &GameState, square: Square) -> bool { game_state: &GameState,
let mut enemy_game_state = game_state.clone(); square: Square,
enemy_game_state.color = opposite(game_state.color); color: Color,
// Do not attempt to commit moves, just check for attacked squares. attacked_bb: Bitboard,
let enemy_moves = get_player_moves(board, &enemy_game_state, true); moves: &mut Vec<Move>
for m in enemy_moves.iter() { ) {
if square == m.dest { let combined_bb = board.combined();
return true
// 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)] #[cfg(test)]
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_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 b = Board::new();
let gs = GameState::new(); let 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, false); let moves = get_player_moves(&b, &gs);
assert_eq!(moves.len(), 20); 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. // 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_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))); 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. // 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_piece_moves(&b, &gs, E2, WHITE, false); let moves = get_legal_piece_moves(&b, &gs, E2, WHITE);
assert_eq!(moves.len(), 2); assert_eq!(moves.len(), 2);
assert!(moves.contains(&Move::new(E2, E3))); assert!(moves.contains(&Move::new(E2, E3)));
assert!(moves.contains(&Move::new(E2, E4))); 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. // 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_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))); assert!(moves.len() == 1 && moves.contains(&Move::new(E2, 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_piece_moves(&b, &gs, E2, WHITE, false); let moves = get_legal_piece_moves(&b, &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_piece_moves(&b, &gs, E2, WHITE, false); let moves = get_legal_piece_moves(&b, &gs, E2, WHITE);
assert_eq!(moves.len(), 0); assert_eq!(moves.len(), 0);
// 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(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))); assert!(moves.len() == 1 && moves.contains(&Move::new(E2, F3)));
b.set_square(D3, BLACK, PAWN); 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_eq!(moves.len(), 2);
assert!(moves.contains( &Move::new(E2, F3) )); assert!(moves.contains( &Move::new(E2, F3) ));
assert!(moves.contains( &Move::new(E2, D3) )); 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. // 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_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))); 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. // 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_piece_moves(&b, &gs, D4, WHITE, false); let moves = get_legal_piece_moves(&b, &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.contains(&Move::new(D4, E5)));
@ -555,11 +346,11 @@ mod tests {
// 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_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. // 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_piece_moves(&b, &gs, D4, WHITE, false).len(), 12); assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 12);
} }
#[test] #[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 // 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_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. // If on a side if has only 4 moves.
b.set_square(A4, WHITE, KNIGHT); 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. // And in a corner, only 2 moves.
b.set_square(A1, WHITE, KNIGHT); 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. // 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_piece_moves(&b, &gs, A1, WHITE, false).len(), 0); assert_eq!(get_legal_piece_moves(&b, &gs, A1, WHITE).len(), 0);
} }
#[test] #[test]
@ -592,11 +383,11 @@ mod tests {
let gs = GameState::new(); let gs = GameState::new();
b.set_square(D4, WHITE, ROOK); 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); 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); 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] #[test]
@ -605,7 +396,7 @@ mod tests {
let gs = GameState::new(); let gs = GameState::new();
b.set_square(D4, WHITE, QUEEN); 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] #[test]
@ -615,23 +406,23 @@ 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_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); 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. // 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_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. // 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_piece_moves(&b, &gs, E8, BLACK, false).len(), 5 + 2); assert_eq!(get_legal_piece_moves(&b, &gs, E8, BLACK).len(), 5 + 2);
} }
#[test] #[test]
@ -646,21 +437,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_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); 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));
}
} }