diff --git a/res/scripts/gen_squares.py b/res/scripts/gen_squares.py new file mode 100755 index 0000000..82b662e --- /dev/null +++ b/res/scripts/gen_squares.py @@ -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)) diff --git a/src/board.rs b/src/board.rs index 838a542..0d3aec9 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,261 +1,294 @@ //! 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; + +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), + } +} + +/// 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 color: [Bitboard; 2], - pub piece: [Bitboard; 6], + pub colors: [Bitboard; 2], + pub pieces: [Bitboard; 6], } -/// Generate the board of a new game. -pub const fn new() -> Board { - Board { - color: [ - 0b11000000_11000000_11000000_11000000_11000000_11000000_11000000_11000000, - 0b00000011_00000011_00000011_00000011_00000011_00000011_00000011_00000011, - ], - piece: [ - 0b01000010_01000010_01000010_01000010_01000010_01000010_01000010_01000010, - 0b00000000_00000000_10000001_00000000_00000000_10000001_00000000_00000000, - 0b00000000_10000001_00000000_00000000_00000000_00000000_10000001_00000000, - 0b10000001_00000000_00000000_00000000_00000000_00000000_00000000_10000001, - 0b00000000_00000000_00000000_10000001_00000000_00000000_00000000_00000000, - 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]; - 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 } - '/' => { f = 0; r -= 1; } - d if d.is_digit(10) => { f += d.to_digit(10).unwrap() as i8 } - _ => break, +// Factories. +impl Board { + /// Generate the board of a new game. + pub const fn new() -> Board { + Board { + colors: [ + 0b11000000_11000000_11000000_11000000_11000000_11000000_11000000_11000000, + 0b00000011_00000011_00000011_00000011_00000011_00000011_00000011_00000011, + ], + 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, + 0b10000001_00000000_00000000_00000000_00000000_00000000_00000000_10000001, + 0b00000000_00000000_00000000_10000001_00000000_00000000_00000000_00000000, + 0b00000000_00000000_00000000_00000000_10000001_00000000_00000000_00000000, + ] } } - 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] -} + /// Generate an empty board. + pub const fn new_empty() -> Board { + Board { + colors: [0; 2], + pieces: [0; 6], + } + } -/// 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 + '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 { - 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)) + /// 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' => { 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 } - None } -/// Count number of pieces on board. Used for debugging. -pub fn num_pieces(board: &Board) -> u8 { - let mut count = 0; - for i in board.iter() { - if *i != SQ_E { - count += 1; - } +impl Board { + /// Get combined white/black pieces bitboard. + #[inline] + pub fn combined(&self) -> Bitboard { + self.colors[WHITE] | self.colors[BLACK] } - count -} -/// Write a text view of the board. Used for debugging. -pub fn draw(board: &Board, f: &mut dyn std::io::Write) { - 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); - } - writeln!(f, "{} {}", r + 1, rank).unwrap(); + /// 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 { + 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 + } + + /// Debug only: count number of pieces on board. + pub fn num_pieces(&self) -> u8 { + let cbb = self.combined(); + let mut count = 0; + while cbb > 0 { + count += cbb & 1; + cbb >>= 1; + } + 0 + } + + /// 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 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(); } - 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); } }