rules: rework move generation for bitboards
This commit is contained in:
parent
5a6893acc7
commit
97444db39c
56
src/board.rs
56
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();
|
||||
|
|
|
@ -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<i8>, 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.
|
||||
]
|
||||
];
|
||||
|
|
475
src/rules.rs
475
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<Move> {
|
||||
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<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,
|
||||
attacked_bb: Bitboard,
|
||||
) -> Vec<Move> {
|
||||
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
|
||||
match piece {
|
||||
PAWN => {
|
||||
board.get_pawn_progresses(square, color)
|
||||
| board.get_pawn_captures(square, color)
|
||||
}
|
||||
|
||||
fn get_bishop_moves(
|
||||
board: &Board,
|
||||
game_state: &GameState,
|
||||
square: Square,
|
||||
color: Color,
|
||||
pseudo_legal: bool,
|
||||
) -> Vec<Move> {
|
||||
get_moves_from_bb(
|
||||
board,
|
||||
game_state,
|
||||
board.get_bishop_rays(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,
|
||||
BISHOP,
|
||||
pseudo_legal
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
piece,
|
||||
attacked_bb,
|
||||
&mut moves
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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<Move> {
|
||||
let mut moves = Vec::with_capacity(count_bits(bitboard).into());
|
||||
attacked_bb: Bitboard,
|
||||
moves: &mut Vec<Move>
|
||||
) {
|
||||
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<Move> {
|
||||
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<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.
|
||||
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);
|
||||
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.
|
||||
// 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
|
||||
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<Move>
|
||||
) {
|
||||
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<Move> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue