board: make all board types use bitboards

This commit is contained in:
dece 2020-06-19 02:06:48 +02:00
parent 8485714a24
commit ea58c72436
2 changed files with 317 additions and 277 deletions

5
res/scripts/gen_squares.py Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python3
for f in range(8):
for r in range(8):
print("pub const {}{}: Pos = {};".format(chr(f + 65), r + 1, f * 8 + r))

View file

@ -1,132 +1,147 @@
//! Basic type definitions and functions.
// Piece type flags.
pub const SQ_E: u8 = 0;
pub const SQ_P: u8 = 0b00000001;
pub const SQ_B: u8 = 0b00000010;
pub const SQ_N: u8 = 0b00000100;
pub const SQ_R: u8 = 0b00001000;
pub const SQ_Q: u8 = 0b00010000;
pub const SQ_K: u8 = 0b00100000;
pub const SQ_TYPE_MASK: u8 = 0b00111111;
// Piece color flags.
pub const SQ_WH: u8 = 0b01000000;
pub const SQ_BL: u8 = 0b10000000;
pub const SQ_COLOR_MASK: u8 = 0b11000000;
// Piece flags helpers.
pub const SQ_WH_P: u8 = SQ_WH|SQ_P;
pub const SQ_WH_B: u8 = SQ_WH|SQ_B;
pub const SQ_WH_N: u8 = SQ_WH|SQ_N;
pub const SQ_WH_R: u8 = SQ_WH|SQ_R;
pub const SQ_WH_Q: u8 = SQ_WH|SQ_Q;
pub const SQ_WH_K: u8 = SQ_WH|SQ_K;
pub const SQ_BL_P: u8 = SQ_BL|SQ_P;
pub const SQ_BL_B: u8 = SQ_BL|SQ_B;
pub const SQ_BL_N: u8 = SQ_BL|SQ_N;
pub const SQ_BL_R: u8 = SQ_BL|SQ_R;
pub const SQ_BL_Q: u8 = SQ_BL|SQ_Q;
pub const SQ_BL_K: u8 = SQ_BL|SQ_K;
#[inline]
pub const fn has_flag(i: u8, flag: u8) -> bool { i & flag == flag }
// Wrappers for clearer naming.
/// Get type of piece on square, without color.
#[inline]
pub const fn get_type(square: u8) -> u8 { square & SQ_TYPE_MASK }
/// Return true if the piece on this square is of type `piece_type`.
#[inline]
pub const fn is_type(square: u8, piece_type: u8) -> bool { get_type(square) == piece_type }
/// Return true if the piece on this square has this color.
#[inline]
pub const fn is_color(square: u8, color: u8) -> bool { has_flag(square, color) }
/// Return true if this square has a white piece.
#[inline]
pub const fn is_white(square: u8) -> bool { is_color(square, SQ_WH) }
/// Return true if this square has a black piece.
#[inline]
pub const fn is_black(square: u8) -> bool { is_color(square, SQ_BL) }
/// Return the color of the piece on this square.
#[inline]
pub const fn get_color(square: u8) -> u8 { square & SQ_COLOR_MASK }
/// Return true if the piece on this square is the same as `piece`.
#[inline]
pub const fn is_piece(square: u8, piece: u8) -> bool { has_flag(square, piece) }
/// Get opposite color.
#[inline]
pub const fn opposite(color: u8) -> u8 { color ^ SQ_COLOR_MASK }
/// Pretty-print a color.
pub fn color_to_string(color: u8) -> String {
match color {
SQ_WH => "white".to_string(),
SQ_BL => "black".to_string(),
_ => panic!("Unknown color {}", color),
}
}
/// Minimum allowed value for stored Pos components.
pub const POS_MIN: i8 = 0;
/// Maximum allowed value for stored Pos components.
pub const POS_MAX: i8 = 7;
/// Coords (file, rank) of a square on a board, both components are in [0, 7].
pub type Pos = (i8, i8);
/// Check if a Pos component is in the [0, 7] range.
#[inline]
pub fn is_valid_pos_c(component: i8) -> bool { component >= 0 && component <= 7 }
/// Check if both `pos` components are valid.
#[inline]
pub fn is_valid_pos(pos: Pos) -> bool { is_valid_pos_c(pos.0) && is_valid_pos_c(pos.1) }
/// Convert string coordinates to Pos.
///
/// `s` has to be valid UTF8, or the very least ASCII because chars
/// are interpreted as raw bytes, and lowercase.
#[inline]
pub const fn pos(s: &str) -> Pos {
let chars = s.as_bytes();
((chars[0] - 0x61) as i8, (chars[1] - 0x31) as i8)
}
/// Return string coordinates from Pos.
pub fn pos_string(p: &Pos) -> String {
let mut bytes = [0u8; 2];
bytes[0] = (p.0 + 0x61) as u8;
bytes[1] = (p.1 + 0x31) as u8;
String::from_utf8_lossy(&bytes).to_string()
}
/// Bitboard for color or piece bits.
pub type Bitboard = u64;
const BB_WH: u8 = 0;
const BB_BL: u8 = 1;
const BB_P: u8 = 0;
const BB_B: u8 = 1;
const BB_N: u8 = 2;
const BB_R: u8 = 3;
const BB_Q: u8 = 4;
const BB_K: u8 = 5;
/// Color type, used to index `Board.color`.
pub type Color = usize;
/// Board representation with color/piece bitboards.
pub struct Board {
pub color: [Bitboard; 2],
pub piece: [Bitboard; 6],
const WHITE: usize = 0;
const BLACK: usize = 1;
const NUM_COLORS: usize = 2;
/// Get opposite color.
#[inline]
pub const fn opposite(color: Color) -> Color { color ^ 1 }
/// Pretty-print a color.
pub fn color_to_string(color: Color) -> String {
match color {
0 => "white".to_string(),
1 => "black".to_string(),
_ => panic!("Unknown color {}", color),
}
}
/// Generate the board of a new game.
pub const fn new() -> Board {
/// Piece type, used to index `Board.piece`.
pub type Piece = usize;
const PAWN: usize = 0;
const BISHOP: usize = 1;
const KNIGHT: usize = 2;
const ROOK: usize = 3;
const QUEEN: usize = 4;
const KING: usize = 5;
const NUM_PIECES: usize = 6;
/// Coords (file, rank) of a square on a board.
pub type Square = i8;
// Generated by gen_squares.py.
pub const A1: Square = 0;
pub const A2: Square = 1;
pub const A3: Square = 2;
pub const A4: Square = 3;
pub const A5: Square = 4;
pub const A6: Square = 5;
pub const A7: Square = 6;
pub const A8: Square = 7;
pub const B1: Square = 8;
pub const B2: Square = 9;
pub const B3: Square = 10;
pub const B4: Square = 11;
pub const B5: Square = 12;
pub const B6: Square = 13;
pub const B7: Square = 14;
pub const B8: Square = 15;
pub const C1: Square = 16;
pub const C2: Square = 17;
pub const C3: Square = 18;
pub const C4: Square = 19;
pub const C5: Square = 20;
pub const C6: Square = 21;
pub const C7: Square = 22;
pub const C8: Square = 23;
pub const D1: Square = 24;
pub const D2: Square = 25;
pub const D3: Square = 26;
pub const D4: Square = 27;
pub const D5: Square = 28;
pub const D6: Square = 29;
pub const D7: Square = 30;
pub const D8: Square = 31;
pub const E1: Square = 32;
pub const E2: Square = 33;
pub const E3: Square = 34;
pub const E4: Square = 35;
pub const E5: Square = 36;
pub const E6: Square = 37;
pub const E7: Square = 38;
pub const E8: Square = 39;
pub const F1: Square = 40;
pub const F2: Square = 41;
pub const F3: Square = 42;
pub const F4: Square = 43;
pub const F5: Square = 44;
pub const F6: Square = 45;
pub const F7: Square = 46;
pub const F8: Square = 47;
pub const G1: Square = 48;
pub const G2: Square = 49;
pub const G3: Square = 50;
pub const G4: Square = 51;
pub const G5: Square = 52;
pub const G6: Square = 53;
pub const G7: Square = 54;
pub const G8: Square = 55;
pub const H1: Square = 56;
pub const H2: Square = 57;
pub const H3: Square = 58;
pub const H4: Square = 59;
pub const H5: Square = 60;
pub const H6: Square = 61;
pub const H7: Square = 62;
pub const H8: Square = 63;
/// Get bit mask of `p` in a bitboard.
#[inline]
pub const fn bit_pos(square: Square) -> u64 { 1 << square }
/// Convert string coordinates to Square.
///
/// `s` has to be valid UTF8, or the very least ASCII because chars
/// are interpreted as raw bytes, and lowercase.
#[inline]
pub const fn sq_from_string(square: &str) -> Square {
let chars = square.as_bytes();
(chars[0] - 0x61) as i8 * 8 + (chars[1] - 0x31) as i8
}
/// Return string coordinates from Square.
pub fn sq_to_string(square: Square) -> String {
let mut bytes = [0u8; 2];
bytes[0] = ((square / 8) + 0x61) as u8;
bytes[1] = ((square % 8) + 0x31) as u8;
String::from_utf8_lossy(&bytes).to_string()
}
/// Board representation with color/piece bitboards.
#[derive(PartialEq)]
pub struct Board {
pub colors: [Bitboard; 2],
pub pieces: [Bitboard; 6],
}
// Factories.
impl Board {
/// Generate the board of a new game.
pub const fn new() -> Board {
Board {
color: [
colors: [
0b11000000_11000000_11000000_11000000_11000000_11000000_11000000_11000000,
0b00000011_00000011_00000011_00000011_00000011_00000011_00000011_00000011,
],
piece: [
pieces: [
0b01000010_01000010_01000010_01000010_01000010_01000010_01000010_01000010,
0b00000000_00000000_10000001_00000000_00000000_10000001_00000000_00000000,
0b00000000_10000001_00000000_00000000_00000000_00000000_10000001_00000000,
@ -135,127 +150,145 @@ pub const fn new() -> Board {
0b00000000_00000000_00000000_00000000_10000001_00000000_00000000_00000000,
]
}
}
/// Generate an empty board.
pub const fn new_empty() -> Board {
Board {
color: [0; 2],
piece: [0; 6],
}
}
/// Generate a board from a FEN placement string.
pub fn new_from_fen(fen: &str) -> Board {
let mut board = [SQ_E; 64];
/// Generate an empty board.
pub const fn new_empty() -> Board {
Board {
colors: [0; 2],
pieces: [0; 6],
}
}
/// Generate a board from a FEN placement string.
pub fn new_from_fen(fen: &str) -> Board {
let mut board = Board::new_empty();
let mut f = 0;
let mut r = 7;
for c in fen.chars() {
match c {
'r' => { set_square(&mut board, &(f, r), SQ_BL_R); f += 1 }
'n' => { set_square(&mut board, &(f, r), SQ_BL_N); f += 1 }
'b' => { set_square(&mut board, &(f, r), SQ_BL_B); f += 1 }
'q' => { set_square(&mut board, &(f, r), SQ_BL_Q); f += 1 }
'k' => { set_square(&mut board, &(f, r), SQ_BL_K); f += 1 }
'p' => { set_square(&mut board, &(f, r), SQ_BL_P); f += 1 }
'R' => { set_square(&mut board, &(f, r), SQ_WH_R); f += 1 }
'N' => { set_square(&mut board, &(f, r), SQ_WH_N); f += 1 }
'B' => { set_square(&mut board, &(f, r), SQ_WH_B); f += 1 }
'Q' => { set_square(&mut board, &(f, r), SQ_WH_Q); f += 1 }
'K' => { set_square(&mut board, &(f, r), SQ_WH_K); f += 1 }
'P' => { set_square(&mut board, &(f, r), SQ_WH_P); f += 1 }
'r' => { board.set_square(f * 8 + r, BLACK, ROOK); f += 1 }
'n' => { board.set_square(f * 8 + r, BLACK, KNIGHT); f += 1 }
'b' => { board.set_square(f * 8 + r, BLACK, BISHOP); f += 1 }
'q' => { board.set_square(f * 8 + r, BLACK, QUEEN); f += 1 }
'k' => { board.set_square(f * 8 + r, BLACK, KING); f += 1 }
'p' => { board.set_square(f * 8 + r, BLACK, PAWN); f += 1 }
'R' => { board.set_square(f * 8 + r, WHITE, ROOK); f += 1 }
'N' => { board.set_square(f * 8 + r, WHITE, KNIGHT); f += 1 }
'B' => { board.set_square(f * 8 + r, WHITE, BISHOP); f += 1 }
'Q' => { board.set_square(f * 8 + r, WHITE, QUEEN); f += 1 }
'K' => { board.set_square(f * 8 + r, WHITE, KING); f += 1 }
'P' => { board.set_square(f * 8 + r, WHITE, PAWN); f += 1 }
'/' => { f = 0; r -= 1; }
d if d.is_digit(10) => { f += d.to_digit(10).unwrap() as i8 }
_ => break,
}
}
board
}
/// Get value of the square at this position.
#[inline]
pub const fn get_square(board: &Board, coords: &Pos) -> u8 {
board[(coords.0 * 8 + coords.1) as usize]
}
/// Set a new value for the square at this position.
#[inline]
pub fn set_square(board: &mut Board, coords: &Pos, piece: u8) {
board[(coords.0 * 8 + coords.1) as usize] = piece;
}
/// Set the square empty at this position.
#[inline]
pub fn clear_square(board: &mut Board, coords: &Pos) {
set_square(board, coords, SQ_E);
}
/// Move a piece from a position to another, clearing initial square.
#[inline]
pub fn move_piece(board: &mut Board, from: &Pos, to: &Pos) {
set_square(board, &to, get_square(board, &from));
clear_square(board, &from);
}
/// Return true of the square at this position is empty.
#[inline]
pub const fn is_empty(board: &Board, coords: &Pos) -> bool {
get_square(board, coords) == SQ_E
}
/// Return an iterator over the pieces of the board along with pos.
pub fn get_piece_iterator<'a>(board: &'a Board) -> Box<dyn Iterator<Item = (u8, Pos)> + 'a> {
Box::new(
board.iter().enumerate()
.filter(|(_, s)| **s != SQ_E)
.map(|(i, s)| (*s, ((i / 8) as i8, (i % 8) as i8)))
)
}
/// Find the king of `color`.
pub fn find_king(board: &Board, color: u8) -> Option<Pos> {
for f in 0..8 {
for r in 0..8 {
let s = get_square(board, &(f, r));
if is_color(s, color) && is_piece(s, SQ_K) {
return Some((f, r))
}
}
impl Board {
/// Get combined white/black pieces bitboard.
#[inline]
pub fn combined(&self) -> Bitboard {
self.colors[WHITE] | self.colors[BLACK]
}
/// Get color type at position. It must hold a piece!
pub fn get_color(&self, p: Square) -> Color {
let bp = bit_pos(p);
if (self.colors[WHITE] & bp) == 1 { WHITE }
else if (self.pieces[BLACK] & bp) == 1 { BLACK }
else { panic!("Empty square.") }
}
/// Get piece type at position. It must hold a piece!
pub fn get_piece(&self, p: Square) -> Piece {
let bp = bit_pos(p);
if (self.pieces[PAWN] & bp) == 1 { PAWN }
else if (self.pieces[BISHOP] & bp) == 1 { BISHOP }
else if (self.pieces[KNIGHT] & bp) == 1 { KNIGHT }
else if (self.pieces[ROOK] & bp) == 1 { ROOK }
else if (self.pieces[QUEEN] & bp) == 1 { QUEEN }
else if (self.pieces[KING] & bp) == 1 { KING }
else { panic!("Empty square.") }
}
/// Set a new value for the square at this position.
#[inline]
pub fn set_square(&mut self, p: Square, color: Color, piece: Piece) {
self.colors[color] |= bit_pos(p);
self.pieces[piece] |= bit_pos(p);
}
/// Set the square empty at this position.
#[inline]
pub fn clear_square(&mut self, p: Square) {
for color in 0..NUM_COLORS { self.colors[color] &= !bit_pos(p); }
for piece in 0..NUM_PIECES { self.pieces[piece] &= !bit_pos(p); }
}
/// Move a piece from a position to another, clearing initial position.
#[inline]
pub fn move_square(&mut self, from: Square, to: Square) {
self.set_square(to, self.get_color(from), self.get_piece(from));
self.clear_square(from);
}
/// Find position of this king.
pub fn find_king(&self, color: Color) -> Option<Square> {
let king_bb = self.colors[color] & self.pieces[KING];
for p in 0..64 {
if king_bb & bit_pos(p) == 1 {
return Some(p)
}
}
None
}
}
/// Count number of pieces on board. Used for debugging.
pub fn num_pieces(board: &Board) -> u8 {
/// Debug only: count number of pieces on board.
pub fn num_pieces(&self) -> u8 {
let cbb = self.combined();
let mut count = 0;
for i in board.iter() {
if *i != SQ_E {
count += 1;
while cbb > 0 {
count += cbb & 1;
cbb >>= 1;
}
0
}
count
}
/// Write a text view of the board. Used for debugging.
pub fn draw(board: &Board, f: &mut dyn std::io::Write) {
/// Debug only: write a text view of the board.
pub fn draw(&self, f: &mut dyn std::io::Write) {
let cbb = self.colors[WHITE] | self.colors[BLACK];
for r in (0..8).rev() {
let mut rank = String::with_capacity(8);
for f in 0..8 {
let s = get_square(board, &(f, r));
let piece =
if is_piece(s, SQ_P) { 'p' }
else if is_piece(s, SQ_B) { 'b' }
else if is_piece(s, SQ_N) { 'n' }
else if is_piece(s, SQ_R) { 'r' }
else if is_piece(s, SQ_Q) { 'q' }
else if is_piece(s, SQ_K) { 'k' }
else { '.' };
let piece = if is_color(s, SQ_WH) { piece.to_ascii_uppercase() } else { piece };
rank.push(piece);
let p = f * 8 + r;
let bp = bit_pos(p);
let piece_char = if cbb & bp == 0 {
'.'
} else {
let (color, piece) = (self.get_color(p), self.get_piece(p));
let mut piece_char = match piece {
PAWN => 'p',
BISHOP => 'b',
KNIGHT => 'n',
ROOK => 'r',
QUEEN => 'q',
KING => 'k',
};
if color == WHITE {
let piece_char = piece_char.to_ascii_uppercase();
}
piece_char
};
rank.push(piece_char);
}
writeln!(f, "{} {}", r + 1, rank).unwrap();
}
write!(f, " abcdefgh").unwrap();
}
}
#[cfg(test)]
@ -265,70 +298,72 @@ mod tests {
#[test]
fn test_opposite() {
assert_eq!(opposite(SQ_WH), SQ_BL);
assert_eq!(opposite(SQ_BL), SQ_WH);
assert_eq!(opposite(WHITE), BLACK);
assert_eq!(opposite(BLACK), WHITE);
}
#[test]
fn test_pos() {
assert_eq!(pos("a1"), (0, 0));
assert_eq!(pos("a2"), (0, 1));
assert_eq!(pos("a8"), (0, 7));
assert_eq!(pos("b1"), (1, 0));
assert_eq!(pos("h8"), (7, 7));
fn test_sq_from_string() {
assert_eq!(sq_from_string("a1"), A1);
assert_eq!(sq_from_string("a2"), A2);
assert_eq!(sq_from_string("a8"), A8);
assert_eq!(sq_from_string("b1"), B1);
assert_eq!(sq_from_string("h8"), H8);
}
#[test]
fn test_pos_string() {
assert_eq!(pos_string(&(0, 0)), "a1");
assert_eq!(pos_string(&(0, 1)), "a2");
assert_eq!(pos_string(&(0, 7)), "a8");
assert_eq!(pos_string(&(7, 7)), "h8");
fn test_sq_to_string() {
assert_eq!(sq_to_string(A1), "a1");
assert_eq!(sq_to_string(A2), "a2");
assert_eq!(sq_to_string(A8), "a8");
assert_eq!(sq_to_string(H8), "h8");
}
#[test]
fn test_new_from_fen() {
let b1 = new();
let b2 = new_from_fen(notation::FEN_START);
assert!(eq(&b1, &b2));
let b1 = Board::new();
let b2 = Board::new_from_fen(notation::FEN_START);
assert!(b1 == b2);
}
#[test]
fn test_get_square() {
let b = new();
assert_eq!(get_square(&b, &pos("a1")), SQ_WH_R);
assert_eq!(get_square(&b, &pos("a2")), SQ_WH_P);
assert_eq!(get_square(&b, &pos("a3")), SQ_E);
assert_eq!(get_square(&b, &pos("a7")), SQ_BL_P);
assert_eq!(get_square(&b, &pos("a8")), SQ_BL_R);
assert_eq!(get_square(&b, &pos("d1")), SQ_WH_Q);
assert_eq!(get_square(&b, &pos("d8")), SQ_BL_Q);
assert_eq!(get_square(&b, &pos("e1")), SQ_WH_K);
assert_eq!(get_square(&b, &pos("e8")), SQ_BL_K);
fn test_get_color() {
let b = Board::new();
assert_eq!(b.get_color(A1), WHITE);
assert_eq!(b.get_color(A2), WHITE);
assert_eq!(b.get_color(A7), BLACK);
assert_eq!(b.get_color(A8), BLACK);
assert_eq!(b.get_color(D1), WHITE);
assert_eq!(b.get_color(D8), BLACK);
assert_eq!(b.get_color(E1), WHITE);
assert_eq!(b.get_color(E8), BLACK);
}
#[test]
fn test_is_empty() {
let b = new();
assert_eq!(is_empty(&b, &pos("a1")), false);
assert_eq!(is_empty(&b, &pos("a2")), false);
assert_eq!(is_empty(&b, &pos("a3")), true);
fn test_get_piece() {
let b = Board::new();
assert_eq!(b.get_piece(A1), ROOK);
assert_eq!(b.get_piece(A2), PAWN);
assert_eq!(b.get_piece(A7), PAWN);
assert_eq!(b.get_piece(A8), ROOK);
assert_eq!(b.get_piece(D1), QUEEN);
assert_eq!(b.get_piece(D8), QUEEN);
assert_eq!(b.get_piece(E1), KING);
assert_eq!(b.get_piece(E8), KING);
}
#[test]
fn test_find_king() {
let b = new_empty();
assert_eq!(find_king(&b, SQ_WH), None);
let b = new();
assert_eq!(find_king(&b, SQ_WH), Some(pos("e1")));
assert_eq!(find_king(&b, SQ_BL), Some(pos("e8")));
let b = Board::new_empty();
assert_eq!(b.find_king(WHITE), None);
let b = Board::new();
assert_eq!(b.find_king(WHITE), Some(E1));
assert_eq!(b.find_king(BLACK), Some(E8));
}
#[test]
fn test_num_pieces() {
assert_eq!(num_pieces(&new_empty()), 0);
assert_eq!(num_pieces(&new()), 32);
assert_eq!(Board::new_empty().num_pieces(), 0);
assert_eq!(Board::new().num_pieces(), 32);
}
}