movement: use bitboards
This commit is contained in:
parent
9c6327ec91
commit
e114138a48
73
src/board.rs
73
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<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();
|
||||
}
|
||||
|
|
161
src/movement.rs
161
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<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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Reference in a new issue