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`.
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<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)
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();
}

View file

@ -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<u8>);
pub type Move = (Square, Square, Option<Piece>);
/// 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<u8> {
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<u8> {
/// 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);
}

View file

@ -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<Pos>,
pub en_passant: Option<Square>,
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,