rules: use generic bitboard move generation

For bishops, knights, rooks and queens that is.
This commit is contained in:
dece 2020-06-21 01:26:04 +02:00
parent 5efdd5407b
commit ce62d3ab3a
4 changed files with 196 additions and 149 deletions

30
res/scripts/gen_king_rays.py Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env python3
"""Pre-compute king ras bitboards for each square."""
TEMPLATE = """\
/// Pre-computed king rays.
const KING_RAYS: [Bitboard; 64] = [
{}
];
"""
DIRS = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
def bit_pos(square):
return 1 << square
def get_rays():
rays = []
for f in range(8):
for r in range(8):
bitboard = 0
for dir_f, dir_r in DIRS:
ray_f = f + dir_f
ray_r = r + dir_r
if ray_f < 0 or ray_f > 7 or ray_r < 0 or ray_r > 7:
continue
bitboard |= bit_pos(ray_f * 8 + ray_r)
rays.append(" 0b{:064b},".format(bitboard))
return rays
print(TEMPLATE.format("\n".join(get_rays())))

View file

@ -2,6 +2,7 @@
"""Pre-compute knight ray bitboards for each square.""" """Pre-compute knight ray bitboards for each square."""
TEMPLATE = """\ TEMPLATE = """\
/// Pre-computed knight rays.
const KNIGHT_RAYS: [Bitboard; 64] = [ const KNIGHT_RAYS: [Bitboard; 64] = [
{} {}
]; ];

View file

@ -99,6 +99,7 @@ pub const H5: Square = 60;
pub const H6: Square = 61; pub const H6: Square = 61;
pub const H7: Square = 62; pub const H7: Square = 62;
pub const H8: Square = 63; pub const H8: Square = 63;
pub const NUM_SQUARES: Square = 64;
/// Get square from file and rank, both starting from 0. /// Get square from file and rank, both starting from 0.
#[inline] #[inline]
@ -192,9 +193,8 @@ pub const fn after_on_file(file: i8, rank: i8) -> Bitboard {
FILES[file as usize] & bits_after(file, rank) FILES[file as usize] & bits_after(file, rank)
} }
/// Debug only: count positive bits of the bitboard. /// Count positive bits of the bitboard.
#[allow(dead_code)] pub fn count_bits(bitboard: Bitboard) -> u8 {
pub(crate) fn count_bits(bitboard: Bitboard) -> u8 {
let mut bitboard = bitboard; let mut bitboard = bitboard;
let mut count = 0; let mut count = 0;
while bitboard > 0 { while bitboard > 0 {
@ -216,6 +216,75 @@ pub(crate) fn draw_bits(bitboard: Bitboard, f: &mut dyn std::io::Write) {
} }
} }
// Generated by gen_king_rays.py.
/// Pre-computed king rays.
const KING_RAYS: [Bitboard; 64] = [
0b00000000_00000000_00000000_00000000_00000000_00000000_00000011_00000010,
0b00000000_00000000_00000000_00000000_00000000_00000000_00000111_00000101,
0b00000000_00000000_00000000_00000000_00000000_00000000_00001110_00001010,
0b00000000_00000000_00000000_00000000_00000000_00000000_00011100_00010100,
0b00000000_00000000_00000000_00000000_00000000_00000000_00111000_00101000,
0b00000000_00000000_00000000_00000000_00000000_00000000_01110000_01010000,
0b00000000_00000000_00000000_00000000_00000000_00000000_11100000_10100000,
0b00000000_00000000_00000000_00000000_00000000_00000000_11000000_01000000,
0b00000000_00000000_00000000_00000000_00000000_00000011_00000010_00000011,
0b00000000_00000000_00000000_00000000_00000000_00000111_00000101_00000111,
0b00000000_00000000_00000000_00000000_00000000_00001110_00001010_00001110,
0b00000000_00000000_00000000_00000000_00000000_00011100_00010100_00011100,
0b00000000_00000000_00000000_00000000_00000000_00111000_00101000_00111000,
0b00000000_00000000_00000000_00000000_00000000_01110000_01010000_01110000,
0b00000000_00000000_00000000_00000000_00000000_11100000_10100000_11100000,
0b00000000_00000000_00000000_00000000_00000000_11000000_01000000_11000000,
0b00000000_00000000_00000000_00000000_00000011_00000010_00000011_00000000,
0b00000000_00000000_00000000_00000000_00000111_00000101_00000111_00000000,
0b00000000_00000000_00000000_00000000_00001110_00001010_00001110_00000000,
0b00000000_00000000_00000000_00000000_00011100_00010100_00011100_00000000,
0b00000000_00000000_00000000_00000000_00111000_00101000_00111000_00000000,
0b00000000_00000000_00000000_00000000_01110000_01010000_01110000_00000000,
0b00000000_00000000_00000000_00000000_11100000_10100000_11100000_00000000,
0b00000000_00000000_00000000_00000000_11000000_01000000_11000000_00000000,
0b00000000_00000000_00000000_00000011_00000010_00000011_00000000_00000000,
0b00000000_00000000_00000000_00000111_00000101_00000111_00000000_00000000,
0b00000000_00000000_00000000_00001110_00001010_00001110_00000000_00000000,
0b00000000_00000000_00000000_00011100_00010100_00011100_00000000_00000000,
0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000,
0b00000000_00000000_00000000_01110000_01010000_01110000_00000000_00000000,
0b00000000_00000000_00000000_11100000_10100000_11100000_00000000_00000000,
0b00000000_00000000_00000000_11000000_01000000_11000000_00000000_00000000,
0b00000000_00000000_00000011_00000010_00000011_00000000_00000000_00000000,
0b00000000_00000000_00000111_00000101_00000111_00000000_00000000_00000000,
0b00000000_00000000_00001110_00001010_00001110_00000000_00000000_00000000,
0b00000000_00000000_00011100_00010100_00011100_00000000_00000000_00000000,
0b00000000_00000000_00111000_00101000_00111000_00000000_00000000_00000000,
0b00000000_00000000_01110000_01010000_01110000_00000000_00000000_00000000,
0b00000000_00000000_11100000_10100000_11100000_00000000_00000000_00000000,
0b00000000_00000000_11000000_01000000_11000000_00000000_00000000_00000000,
0b00000000_00000011_00000010_00000011_00000000_00000000_00000000_00000000,
0b00000000_00000111_00000101_00000111_00000000_00000000_00000000_00000000,
0b00000000_00001110_00001010_00001110_00000000_00000000_00000000_00000000,
0b00000000_00011100_00010100_00011100_00000000_00000000_00000000_00000000,
0b00000000_00111000_00101000_00111000_00000000_00000000_00000000_00000000,
0b00000000_01110000_01010000_01110000_00000000_00000000_00000000_00000000,
0b00000000_11100000_10100000_11100000_00000000_00000000_00000000_00000000,
0b00000000_11000000_01000000_11000000_00000000_00000000_00000000_00000000,
0b00000011_00000010_00000011_00000000_00000000_00000000_00000000_00000000,
0b00000111_00000101_00000111_00000000_00000000_00000000_00000000_00000000,
0b00001110_00001010_00001110_00000000_00000000_00000000_00000000_00000000,
0b00011100_00010100_00011100_00000000_00000000_00000000_00000000_00000000,
0b00111000_00101000_00111000_00000000_00000000_00000000_00000000_00000000,
0b01110000_01010000_01110000_00000000_00000000_00000000_00000000_00000000,
0b11100000_10100000_11100000_00000000_00000000_00000000_00000000_00000000,
0b11000000_01000000_11000000_00000000_00000000_00000000_00000000_00000000,
0b00000010_00000011_00000000_00000000_00000000_00000000_00000000_00000000,
0b00000101_00000111_00000000_00000000_00000000_00000000_00000000_00000000,
0b00001010_00001110_00000000_00000000_00000000_00000000_00000000_00000000,
0b00010100_00011100_00000000_00000000_00000000_00000000_00000000_00000000,
0b00101000_00111000_00000000_00000000_00000000_00000000_00000000_00000000,
0b01010000_01110000_00000000_00000000_00000000_00000000_00000000_00000000,
0b10100000_11100000_00000000_00000000_00000000_00000000_00000000_00000000,
0b01000000_11000000_00000000_00000000_00000000_00000000_00000000_00000000,
];
// Generated by gen_knight_rays.py. // Generated by gen_knight_rays.py.
/// Pre-computed knight rays. /// Pre-computed knight rays.
const KNIGHT_RAYS: [Bitboard; 64] = [ const KNIGHT_RAYS: [Bitboard; 64] = [
@ -477,6 +546,10 @@ impl Board {
KNIGHT_RAYS[square as usize] & !self.by_color(color) KNIGHT_RAYS[square as usize] & !self.by_color(color)
} }
pub fn get_king_rays(&self, square: Square, color: Color) -> Bitboard {
KING_RAYS[square as usize] & !self.by_color(color)
}
/// Debug only: write a text view of the board. /// Debug only: write a text view of the board.
pub(crate) fn draw(&self, f: &mut dyn std::io::Write) { pub(crate) fn draw(&self, f: &mut dyn std::io::Write) {
let cbb = self.combined(); let cbb = self.combined();

View file

@ -178,37 +178,15 @@ fn get_bishop_moves(
color: Color, color: Color,
pseudo_legal: bool, pseudo_legal: bool,
) -> Vec<Move> { ) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square)); get_moves_from_bb(
let mut views = [true; 4]; // Store diagonals where a piece blocks commit. board,
let mut moves = Vec::with_capacity(8); game_state,
for dist in 1..=7 { board.get_bishop_rays(square, color),
for (dir, offset) in [(1, -1), (1, 1), (-1, 1), (-1, -1)].iter().enumerate() { square,
if !views[dir] { color,
continue BISHOP,
} pseudo_legal
)
// If this position is out of the board, stop looking in that direction.
let ray_f = f + offset.0 * dist;
if ray_f < POS_MIN || ray_f > POS_MAX {
views[dir] = false;
continue
}
let ray_r = r + offset.1 * dist;
if ray_r < POS_MIN || ray_r > POS_MAX {
views[dir] = false;
continue
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) => { moves.push(m) }
MoveType::Capture(m) => { moves.push(m); views[dir] = false; }
MoveType::CantTakeFriend => { views[dir] = false; }
_ => {}
}
}
}
moves
} }
fn get_knight_moves( fn get_knight_moves(
@ -218,25 +196,15 @@ fn get_knight_moves(
color: Color, color: Color,
pseudo_legal: bool, pseudo_legal: bool,
) -> Vec<Move> { ) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square)); get_moves_from_bb(
let mut moves = Vec::with_capacity(8); board,
for offset in [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)].iter() { game_state,
let ray_f = f + offset.0; board.get_knight_rays(square, color),
if ray_f < POS_MIN || ray_f > POS_MAX { square,
continue color,
} KNIGHT,
let ray_r = r + offset.1; pseudo_legal
if ray_r < POS_MIN || ray_r > POS_MAX { )
continue
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) | MoveType::Capture(m) => moves.push(m),
_ => {}
}
}
moves
} }
fn get_rook_moves( fn get_rook_moves(
@ -246,36 +214,15 @@ fn get_rook_moves(
color: Color, color: Color,
pseudo_legal: bool, pseudo_legal: bool,
) -> Vec<Move> { ) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square)); get_moves_from_bb(
let mut moves = Vec::with_capacity(8); board,
let mut views = [true; 4]; // Store lines where a piece blocks commit. game_state,
for dist in 1..=7 { board.get_rook_rays(square, color),
for (dir, offset) in [(0, 1), (1, 0), (0, -1), (-1, 0)].iter().enumerate() { square,
if !views[dir] { color,
continue ROOK,
} pseudo_legal
)
let ray_f = f + offset.0 * dist;
if ray_f < POS_MIN || ray_f > POS_MAX {
views[dir] = false;
continue
}
let ray_r = r + offset.1 * dist;
if ray_r < POS_MIN || ray_r > POS_MAX {
views[dir] = false;
continue
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) => { moves.push(m) }
MoveType::Capture(m) => { moves.push(m); views[dir] = false; }
MoveType::CantTakeFriend => { views[dir] = false; }
_ => {}
}
}
}
moves
} }
fn get_queen_moves( fn get_queen_moves(
@ -285,11 +232,15 @@ fn get_queen_moves(
color: Color, color: Color,
pseudo_legal: bool, pseudo_legal: bool,
) -> Vec<Move> { ) -> Vec<Move> {
let mut moves = Vec::with_capacity(16); get_moves_from_bb(
// Easy way to get queen moves, but may be a bit quicker if everything was rewritten here. board,
moves.append(&mut get_bishop_moves(board, game_state, square, color, pseudo_legal)); game_state,
moves.append(&mut get_rook_moves(board, game_state, square, color, pseudo_legal)); board.get_queen_rays(square, color),
moves square,
color,
QUEEN,
pseudo_legal
)
} }
fn get_king_moves( fn get_king_moves(
@ -299,24 +250,15 @@ fn get_king_moves(
color: Color, color: Color,
pseudo_legal: bool, pseudo_legal: bool,
) -> Vec<Move> { ) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square)); let mut moves = get_moves_from_bb(
let mut moves = Vec::with_capacity(8); board,
for offset in [(-1, 1), (0, 1), (1, 1), (-1, 0), (1, 0), (-1, -1), (0, -1), (1, -1)].iter() { game_state,
let ray_f = f + offset.0; board.get_king_rays(square, color),
if ray_f < POS_MIN || ray_f > POS_MAX { square,
continue color,
} KING,
let ray_r = r + offset.1; pseudo_legal
if ray_r < POS_MIN || ray_r > POS_MAX { );
continue
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) | MoveType::Capture(m) => moves.push(m),
_ => {}
}
}
// Stop here for pseudo legal moves as castling is not considered along with them. // Stop here for pseudo legal moves as castling is not considered along with them.
if pseudo_legal { if pseudo_legal {
@ -338,6 +280,7 @@ fn get_king_moves(
(7, CASTLING_BL_MASK) (7, CASTLING_BL_MASK)
}; };
let r = sq_rank(square);
// Check for castling if the king is on its castling rank (R1) // Check for castling if the king is on its castling rank (R1)
// and is not in check (R4). // and is not in check (R4).
if if
@ -380,58 +323,58 @@ fn get_king_moves(
moves moves
} }
/// Get moves from this ray bitboard.
fn get_moves_from_bb(
board: &Board,
game_state: &GameState,
bitboard: Bitboard,
square: Square,
color: Color,
piece: Piece,
pseudo_legal: bool
) -> Vec<Move> {
let mut moves = Vec::with_capacity(count_bits(bitboard).into());
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) {
// Automatic queen promotion for pawns moving to the opposite rank.
if
piece == PAWN
&& (color == WHITE && sq_rank(ray_square) == RANK_8)
|| (color == BLACK && sq_rank(ray_square) == RANK_1)
{
m.promotion = Some(QUEEN);
}
moves.push(m);
}
}
moves
}
/// Accept or ignore a move from `square` to `ray_square`. /// Accept or ignore a move from `square` to `ray_square`.
fn get_move_type( ///
/// 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 does not set promotions for pawns reaching last rank.
fn inspect_move(
board: &Board, board: &Board,
game_state: &GameState, game_state: &GameState,
square: Square, square: Square,
ray_square: Square, ray_square: Square,
color: Color, pseudo_legal: bool
commit: bool ) -> Option<Move> {
) -> MoveType { let m = Move::new(square, ray_square);
if board.is_empty(ray_square) { if pseudo_legal || !is_illegal(board, game_state, &m) {
let m = Move::new(square, ray_square); Some(m)
if is_legal(commit, board, game_state, &m) {
MoveType::Simple(m)
} else {
MoveType::CantRegister
}
} else { } else {
let ray_color = board.get_color_on(ray_square); None
if let Some(m) = get_capture_move(color, square, ray_color, ray_square, false) {
if is_legal(commit, board, game_state, &m) {
MoveType::Capture(m)
} else {
MoveType::CantRegister
}
} else {
MoveType::CantTakeFriend
}
} }
} }
enum MoveType {
/// Move to an empty square.
Simple(Move),
/// Capture an enemy piece.
Capture(Move),
/// May be illegal, or should not be committed, e.g. to test rays.
CantRegister,
/// Can't capture a friend piece.
CantTakeFriend,
}
/// Return true if `pseudo_legal` is true, or the move is not illegal,
///
/// Committing a move means that it can be safely played afterwards.
/// Sometimes it is not what is needed to accept a move in a collection
/// of moves, e.g. when simply checking if some moves would make a
/// previous move illegal.
#[inline]
fn is_legal(pseudo_legal: bool, board: &Board, game_state: &GameState, m: &Move) -> bool {
pseudo_legal || !is_illegal(board, game_state, m)
}
/// Return a move from `square1` to `square2` if colors are opposite. /// Return a move from `square1` to `square2` if colors are opposite.
fn get_capture_move( fn get_capture_move(
color1: Color, color1: Color,