From e114138a4899fb7830a6b3ef6d618a1ee9c9cef9 Mon Sep 17 00:00:00 2001 From: dece Date: Fri, 19 Jun 2020 02:44:33 +0200 Subject: [PATCH] movement: use bitboards --- src/board.rs | 73 ++++++++++++---------- src/movement.rs | 161 +++++++++++++++++++++++++----------------------- src/rules.rs | 6 +- 3 files changed, 127 insertions(+), 113 deletions(-) diff --git a/src/board.rs b/src/board.rs index efd61d9..4b1902c 100644 --- a/src/board.rs +++ b/src/board.rs @@ -6,9 +6,9 @@ pub type Bitboard = u64; /// Color type, used to index `Board.color`. pub type Color = usize; -const WHITE: usize = 0; -const BLACK: usize = 1; -const NUM_COLORS: usize = 2; +pub const WHITE: usize = 0; +pub const BLACK: usize = 1; +pub const NUM_COLORS: usize = 2; /// Get opposite color. #[inline] @@ -26,13 +26,13 @@ pub fn color_to_string(color: Color) -> String { /// 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; +pub const PAWN: usize = 0; +pub const BISHOP: usize = 1; +pub const KNIGHT: usize = 2; +pub const ROOK: usize = 3; +pub const QUEEN: usize = 4; +pub const KING: usize = 5; +pub const NUM_PIECES: usize = 6; /// Coords (file, rank) of a square on a board. pub type Square = i8; @@ -195,17 +195,22 @@ impl Board { self.colors[WHITE] | self.colors[BLACK] } + /// True if this square is empty. + pub fn is_empty(&self, square: Square) -> bool { + self.combined() & bit_pos(square) == 0 + } + /// Get color type at position. It must hold a piece! - pub fn get_color(&self, p: Square) -> Color { - let bp = bit_pos(p); + pub fn get_color(&self, square: Square) -> Color { + let bp = bit_pos(square); 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); + pub fn get_piece(&self, square: Square) -> Piece { + let bp = bit_pos(square); if (self.pieces[PAWN] & bp) == 1 { PAWN } else if (self.pieces[BISHOP] & bp) == 1 { BISHOP } else if (self.pieces[KNIGHT] & bp) == 1 { KNIGHT } @@ -217,31 +222,31 @@ impl Board { /// 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); + pub fn set_square(&mut self, square: Square, color: Color, piece: Piece) { + self.colors[color] |= bit_pos(square); + self.pieces[piece] |= bit_pos(square); } /// 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); } + pub fn clear_square(&mut self, square: Square) { + for color in 0..NUM_COLORS { self.colors[color] &= !bit_pos(square); } + for piece in 0..NUM_PIECES { self.pieces[piece] &= !bit_pos(square); } } /// 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); + pub fn move_square(&mut self, source: Square, dest: Square) { + self.set_square(dest, self.get_color(source), self.get_piece(source)); + self.clear_square(source); } /// 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) + for square in 0..64 { + if king_bb & bit_pos(square) == 1 { + return Some(square) } } None @@ -261,15 +266,15 @@ impl Board { /// 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); + for rank in (0..8).rev() { + let mut rank_str = String::with_capacity(8); + for file in 0..8 { + let square = file * 8 + rank; + let bp = bit_pos(square); let piece_char = if cbb & bp == 0 { '.' } else { - let (color, piece) = (self.get_color(p), self.get_piece(p)); + let (color, piece) = (self.get_color(square), self.get_piece(square)); let mut piece_char = match piece { PAWN => 'p', BISHOP => 'b', @@ -283,9 +288,9 @@ impl Board { } piece_char }; - rank.push(piece_char); + rank_str.push(piece_char); } - writeln!(f, "{} {}", r + 1, rank).unwrap(); + writeln!(f, "{} {}", rank + 1, rank_str).unwrap(); } write!(f, " abcdefgh").unwrap(); } diff --git a/src/movement.rs b/src/movement.rs index fa8c355..808ad6d 100644 --- a/src/movement.rs +++ b/src/movement.rs @@ -4,11 +4,11 @@ use crate::board::*; use crate::castling::*; use crate::rules; -const START_WH_K_POS: Pos = pos("e1"); -const START_BL_K_POS: Pos = pos("e8"); +const START_WH_K_POS: Square = E1; +const START_BL_K_POS: Square = E8; /// A movement, with before/after positions and optional promotion. -pub type Move = (Pos, Pos, Option); +pub type Move = (Square, Square, Option); /// Apply a move `m` to copies to `board` and `game_state`. /// @@ -36,15 +36,16 @@ pub fn apply_move_to( game_state: &mut rules::GameState, m: &Move ) { + let (source, dest) = (m.0, m.1); + // If a rook is taken, remove its castling option. Needs to be checked before we update board. - if m.1 == pos("a1") && get_square(board, &pos("a1")) == SQ_WH_R { - game_state.castling &= !CASTLING_WH_Q; - } else if m.1 == pos("h1") && get_square(board, &pos("h1")) == SQ_WH_R { - game_state.castling &= !CASTLING_WH_K; - } else if m.1 == pos("a8") && get_square(board, &pos("a8")) == SQ_BL_R { - game_state.castling &= !CASTLING_BL_Q; - } else if m.1 == pos("h8") && get_square(board, &pos("h8")) == SQ_BL_R { - game_state.castling &= !CASTLING_BL_K; + // Note that we only check for a piece going to rook's initial position: it means the rook + // either moved previously, or it has been taken. + match source { + A1 => { game_state.castling &= !CASTLING_WH_Q; } + H1 => { game_state.castling &= !CASTLING_WH_K; } + A8 => { game_state.castling &= !CASTLING_BL_Q; } + H8 => { game_state.castling &= !CASTLING_BL_K; } } // Update board and game state. @@ -61,34 +62,34 @@ pub fn apply_move_to( } // Else, check if the king or a rook moved to update castling options. else { - let piece = get_square(board, &m.1); - if is_white(piece) && game_state.castling & CASTLING_WH_MASK != 0 { - match get_type(piece) { - SQ_K => { - if m.0 == pos("e1") { + let color = board.get_color(dest); + if color == WHITE && game_state.castling & CASTLING_WH_MASK != 0 { + match board.get_piece(dest) { + KING => { + if source == E1 { game_state.castling &= !CASTLING_WH_MASK; } } - SQ_R => { - if m.0 == pos("a1") { + ROOK => { + if source == A1 { game_state.castling &= !CASTLING_WH_Q; - } else if m.0 == pos("h1") { + } else if source == H1 { game_state.castling &= !CASTLING_WH_K; } } _ => {} } - } else if is_black(piece) && game_state.castling & CASTLING_BL_MASK != 0 { - match get_type(piece) { - SQ_K => { - if m.0 == pos("e8") { + } else if color == BLACK && game_state.castling & CASTLING_BL_MASK != 0 { + match board.get_piece(dest) { + KING => { + if source == E8 { game_state.castling &= !CASTLING_BL_MASK; } } - SQ_R => { - if m.0 == pos("a8") { + ROOK => { + if source == A8 { game_state.castling &= !CASTLING_BL_Q; - } else if m.0 == pos("h8") { + } else if source == H8 { game_state.castling &= !CASTLING_BL_K; } } @@ -103,46 +104,47 @@ pub fn apply_move_to_board(board: &mut Board, m: &Move) { if let Some(castle) = get_castle(m) { match castle { CASTLING_WH_K => { - move_piece(board, &START_WH_K_POS, &pos("g1")); - move_piece(board, &pos("h1"), &pos("f1")); + board.move_square(START_WH_K_POS, G1); + board.move_square(H1, F1); } CASTLING_WH_Q => { - move_piece(board, &START_WH_K_POS, &pos("c1")); - move_piece(board, &pos("a1"), &pos("d1")); + board.move_square(START_WH_K_POS, C1); + board.move_square(A1, D1); } CASTLING_BL_K => { - move_piece(board, &START_BL_K_POS, &pos("g8")); - move_piece(board, &pos("h8"), &pos("f8")); + board.move_square(START_BL_K_POS, G8); + board.move_square(H8, F8); } CASTLING_BL_Q => { - move_piece(board, &START_BL_K_POS, &pos("c8")); - move_piece(board, &pos("a8"), &pos("d8")); + board.move_square(START_BL_K_POS, C8); + board.move_square(A8, D8); } _ => {} } } else { - move_piece(board, &m.0, &m.1); + board.move_square(m.0, m.1); if let Some(prom_type) = m.2 { - let color = get_color(get_square(board, &m.1)); - set_square(board, &m.1, color|prom_type); + let color = board.get_color(m.1); + board.set_square(m.1, color, prom_type); } } } /// Get the corresponding castling flag for this move. pub fn get_castle(m: &Move) -> Option { - if m.0 == pos("e1") { - if m.1 == pos("c1") { + let (source, dest) = (m.0, m.1); + if source == E1 { + if dest == C1 { Some(CASTLING_WH_Q) - } else if m.1 == pos("g1") { + } else if dest == G1 { Some(CASTLING_WH_K) } else { None } - } else if m.0 == pos("e8") { - if m.1 == pos("c8") { + } else if source == E8 { + if dest == C8 { Some(CASTLING_BL_Q) - } else if m.1 == pos("g8") { + } else if dest == G8 { Some(CASTLING_BL_K) } else { None @@ -155,10 +157,10 @@ pub fn get_castle(m: &Move) -> Option { /// Get the move for this castle. pub fn get_castle_move(castle: u8) -> Move { match castle { - CASTLING_WH_Q => (pos("e1"), pos("c1"), None), - CASTLING_WH_K => (pos("e1"), pos("g1"), None), - CASTLING_BL_Q => (pos("e8"), pos("c8"), None), - CASTLING_BL_K => (pos("e8"), pos("g8"), None), + CASTLING_WH_Q => (E1, C1, None), + CASTLING_WH_K => (E1, G1, None), + CASTLING_BL_Q => (E8, C8, None), + CASTLING_BL_K => (E8, G8, None), _ => panic!("Illegal castling requested: {:08b}", castle), } } @@ -170,52 +172,59 @@ mod tests { #[test] fn test_apply_move_to_board() { - let mut b = new_empty(); + let mut b = Board::new_empty(); // Put 2 enemy knights on board. - set_square(&mut b, &pos("d4"), SQ_WH_N); - set_square(&mut b, &pos("f4"), SQ_BL_N); + b.set_square(D4, WHITE, KNIGHT); + b.set_square(F4, BLACK, KNIGHT); // Move white knight in a position attacked by black knight. - apply_move_to_board(&mut b, &(pos("d4"), pos("e6"), None)); - assert_eq!(get_square(&b, &pos("d4")), SQ_E); - assert_eq!(get_square(&b, &pos("e6")), SQ_WH_N); - assert_eq!(num_pieces(&b), 2); + apply_move_to_board(&mut b, &(D4, E6, None)); + assert!(b.is_empty(D4)); + assert_eq!(b.get_color(E6), WHITE); + assert_eq!(b.get_piece(E6), KNIGHT); + assert_eq!(b.num_pieces(), 2); // Sack it with black knight - apply_move_to_board(&mut b, &(pos("f4"), pos("e6"), None)); - assert_eq!(get_square(&b, &pos("e6")), SQ_BL_N); - assert_eq!(num_pieces(&b), 1); + apply_move_to_board(&mut b, &(F4, E6, None)); + assert_eq!(b.get_color(E6), BLACK); + assert_eq!(b.get_piece(E6), KNIGHT); + assert_eq!(b.num_pieces(), 1); } #[test] fn test_apply_move_to_castling() { - let mut b = new(); + let mut b = Board::new(); let mut gs = rules::GameState::new(); assert_eq!(gs.castling, CASTLING_MASK); // On a starting board, start by making place for all castles. - clear_square(&mut b, &pos("b1")); - clear_square(&mut b, &pos("c1")); - clear_square(&mut b, &pos("d1")); - clear_square(&mut b, &pos("f1")); - clear_square(&mut b, &pos("g1")); - clear_square(&mut b, &pos("b8")); - clear_square(&mut b, &pos("c8")); - clear_square(&mut b, &pos("d8")); - clear_square(&mut b, &pos("f8")); - clear_square(&mut b, &pos("g8")); + b.clear_square(B1); + b.clear_square(C1); + b.clear_square(D1); + b.clear_square(F1); + b.clear_square(G1); + b.clear_square(B8); + b.clear_square(C8); + b.clear_square(D8); + b.clear_square(F8); + b.clear_square(G8); // White queen-side castling. apply_move_to(&mut b, &mut gs, &parse_move("e1c1")); - assert!(is_piece(get_square(&b, &pos("c1")), SQ_WH_K)); - assert!(is_piece(get_square(&b, &pos("d1")), SQ_WH_R)); - assert!(is_empty(&b, &pos("a1"))); - assert!(is_empty(&b, &pos("e1"))); + assert_eq!(b.get_color(C1), WHITE); + assert_eq!(b.get_piece(C1), KING); + assert_eq!(b.get_color(D1), WHITE); + assert_eq!(b.get_piece(D1), ROOK); + assert!(b.is_empty(A1)); + assert!(b.is_empty(E1)); assert_eq!(gs.castling, CASTLING_BL_MASK); // Black king-side castling. apply_move_to(&mut b, &mut gs, &parse_move("e8g8")); - assert!(is_piece(get_square(&b, &pos("g8")), SQ_BL_K)); - assert!(is_piece(get_square(&b, &pos("f8")), SQ_BL_R)); - assert!(is_empty(&b, &pos("h8"))); - assert!(is_empty(&b, &pos("e8"))); + assert_eq!(b.get_color(G1), BLACK); + assert_eq!(b.get_piece(G1), KING); + assert_eq!(b.get_color(F1), BLACK); + assert_eq!(b.get_piece(F1), ROOK); + assert!(b.is_empty(H8)); + assert!(b.is_empty(E8)); + // At the end, no more castling options for both sides. assert_eq!(gs.castling, 0); } diff --git a/src/rules.rs b/src/rules.rs index f6a23aa..ff8a6bd 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -17,9 +17,9 @@ use crate::notation; /// - `fullmove`: same #[derive(Debug, PartialEq, Clone, Hash)] pub struct GameState { - pub color: u8, + pub color: Color, pub castling: u8, - pub en_passant: Option, + pub en_passant: Option, pub halfmove: i32, pub fullmove: i32, } @@ -27,7 +27,7 @@ pub struct GameState { impl GameState { pub const fn new() -> GameState { GameState { - color: SQ_WH, + color: WHITE, castling: CASTLING_MASK, en_passant: None, halfmove: 0,