movement: use bitboards

This commit is contained in:
dece 2020-06-19 02:44:33 +02:00
parent 9c6327ec91
commit e114138a48
3 changed files with 127 additions and 113 deletions

View file

@ -6,9 +6,9 @@ pub type Bitboard = u64;
/// Color type, used to index `Board.color`. /// Color type, used to index `Board.color`.
pub type Color = usize; pub type Color = usize;
const WHITE: usize = 0; pub const WHITE: usize = 0;
const BLACK: usize = 1; pub const BLACK: usize = 1;
const NUM_COLORS: usize = 2; pub const NUM_COLORS: usize = 2;
/// Get opposite color. /// Get opposite color.
#[inline] #[inline]
@ -26,13 +26,13 @@ pub fn color_to_string(color: Color) -> String {
/// Piece type, used to index `Board.piece`. /// Piece type, used to index `Board.piece`.
pub type Piece = usize; pub type Piece = usize;
const PAWN: usize = 0; pub const PAWN: usize = 0;
const BISHOP: usize = 1; pub const BISHOP: usize = 1;
const KNIGHT: usize = 2; pub const KNIGHT: usize = 2;
const ROOK: usize = 3; pub const ROOK: usize = 3;
const QUEEN: usize = 4; pub const QUEEN: usize = 4;
const KING: usize = 5; pub const KING: usize = 5;
const NUM_PIECES: usize = 6; pub const NUM_PIECES: usize = 6;
/// Coords (file, rank) of a square on a board. /// Coords (file, rank) of a square on a board.
pub type Square = i8; pub type Square = i8;
@ -195,17 +195,22 @@ impl Board {
self.colors[WHITE] | self.colors[BLACK] 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! /// Get color type at position. It must hold a piece!
pub fn get_color(&self, p: Square) -> Color { pub fn get_color(&self, square: Square) -> Color {
let bp = bit_pos(p); let bp = bit_pos(square);
if (self.colors[WHITE] & bp) == 1 { WHITE } if (self.colors[WHITE] & bp) == 1 { WHITE }
else if (self.pieces[BLACK] & bp) == 1 { BLACK } else if (self.pieces[BLACK] & bp) == 1 { BLACK }
else { panic!("Empty square.") } else { panic!("Empty square.") }
} }
/// Get piece type at position. It must hold a piece! /// Get piece type at position. It must hold a piece!
pub fn get_piece(&self, p: Square) -> Piece { pub fn get_piece(&self, square: Square) -> Piece {
let bp = bit_pos(p); let bp = bit_pos(square);
if (self.pieces[PAWN] & bp) == 1 { PAWN } if (self.pieces[PAWN] & bp) == 1 { PAWN }
else if (self.pieces[BISHOP] & bp) == 1 { BISHOP } else if (self.pieces[BISHOP] & bp) == 1 { BISHOP }
else if (self.pieces[KNIGHT] & bp) == 1 { KNIGHT } else if (self.pieces[KNIGHT] & bp) == 1 { KNIGHT }
@ -217,31 +222,31 @@ impl Board {
/// Set a new value for the square at this position. /// Set a new value for the square at this position.
#[inline] #[inline]
pub fn set_square(&mut self, p: Square, color: Color, piece: Piece) { pub fn set_square(&mut self, square: Square, color: Color, piece: Piece) {
self.colors[color] |= bit_pos(p); self.colors[color] |= bit_pos(square);
self.pieces[piece] |= bit_pos(p); self.pieces[piece] |= bit_pos(square);
} }
/// Set the square empty at this position. /// Set the square empty at this position.
#[inline] #[inline]
pub fn clear_square(&mut self, p: Square) { pub fn clear_square(&mut self, square: Square) {
for color in 0..NUM_COLORS { self.colors[color] &= !bit_pos(p); } for color in 0..NUM_COLORS { self.colors[color] &= !bit_pos(square); }
for piece in 0..NUM_PIECES { self.pieces[piece] &= !bit_pos(p); } for piece in 0..NUM_PIECES { self.pieces[piece] &= !bit_pos(square); }
} }
/// Move a piece from a position to another, clearing initial position. /// Move a piece from a position to another, clearing initial position.
#[inline] #[inline]
pub fn move_square(&mut self, from: Square, to: Square) { pub fn move_square(&mut self, source: Square, dest: Square) {
self.set_square(to, self.get_color(from), self.get_piece(from)); self.set_square(dest, self.get_color(source), self.get_piece(source));
self.clear_square(from); self.clear_square(source);
} }
/// Find position of this king. /// Find position of this king.
pub fn find_king(&self, color: Color) -> Option<Square> { pub fn find_king(&self, color: Color) -> Option<Square> {
let king_bb = self.colors[color] & self.pieces[KING]; let king_bb = self.colors[color] & self.pieces[KING];
for p in 0..64 { for square in 0..64 {
if king_bb & bit_pos(p) == 1 { if king_bb & bit_pos(square) == 1 {
return Some(p) return Some(square)
} }
} }
None None
@ -261,15 +266,15 @@ impl Board {
/// Debug only: write a text view of the board. /// Debug only: write a text view of the board.
pub fn draw(&self, f: &mut dyn std::io::Write) { pub fn draw(&self, f: &mut dyn std::io::Write) {
let cbb = self.colors[WHITE] | self.colors[BLACK]; let cbb = self.colors[WHITE] | self.colors[BLACK];
for r in (0..8).rev() { for rank in (0..8).rev() {
let mut rank = String::with_capacity(8); let mut rank_str = String::with_capacity(8);
for f in 0..8 { for file in 0..8 {
let p = f * 8 + r; let square = file * 8 + rank;
let bp = bit_pos(p); let bp = bit_pos(square);
let piece_char = if cbb & bp == 0 { let piece_char = if cbb & bp == 0 {
'.' '.'
} else { } 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 { let mut piece_char = match piece {
PAWN => 'p', PAWN => 'p',
BISHOP => 'b', BISHOP => 'b',
@ -283,9 +288,9 @@ impl Board {
} }
piece_char 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(); write!(f, " abcdefgh").unwrap();
} }

View file

@ -4,11 +4,11 @@ use crate::board::*;
use crate::castling::*; use crate::castling::*;
use crate::rules; use crate::rules;
const START_WH_K_POS: Pos = pos("e1"); const START_WH_K_POS: Square = E1;
const START_BL_K_POS: Pos = pos("e8"); const START_BL_K_POS: Square = E8;
/// A movement, with before/after positions and optional promotion. /// A movement, with before/after positions and optional promotion.
pub type Move = (Pos, Pos, Option<u8>); pub type Move = (Square, Square, Option<Piece>);
/// Apply a move `m` to copies to `board` and `game_state`. /// 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, game_state: &mut rules::GameState,
m: &Move 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 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 { // Note that we only check for a piece going to rook's initial position: it means the rook
game_state.castling &= !CASTLING_WH_Q; // either moved previously, or it has been taken.
} else if m.1 == pos("h1") && get_square(board, &pos("h1")) == SQ_WH_R { match source {
game_state.castling &= !CASTLING_WH_K; A1 => { game_state.castling &= !CASTLING_WH_Q; }
} else if m.1 == pos("a8") && get_square(board, &pos("a8")) == SQ_BL_R { H1 => { game_state.castling &= !CASTLING_WH_K; }
game_state.castling &= !CASTLING_BL_Q; A8 => { game_state.castling &= !CASTLING_BL_Q; }
} else if m.1 == pos("h8") && get_square(board, &pos("h8")) == SQ_BL_R { H8 => { game_state.castling &= !CASTLING_BL_K; }
game_state.castling &= !CASTLING_BL_K;
} }
// Update board and game state. // 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, check if the king or a rook moved to update castling options.
else { else {
let piece = get_square(board, &m.1); let color = board.get_color(dest);
if is_white(piece) && game_state.castling & CASTLING_WH_MASK != 0 { if color == WHITE && game_state.castling & CASTLING_WH_MASK != 0 {
match get_type(piece) { match board.get_piece(dest) {
SQ_K => { KING => {
if m.0 == pos("e1") { if source == E1 {
game_state.castling &= !CASTLING_WH_MASK; game_state.castling &= !CASTLING_WH_MASK;
} }
} }
SQ_R => { ROOK => {
if m.0 == pos("a1") { if source == A1 {
game_state.castling &= !CASTLING_WH_Q; game_state.castling &= !CASTLING_WH_Q;
} else if m.0 == pos("h1") { } else if source == H1 {
game_state.castling &= !CASTLING_WH_K; game_state.castling &= !CASTLING_WH_K;
} }
} }
_ => {} _ => {}
} }
} else if is_black(piece) && game_state.castling & CASTLING_BL_MASK != 0 { } else if color == BLACK && game_state.castling & CASTLING_BL_MASK != 0 {
match get_type(piece) { match board.get_piece(dest) {
SQ_K => { KING => {
if m.0 == pos("e8") { if source == E8 {
game_state.castling &= !CASTLING_BL_MASK; game_state.castling &= !CASTLING_BL_MASK;
} }
} }
SQ_R => { ROOK => {
if m.0 == pos("a8") { if source == A8 {
game_state.castling &= !CASTLING_BL_Q; game_state.castling &= !CASTLING_BL_Q;
} else if m.0 == pos("h8") { } else if source == H8 {
game_state.castling &= !CASTLING_BL_K; 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) { if let Some(castle) = get_castle(m) {
match castle { match castle {
CASTLING_WH_K => { CASTLING_WH_K => {
move_piece(board, &START_WH_K_POS, &pos("g1")); board.move_square(START_WH_K_POS, G1);
move_piece(board, &pos("h1"), &pos("f1")); board.move_square(H1, F1);
} }
CASTLING_WH_Q => { CASTLING_WH_Q => {
move_piece(board, &START_WH_K_POS, &pos("c1")); board.move_square(START_WH_K_POS, C1);
move_piece(board, &pos("a1"), &pos("d1")); board.move_square(A1, D1);
} }
CASTLING_BL_K => { CASTLING_BL_K => {
move_piece(board, &START_BL_K_POS, &pos("g8")); board.move_square(START_BL_K_POS, G8);
move_piece(board, &pos("h8"), &pos("f8")); board.move_square(H8, F8);
} }
CASTLING_BL_Q => { CASTLING_BL_Q => {
move_piece(board, &START_BL_K_POS, &pos("c8")); board.move_square(START_BL_K_POS, C8);
move_piece(board, &pos("a8"), &pos("d8")); board.move_square(A8, D8);
} }
_ => {} _ => {}
} }
} else { } else {
move_piece(board, &m.0, &m.1); board.move_square(m.0, m.1);
if let Some(prom_type) = m.2 { if let Some(prom_type) = m.2 {
let color = get_color(get_square(board, &m.1)); let color = board.get_color(m.1);
set_square(board, &m.1, color|prom_type); board.set_square(m.1, color, prom_type);
} }
} }
} }
/// Get the corresponding castling flag for this move. /// Get the corresponding castling flag for this move.
pub fn get_castle(m: &Move) -> Option<u8> { pub fn get_castle(m: &Move) -> Option<u8> {
if m.0 == pos("e1") { let (source, dest) = (m.0, m.1);
if m.1 == pos("c1") { if source == E1 {
if dest == C1 {
Some(CASTLING_WH_Q) Some(CASTLING_WH_Q)
} else if m.1 == pos("g1") { } else if dest == G1 {
Some(CASTLING_WH_K) Some(CASTLING_WH_K)
} else { } else {
None None
} }
} else if m.0 == pos("e8") { } else if source == E8 {
if m.1 == pos("c8") { if dest == C8 {
Some(CASTLING_BL_Q) Some(CASTLING_BL_Q)
} else if m.1 == pos("g8") { } else if dest == G8 {
Some(CASTLING_BL_K) Some(CASTLING_BL_K)
} else { } else {
None None
@ -155,10 +157,10 @@ pub fn get_castle(m: &Move) -> Option<u8> {
/// Get the move for this castle. /// Get the move for this castle.
pub fn get_castle_move(castle: u8) -> Move { pub fn get_castle_move(castle: u8) -> Move {
match castle { match castle {
CASTLING_WH_Q => (pos("e1"), pos("c1"), None), CASTLING_WH_Q => (E1, C1, None),
CASTLING_WH_K => (pos("e1"), pos("g1"), None), CASTLING_WH_K => (E1, G1, None),
CASTLING_BL_Q => (pos("e8"), pos("c8"), None), CASTLING_BL_Q => (E8, C8, None),
CASTLING_BL_K => (pos("e8"), pos("g8"), None), CASTLING_BL_K => (E8, G8, None),
_ => panic!("Illegal castling requested: {:08b}", castle), _ => panic!("Illegal castling requested: {:08b}", castle),
} }
} }
@ -170,52 +172,59 @@ mod tests {
#[test] #[test]
fn test_apply_move_to_board() { fn test_apply_move_to_board() {
let mut b = new_empty(); let mut b = Board::new_empty();
// Put 2 enemy knights on board. // Put 2 enemy knights on board.
set_square(&mut b, &pos("d4"), SQ_WH_N); b.set_square(D4, WHITE, KNIGHT);
set_square(&mut b, &pos("f4"), SQ_BL_N); b.set_square(F4, BLACK, KNIGHT);
// Move white knight in a position attacked by black knight. // Move white knight in a position attacked by black knight.
apply_move_to_board(&mut b, &(pos("d4"), pos("e6"), None)); apply_move_to_board(&mut b, &(D4, E6, None));
assert_eq!(get_square(&b, &pos("d4")), SQ_E); assert!(b.is_empty(D4));
assert_eq!(get_square(&b, &pos("e6")), SQ_WH_N); assert_eq!(b.get_color(E6), WHITE);
assert_eq!(num_pieces(&b), 2); assert_eq!(b.get_piece(E6), KNIGHT);
assert_eq!(b.num_pieces(), 2);
// Sack it with black knight // Sack it with black knight
apply_move_to_board(&mut b, &(pos("f4"), pos("e6"), None)); apply_move_to_board(&mut b, &(F4, E6, None));
assert_eq!(get_square(&b, &pos("e6")), SQ_BL_N); assert_eq!(b.get_color(E6), BLACK);
assert_eq!(num_pieces(&b), 1); assert_eq!(b.get_piece(E6), KNIGHT);
assert_eq!(b.num_pieces(), 1);
} }
#[test] #[test]
fn test_apply_move_to_castling() { fn test_apply_move_to_castling() {
let mut b = new(); let mut b = Board::new();
let mut gs = rules::GameState::new(); let mut gs = rules::GameState::new();
assert_eq!(gs.castling, CASTLING_MASK); assert_eq!(gs.castling, CASTLING_MASK);
// On a starting board, start by making place for all castles. // On a starting board, start by making place for all castles.
clear_square(&mut b, &pos("b1")); b.clear_square(B1);
clear_square(&mut b, &pos("c1")); b.clear_square(C1);
clear_square(&mut b, &pos("d1")); b.clear_square(D1);
clear_square(&mut b, &pos("f1")); b.clear_square(F1);
clear_square(&mut b, &pos("g1")); b.clear_square(G1);
clear_square(&mut b, &pos("b8")); b.clear_square(B8);
clear_square(&mut b, &pos("c8")); b.clear_square(C8);
clear_square(&mut b, &pos("d8")); b.clear_square(D8);
clear_square(&mut b, &pos("f8")); b.clear_square(F8);
clear_square(&mut b, &pos("g8")); b.clear_square(G8);
// White queen-side castling. // White queen-side castling.
apply_move_to(&mut b, &mut gs, &parse_move("e1c1")); apply_move_to(&mut b, &mut gs, &parse_move("e1c1"));
assert!(is_piece(get_square(&b, &pos("c1")), SQ_WH_K)); assert_eq!(b.get_color(C1), WHITE);
assert!(is_piece(get_square(&b, &pos("d1")), SQ_WH_R)); assert_eq!(b.get_piece(C1), KING);
assert!(is_empty(&b, &pos("a1"))); assert_eq!(b.get_color(D1), WHITE);
assert!(is_empty(&b, &pos("e1"))); assert_eq!(b.get_piece(D1), ROOK);
assert!(b.is_empty(A1));
assert!(b.is_empty(E1));
assert_eq!(gs.castling, CASTLING_BL_MASK); assert_eq!(gs.castling, CASTLING_BL_MASK);
// Black king-side castling. // Black king-side castling.
apply_move_to(&mut b, &mut gs, &parse_move("e8g8")); apply_move_to(&mut b, &mut gs, &parse_move("e8g8"));
assert!(is_piece(get_square(&b, &pos("g8")), SQ_BL_K)); assert_eq!(b.get_color(G1), BLACK);
assert!(is_piece(get_square(&b, &pos("f8")), SQ_BL_R)); assert_eq!(b.get_piece(G1), KING);
assert!(is_empty(&b, &pos("h8"))); assert_eq!(b.get_color(F1), BLACK);
assert!(is_empty(&b, &pos("e8"))); 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); assert_eq!(gs.castling, 0);
} }

View file

@ -17,9 +17,9 @@ use crate::notation;
/// - `fullmove`: same /// - `fullmove`: same
#[derive(Debug, PartialEq, Clone, Hash)] #[derive(Debug, PartialEq, Clone, Hash)]
pub struct GameState { pub struct GameState {
pub color: u8, pub color: Color,
pub castling: u8, pub castling: u8,
pub en_passant: Option<Pos>, pub en_passant: Option<Square>,
pub halfmove: i32, pub halfmove: i32,
pub fullmove: i32, pub fullmove: i32,
} }
@ -27,7 +27,7 @@ pub struct GameState {
impl GameState { impl GameState {
pub const fn new() -> GameState { pub const fn new() -> GameState {
GameState { GameState {
color: SQ_WH, color: WHITE,
castling: CASTLING_MASK, castling: CASTLING_MASK,
en_passant: None, en_passant: None,
halfmove: 0, halfmove: 0,