From ce62d3ab3a796f1651a136fe0d1782e6bd98f6f2 Mon Sep 17 00:00:00 2001 From: dece Date: Sun, 21 Jun 2020 01:26:04 +0200 Subject: [PATCH] rules: use generic bitboard move generation For bishops, knights, rooks and queens that is. --- res/scripts/gen_king_rays.py | 30 +++++ res/scripts/gen_knight_rays.py | 1 + src/board.rs | 79 ++++++++++- src/rules.rs | 235 +++++++++++++-------------------- 4 files changed, 196 insertions(+), 149 deletions(-) create mode 100755 res/scripts/gen_king_rays.py diff --git a/res/scripts/gen_king_rays.py b/res/scripts/gen_king_rays.py new file mode 100755 index 0000000..74371f9 --- /dev/null +++ b/res/scripts/gen_king_rays.py @@ -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()))) diff --git a/res/scripts/gen_knight_rays.py b/res/scripts/gen_knight_rays.py index f30e6dd..04fc6e3 100755 --- a/res/scripts/gen_knight_rays.py +++ b/res/scripts/gen_knight_rays.py @@ -2,6 +2,7 @@ """Pre-compute knight ray bitboards for each square.""" TEMPLATE = """\ +/// Pre-computed knight rays. const KNIGHT_RAYS: [Bitboard; 64] = [ {} ]; diff --git a/src/board.rs b/src/board.rs index 62263c0..b03ce08 100644 --- a/src/board.rs +++ b/src/board.rs @@ -99,6 +99,7 @@ pub const H5: Square = 60; pub const H6: Square = 61; pub const H7: Square = 62; pub const H8: Square = 63; +pub const NUM_SQUARES: Square = 64; /// Get square from file and rank, both starting from 0. #[inline] @@ -192,9 +193,8 @@ pub const fn after_on_file(file: i8, rank: i8) -> Bitboard { FILES[file as usize] & bits_after(file, rank) } -/// Debug only: count positive bits of the bitboard. -#[allow(dead_code)] -pub(crate) fn count_bits(bitboard: Bitboard) -> u8 { +/// Count positive bits of the bitboard. +pub fn count_bits(bitboard: Bitboard) -> u8 { let mut bitboard = bitboard; let mut count = 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. /// Pre-computed knight rays. const KNIGHT_RAYS: [Bitboard; 64] = [ @@ -477,6 +546,10 @@ impl Board { 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. pub(crate) fn draw(&self, f: &mut dyn std::io::Write) { let cbb = self.combined(); diff --git a/src/rules.rs b/src/rules.rs index 11627e6..cc1017b 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -178,37 +178,15 @@ fn get_bishop_moves( color: Color, pseudo_legal: bool, ) -> Vec { - let (f, r) = (sq_file(square), sq_rank(square)); - let mut views = [true; 4]; // Store diagonals where a piece blocks commit. - let mut moves = Vec::with_capacity(8); - for dist in 1..=7 { - for (dir, offset) in [(1, -1), (1, 1), (-1, 1), (-1, -1)].iter().enumerate() { - if !views[dir] { - continue - } - - // 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 + get_moves_from_bb( + board, + game_state, + board.get_bishop_rays(square, color), + square, + color, + BISHOP, + pseudo_legal + ) } fn get_knight_moves( @@ -218,25 +196,15 @@ fn get_knight_moves( color: Color, pseudo_legal: bool, ) -> Vec { - let (f, r) = (sq_file(square), sq_rank(square)); - let mut moves = Vec::with_capacity(8); - for offset in [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)].iter() { - let ray_f = f + offset.0; - if ray_f < POS_MIN || ray_f > POS_MAX { - continue - } - let ray_r = r + offset.1; - 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 + get_moves_from_bb( + board, + game_state, + board.get_knight_rays(square, color), + square, + color, + KNIGHT, + pseudo_legal + ) } fn get_rook_moves( @@ -246,36 +214,15 @@ fn get_rook_moves( color: Color, pseudo_legal: bool, ) -> Vec { - let (f, r) = (sq_file(square), sq_rank(square)); - let mut moves = Vec::with_capacity(8); - let mut views = [true; 4]; // Store lines where a piece blocks commit. - for dist in 1..=7 { - for (dir, offset) in [(0, 1), (1, 0), (0, -1), (-1, 0)].iter().enumerate() { - if !views[dir] { - continue - } - - 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 + get_moves_from_bb( + board, + game_state, + board.get_rook_rays(square, color), + square, + color, + ROOK, + pseudo_legal + ) } fn get_queen_moves( @@ -285,11 +232,15 @@ fn get_queen_moves( color: Color, pseudo_legal: bool, ) -> Vec { - let mut moves = Vec::with_capacity(16); - // Easy way to get queen moves, but may be a bit quicker if everything was rewritten here. - moves.append(&mut get_bishop_moves(board, game_state, square, color, pseudo_legal)); - moves.append(&mut get_rook_moves(board, game_state, square, color, pseudo_legal)); - moves + get_moves_from_bb( + board, + game_state, + board.get_queen_rays(square, color), + square, + color, + QUEEN, + pseudo_legal + ) } fn get_king_moves( @@ -299,24 +250,15 @@ fn get_king_moves( color: Color, pseudo_legal: bool, ) -> Vec { - let (f, r) = (sq_file(square), sq_rank(square)); - let mut moves = Vec::with_capacity(8); - for offset in [(-1, 1), (0, 1), (1, 1), (-1, 0), (1, 0), (-1, -1), (0, -1), (1, -1)].iter() { - let ray_f = f + offset.0; - if ray_f < POS_MIN || ray_f > POS_MAX { - continue - } - let ray_r = r + offset.1; - 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), - _ => {} - } - } + 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 { @@ -338,6 +280,7 @@ fn get_king_moves( (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 @@ -380,58 +323,58 @@ fn get_king_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 { + 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`. -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, game_state: &GameState, square: Square, ray_square: Square, - color: Color, - commit: bool -) -> MoveType { - if board.is_empty(ray_square) { - let m = Move::new(square, ray_square); - if is_legal(commit, board, game_state, &m) { - MoveType::Simple(m) - } else { - MoveType::CantRegister - } + pseudo_legal: bool +) -> Option { + let m = Move::new(square, ray_square); + if pseudo_legal || !is_illegal(board, game_state, &m) { + Some(m) } else { - let ray_color = board.get_color_on(ray_square); - 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 - } + None } } -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. fn get_capture_move( color1: Color,