From d958e617d091b9535559f535c2112001d4006420 Mon Sep 17 00:00:00 2001 From: dece Date: Sat, 20 Jun 2020 18:46:37 +0200 Subject: [PATCH] stats: work with bitboards --- src/board.rs | 95 +++++++++++++++++++++++++++++++++++++++-- src/stats.rs | 116 +++++++++++++++++++++++---------------------------- 2 files changed, 143 insertions(+), 68 deletions(-) diff --git a/src/board.rs b/src/board.rs index 6484ab7..8968b9a 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,8 +1,5 @@ //! Basic type definitions and functions. -/// Bitboard for color or piece bits. -pub type Bitboard = u64; - /// Color type, used to index `Board.color`. pub type Color = usize; @@ -137,6 +134,56 @@ pub fn sq_to_string(square: Square) -> String { String::from_utf8_lossy(&bytes).to_string() } +/// Bitboard for color or piece bits. +pub type Bitboard = u64; + +pub const FILES: [Bitboard; 8] = [ + 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111, + 0b00000000_00000000_00000000_00000000_00000000_00000000_11111111_00000000, + 0b00000000_00000000_00000000_00000000_00000000_11111111_00000000_00000000, + 0b00000000_00000000_00000000_00000000_11111111_00000000_00000000_00000000, + 0b00000000_00000000_00000000_11111111_00000000_00000000_00000000_00000000, + 0b00000000_00000000_11111111_00000000_00000000_00000000_00000000_00000000, + 0b00000000_11111111_00000000_00000000_00000000_00000000_00000000_00000000, + 0b11111111_00000000_00000000_00000000_00000000_00000000_00000000_00000000, +]; + +/// Get the bitboard of bits before the square ("left-most" bits). +#[inline] +const fn bits_before(file: i8, rank: i8) -> Bitboard { + (1 << sq(file, rank)) - 1 +} + +/// Get the bitboard of bits after the square ("right-most" bits). +#[inline] +const fn bits_after(file: i8, rank: i8) -> Bitboard { + !bits_before(file, rank) << 1 +} + +/// Get the bitboard of squares on lower ranks of the file. +#[inline] +pub const fn before_on_file(file: i8, rank: i8) -> Bitboard { + FILES[file as usize] & bits_before(file, rank) +} + +/// Get the bitboard of squares on upper ranks of the file. +#[inline] +pub const fn after_on_file(file: i8, rank: i8) -> Bitboard { + FILES[file as usize] & bits_after(file, rank) +} + +/// Get the bitboard of squares on lower ranks of the `square` file. +#[inline] +pub const fn before_on_square_file(square: Square) -> Bitboard { + before_on_file(sq_file(square), sq_rank(square)) +} + +/// Get the bitboard of squares on upper ranks of the `square` file. +#[inline] +pub const fn after_on_square_file(square: Square) -> Bitboard { + after_on_file(sq_file(square), sq_rank(square)) +} + /// Board representation with color/piece bitboards. #[derive(Clone, PartialEq)] pub struct Board { @@ -221,7 +268,7 @@ impl Board { /// Get the bitboard of a piece type for this color. #[inline] - pub fn by_color_piece(&self, color: Color, piece: Piece) -> Bitboard { + pub fn by_color_and_piece(&self, color: Color, piece: Piece) -> Bitboard { self.by_color(color) & self.by_piece(piece) } @@ -333,12 +380,16 @@ impl Board { mod tests { use super::*; + // Color + #[test] fn test_opposite() { assert_eq!(opposite(WHITE), BLACK); assert_eq!(opposite(BLACK), WHITE); } + // Square + #[test] fn test_sq_from_string() { assert_eq!(sq_from_string("a1"), A1); @@ -356,6 +407,42 @@ mod tests { assert_eq!(sq_to_string(H8), "h8"); } + // Bitboard + + #[test] + fn test_before_on_square_file() { + // Only should the 4 lowest files for readability. + assert_eq!(before_on_square_file(A1), 0b00000000_00000000_00000000_00000000); + assert_eq!(before_on_square_file(A2), 0b00000000_00000000_00000000_00000001); + assert_eq!(before_on_square_file(A4), 0b00000000_00000000_00000000_00000111); + assert_eq!(before_on_square_file(A8), 0b00000000_00000000_00000000_01111111); + assert_eq!(before_on_square_file(B1), 0b00000000_00000000_00000000_00000000); + assert_eq!(before_on_square_file(C1), 0b00000000_00000000_00000000_00000000); + assert_eq!(before_on_square_file(C4), 0b00000000_00000111_00000000_00000000); + // 4 highest files. + assert_eq!(before_on_square_file(H4), 0b00000111_00000000_00000000_00000000 << 32); + assert_eq!(before_on_square_file(H7), 0b00111111_00000000_00000000_00000000 << 32); + assert_eq!(before_on_square_file(H8), 0b01111111_00000000_00000000_00000000 << 32); + } + + #[test] + fn test_after_on_file() { + assert_eq!(after_on_square_file(A1), 0b00000000_00000000_00000000_11111110); + assert_eq!(after_on_square_file(A2), 0b00000000_00000000_00000000_11111100); + assert_eq!(after_on_square_file(A4), 0b00000000_00000000_00000000_11110000); + assert_eq!(after_on_square_file(A8), 0b00000000_00000000_00000000_00000000); + assert_eq!(after_on_square_file(B1), 0b00000000_00000000_11111110_00000000); + assert_eq!(after_on_square_file(C1), 0b00000000_11111110_00000000_00000000); + assert_eq!(after_on_square_file(C4), 0b00000000_11110000_00000000_00000000); + assert_eq!(after_on_square_file(C8), 0b00000000_00000000_00000000_00000000); + // 4 highest files. + assert_eq!(after_on_square_file(H4), 0b11110000_00000000_00000000_00000000 << 32); + assert_eq!(after_on_square_file(H7), 0b10000000_00000000_00000000_00000000 << 32); + assert_eq!(after_on_square_file(H8), 0b00000000_00000000_00000000_00000000 << 32); + } + + // Board + #[test] fn test_new_from_fen() { let b1 = Board::new(); diff --git a/src/stats.rs b/src/stats.rs index 04dd0a2..2d06437 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -1,7 +1,7 @@ //! Board statistics used for heuristics. use crate::board::*; -use crate::rules::{GameState, get_player_moves}; +use crate::rules::{GameState, get_player_moves, POS_MIN, POS_MAX}; /// Storage for board pieces stats. #[derive(Debug, Clone, PartialEq)] @@ -78,75 +78,63 @@ impl BoardStats { KING => self.num_kings += 1, PAWN => { self.num_pawns += 1; - // FIXME redo pawn stats properly - // let mut doubled = false; - // let mut isolated = true; - // let mut backward = true; - // for r in 0..8 { - // // Check for doubled pawns. - // if !doubled { - // let other_color = board.get_color(sq(file, r)); - // is_piece(get_square(board, &(pos_f, r)), color|SQ_P) && r != pos_r - // doubled = true; - // } - // // Check for isolated pawns. - // if - // isolated && - // ( - // // Check on the left file if not on a-file... - // ( - // pos_f > POS_MIN && - // is_piece(get_square(board, &(pos_f - 1, r)), color|SQ_P) - // ) || - // // Check on the right file if not on h-file... - // ( - // pos_f < POS_MAX && - // is_piece(get_square(board, &(pos_f + 1, r)), color|SQ_P) - // ) - // ) - // { - // isolated = false; - // } - // // Check for backward pawns. - // if backward { - // if color == SQ_WH && r <= pos_r { - // if ( - // pos_f > POS_MIN && - // is_type(get_square(board, &(pos_f - 1, r)), SQ_P) - // ) || ( - // pos_f < POS_MAX && - // is_type(get_square(board, &(pos_f + 1, r)), SQ_P) - // ) { - // backward = false; - // } - // } else if color == SQ_BL && r >= pos_r { - // if ( - // pos_f > POS_MIN && - // is_type(get_square(board, &(pos_f - 1, r)), SQ_P) - // ) || ( - // pos_f < POS_MAX && - // is_type(get_square(board, &(pos_f + 1, r)), SQ_P) - // ) { - // backward = false; - // } - // } - // } - // } - // if doubled { - // self.num_doubled_pawns += 1; - // } - // if isolated { - // self.num_isolated_pawns += 1; - // } - // if backward { - // self.num_backward_pawns += 1; - // } + let pawn_bb = board.by_color_and_piece(color, PAWN); + + // Check for doubled pawns. + let file_bb = FILES[file as usize]; + if (pawn_bb ^ bit_pos(square)) & file_bb != 0 { + self.num_doubled_pawns += 1; + } + + // Check for isolated and backward pawns. + let (iso_on_prev_file, bw_on_prev_file) = if file > POS_MIN { + self.find_isolated_and_backward(pawn_bb, square, color, file - 1) + } else { + (true, true) + }; + let (iso_on_next_file, bw_on_next_file) = if file < POS_MAX { + self.find_isolated_and_backward(pawn_bb, square, color, file + 1) + } else { + (true, true) + }; + if iso_on_prev_file && iso_on_next_file { + self.num_isolated_pawns += 1; + } + if bw_on_prev_file && bw_on_next_file { + self.num_backward_pawns += 1; + } }, _ => {} } } } } + + /// Find isolated and backward pawns from `square` perspective. + /// + /// `bb` is the bitboard of `color`. `square` is only used to have + /// the reference rank. `file` is the file to inspect. To detect + /// isolated and backward pawns, `bb` should be the bitboard of + /// pawns of `color`. + fn find_isolated_and_backward( + &mut self, + bb: Bitboard, + square: Square, + color: Color, + file: i8 + ) -> (bool, bool) { + if bb & FILES[file as usize] == 0 { + // If the piece is isolated for this file, it's backward as well. + (true, true) + } else { + let backward_file_bb = if color == WHITE { + before_on_file(file, sq_rank(square)) | bit_pos(sq(file, sq_rank(square))) + } else { + after_on_file(file, sq_rank(square)) | bit_pos(sq(file, sq_rank(square))) + }; + (false, bb & backward_file_bb == 0) + } + } } impl std::fmt::Display for BoardStats {