board: compute move ray bitboards

This commit is contained in:
dece 2020-06-21 00:33:05 +02:00
parent 8b0f4c9255
commit 5efdd5407b
8 changed files with 390 additions and 131 deletions

View file

@ -67,6 +67,8 @@ cp external/lichess-bot/config.yml.example /tmp/vatu-config/config.yml
docker build -f res/docker/Dockerfile -t vatu .
# Run with the config folder mounted at /config.
docker run -v /tmp/vatu-config:/config -ti vatu
# In the container, use the following command:
python lichess-bot.py --config /config/config.yml
```
@ -74,10 +76,11 @@ docker run -v /tmp/vatu-config:/config -ti vatu
TODO
----
- Support time constraints
- Proper unmake mechanism instead of allocating boards like there is no tomorrow
- Precompute some pieces moves, maybe
- Transposition table that does not actually slows search down
- Check Zobrist hashes for previous point
- Actual bitboard
- Multithreading (never)
- [X] Support time constraints
- [ ] Unmake mechanism instead of allocating nodes like there is no tomorrow
- [X] Precompute some pieces moves, maybe (done for knights)
- [ ] Transposition table that does not actually slows search down
- [ ] Check Zobrist hashes for previous point
- [X] Actual bitboard
- [ ] Some kind of move ordering could be great
- [ ] Multithreading (never)

29
res/scripts/gen_knight_rays.py Executable file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Pre-compute knight ray bitboards for each square."""
TEMPLATE = """\
const KNIGHT_RAYS: [Bitboard; 64] = [
{}
];
"""
DIRS = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)]
def bit_pos(square):
return 1 << square
def get_rays():
rays = []
for f in range(8):
for r in range(8):
bitboard = 0
for dir_f, dir_r in DIRS:
ray_f = f + dir_f
ray_r = r + dir_r
if ray_f < 0 or ray_f > 7 or ray_r < 0 or ray_r > 7:
continue
bitboard |= bit_pos(ray_f * 8 + ray_r)
rays.append(" 0b{:064b},".format(bitboard))
return rays
print(TEMPLATE.format("\n".join(get_rays())))

View file

@ -103,7 +103,7 @@ impl Analyzer {
if self.debug {
self.log(format!("Analyzing node:\n{}", &self.node));
let moves = self.node.get_player_moves(true);
let moves = self.node.get_player_legal_moves();
self.log(format!("Legal moves: {}", Move::list_to_uci_string(&moves)));
self.log(format!("Move time: {}", self.time_limit));
}
@ -119,7 +119,7 @@ impl Analyzer {
} else {
// If no best move could be found, checkmate is unavoidable; send the first legal move.
self.log("Checkmate is unavoidable.".to_string());
let moves = rules::get_player_moves(&self.node.board, &self.node.game_state, true);
let moves = rules::get_player_moves(&self.node.board, &self.node.game_state, false);
let m = if moves.len() > 0 { Some(moves[0].clone()) } else { None };
self.report_best_move(m);
}
@ -185,7 +185,7 @@ impl Analyzer {
}
// Get negamax for playable moves.
let moves = node.get_player_moves(true);
let moves = node.get_player_legal_moves();
let mut alpha = alpha;
let mut best_score = MIN_F32;
let mut best_move = None;

View file

@ -137,6 +137,26 @@ pub fn sq_to_string(square: Square) -> String {
/// Bitboard for color or piece bits.
pub type Bitboard = u64;
pub const FILE_A: i8 = 0;
pub const FILE_B: i8 = 1;
pub const FILE_C: i8 = 2;
pub const FILE_D: i8 = 3;
pub const FILE_E: i8 = 4;
pub const FILE_F: i8 = 5;
pub const FILE_G: i8 = 6;
pub const FILE_H: i8 = 7;
pub const NUM_FILES: usize = 8;
pub const RANK_1: i8 = 0;
pub const RANK_2: i8 = 1;
pub const RANK_3: i8 = 2;
pub const RANK_4: i8 = 3;
pub const RANK_5: i8 = 4;
pub const RANK_6: i8 = 5;
pub const RANK_7: i8 = 6;
pub const RANK_8: i8 = 7;
pub const NUM_RANKS: usize = 8;
pub const FILES: [Bitboard; 8] = [
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111,
0b00000000_00000000_00000000_00000000_00000000_00000000_11111111_00000000,
@ -172,18 +192,99 @@ pub const fn after_on_file(file: i8, rank: i8) -> Bitboard {
FILES[file as usize] & bits_after(file, rank)
}
/// Get the bitboard of squares on lower ranks of the `square` file.
#[inline]
pub const fn before_on_square_file(square: Square) -> Bitboard {
before_on_file(sq_file(square), sq_rank(square))
/// Debug only: count positive bits of the bitboard.
#[allow(dead_code)]
pub(crate) fn count_bits(bitboard: Bitboard) -> u8 {
let mut bitboard = bitboard;
let mut count = 0;
while bitboard > 0 {
count += bitboard & 1;
bitboard >>= 1;
}
count as u8
}
/// Get the bitboard of squares on upper ranks of the `square` file.
#[inline]
pub const fn after_on_square_file(square: Square) -> Bitboard {
after_on_file(sq_file(square), sq_rank(square))
/// Debug only: pretty print a bitboard
#[allow(dead_code)]
pub(crate) fn draw_bits(bitboard: Bitboard, f: &mut dyn std::io::Write) {
for rank in (0..8).rev() {
let mut rank_str = String::with_capacity(8);
for file in 0..8 {
rank_str.push(if bitboard & bit_pos(sq(file, rank)) == 0 { '.' } else { '1' });
}
writeln!(f, "{}", rank_str).unwrap();
}
}
// Generated by gen_knight_rays.py.
/// Pre-computed knight rays.
const KNIGHT_RAYS: [Bitboard; 64] = [
0b00000000_00000000_00000000_00000000_00000000_00000010_00000100_00000000,
0b00000000_00000000_00000000_00000000_00000000_00000101_00001000_00000000,
0b00000000_00000000_00000000_00000000_00000000_00001010_00010001_00000000,
0b00000000_00000000_00000000_00000000_00000000_00010100_00100010_00000000,
0b00000000_00000000_00000000_00000000_00000000_00101000_01000100_00000000,
0b00000000_00000000_00000000_00000000_00000000_01010000_10001000_00000000,
0b00000000_00000000_00000000_00000000_00000000_10100000_00010000_00000000,
0b00000000_00000000_00000000_00000000_00000000_01000000_00100000_00000000,
0b00000000_00000000_00000000_00000000_00000010_00000100_00000000_00000100,
0b00000000_00000000_00000000_00000000_00000101_00001000_00000000_00001000,
0b00000000_00000000_00000000_00000000_00001010_00010001_00000000_00010001,
0b00000000_00000000_00000000_00000000_00010100_00100010_00000000_00100010,
0b00000000_00000000_00000000_00000000_00101000_01000100_00000000_01000100,
0b00000000_00000000_00000000_00000000_01010000_10001000_00000000_10001000,
0b00000000_00000000_00000000_00000000_10100000_00010000_00000000_00010000,
0b00000000_00000000_00000000_00000000_01000000_00100000_00000000_00100000,
0b00000000_00000000_00000000_00000010_00000100_00000000_00000100_00000010,
0b00000000_00000000_00000000_00000101_00001000_00000000_00001000_00000101,
0b00000000_00000000_00000000_00001010_00010001_00000000_00010001_00001010,
0b00000000_00000000_00000000_00010100_00100010_00000000_00100010_00010100,
0b00000000_00000000_00000000_00101000_01000100_00000000_01000100_00101000,
0b00000000_00000000_00000000_01010000_10001000_00000000_10001000_01010000,
0b00000000_00000000_00000000_10100000_00010000_00000000_00010000_10100000,
0b00000000_00000000_00000000_01000000_00100000_00000000_00100000_01000000,
0b00000000_00000000_00000010_00000100_00000000_00000100_00000010_00000000,
0b00000000_00000000_00000101_00001000_00000000_00001000_00000101_00000000,
0b00000000_00000000_00001010_00010001_00000000_00010001_00001010_00000000,
0b00000000_00000000_00010100_00100010_00000000_00100010_00010100_00000000,
0b00000000_00000000_00101000_01000100_00000000_01000100_00101000_00000000,
0b00000000_00000000_01010000_10001000_00000000_10001000_01010000_00000000,
0b00000000_00000000_10100000_00010000_00000000_00010000_10100000_00000000,
0b00000000_00000000_01000000_00100000_00000000_00100000_01000000_00000000,
0b00000000_00000010_00000100_00000000_00000100_00000010_00000000_00000000,
0b00000000_00000101_00001000_00000000_00001000_00000101_00000000_00000000,
0b00000000_00001010_00010001_00000000_00010001_00001010_00000000_00000000,
0b00000000_00010100_00100010_00000000_00100010_00010100_00000000_00000000,
0b00000000_00101000_01000100_00000000_01000100_00101000_00000000_00000000,
0b00000000_01010000_10001000_00000000_10001000_01010000_00000000_00000000,
0b00000000_10100000_00010000_00000000_00010000_10100000_00000000_00000000,
0b00000000_01000000_00100000_00000000_00100000_01000000_00000000_00000000,
0b00000010_00000100_00000000_00000100_00000010_00000000_00000000_00000000,
0b00000101_00001000_00000000_00001000_00000101_00000000_00000000_00000000,
0b00001010_00010001_00000000_00010001_00001010_00000000_00000000_00000000,
0b00010100_00100010_00000000_00100010_00010100_00000000_00000000_00000000,
0b00101000_01000100_00000000_01000100_00101000_00000000_00000000_00000000,
0b01010000_10001000_00000000_10001000_01010000_00000000_00000000_00000000,
0b10100000_00010000_00000000_00010000_10100000_00000000_00000000_00000000,
0b01000000_00100000_00000000_00100000_01000000_00000000_00000000_00000000,
0b00000100_00000000_00000100_00000010_00000000_00000000_00000000_00000000,
0b00001000_00000000_00001000_00000101_00000000_00000000_00000000_00000000,
0b00010001_00000000_00010001_00001010_00000000_00000000_00000000_00000000,
0b00100010_00000000_00100010_00010100_00000000_00000000_00000000_00000000,
0b01000100_00000000_01000100_00101000_00000000_00000000_00000000_00000000,
0b10001000_00000000_10001000_01010000_00000000_00000000_00000000_00000000,
0b00010000_00000000_00010000_10100000_00000000_00000000_00000000_00000000,
0b00100000_00000000_00100000_01000000_00000000_00000000_00000000_00000000,
0b00000000_00000100_00000010_00000000_00000000_00000000_00000000_00000000,
0b00000000_00001000_00000101_00000000_00000000_00000000_00000000_00000000,
0b00000000_00010001_00001010_00000000_00000000_00000000_00000000_00000000,
0b00000000_00100010_00010100_00000000_00000000_00000000_00000000_00000000,
0b00000000_01000100_00101000_00000000_00000000_00000000_00000000_00000000,
0b00000000_10001000_01010000_00000000_00000000_00000000_00000000_00000000,
0b00000000_00010000_10100000_00000000_00000000_00000000_00000000_00000000,
0b00000000_00100000_01000000_00000000_00000000_00000000_00000000_00000000,
];
/// Board representation with color/piece bitboards.
#[derive(Clone, PartialEq)]
pub struct Board {
@ -245,9 +346,7 @@ impl Board {
}
board
}
}
impl Board {
/// Get combined white/black pieces bitboard.
#[inline]
pub fn combined(&self) -> Bitboard {
@ -330,16 +429,52 @@ impl Board {
None
}
/// Debug only: count number of pieces on board.
#[allow(dead_code)] // Currently used in tests only.
pub(crate) fn num_pieces(&self) -> u8 {
let mut cbb = self.combined();
let mut count = 0;
while cbb > 0 {
count += cbb & 1;
cbb >>= 1;
pub fn get_bishop_rays(&self, square: Square, color: Color) -> Bitboard {
self.get_blockable_rays(square, color, &[(1, 1), (1, -1), (-1, -1), (-1, 1)])
}
count as u8
pub fn get_rook_rays(&self, square: Square, color: Color) -> Bitboard {
self.get_blockable_rays(square, color, &[(1, 0), (0, 1), (-1, 0), (0, -1)])
}
pub fn get_queen_rays(&self, square: Square, color: Color) -> Bitboard {
self.get_blockable_rays(
square, color, &[(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
)
}
fn get_blockable_rays(&self,
square: Square,
color: Color,
directions: &[(i8, i8)]
) -> Bitboard {
let mut rays_bb: Bitboard = 0;
let color_bb = self.by_color(color);
let enemy_bb = self.by_color(opposite(color));
for dir in directions {
let mut ray_f = sq_file(square);
let mut ray_r = sq_rank(square);
loop {
ray_f += dir.0;
ray_r += dir.1;
if ray_f < 0 || ray_f > 7 || ray_r < 0 || ray_r > 7 {
break
}
let bp = bit_pos(sq(ray_f, ray_r));
if color_bb & bp != 0 {
break
}
rays_bb |= bp;
if enemy_bb & bp != 0 {
break
}
}
}
rays_bb
}
pub fn get_knight_rays(&self, square: Square, color: Color) -> Bitboard {
KNIGHT_RAYS[square as usize] & !self.by_color(color)
}
/// Debug only: write a text view of the board.
@ -372,7 +507,7 @@ impl Board {
}
writeln!(f, "{} {}", rank + 1, rank_str).unwrap();
}
write!(f, " abcdefgh").unwrap();
writeln!(f, " abcdefgh").unwrap();
}
}
@ -410,35 +545,41 @@ mod tests {
// Bitboard
#[test]
fn test_before_on_square_file() {
// Only should the 4 lowest files for readability.
assert_eq!(before_on_square_file(A1), 0b00000000_00000000_00000000_00000000);
assert_eq!(before_on_square_file(A2), 0b00000000_00000000_00000000_00000001);
assert_eq!(before_on_square_file(A4), 0b00000000_00000000_00000000_00000111);
assert_eq!(before_on_square_file(A8), 0b00000000_00000000_00000000_01111111);
assert_eq!(before_on_square_file(B1), 0b00000000_00000000_00000000_00000000);
assert_eq!(before_on_square_file(C1), 0b00000000_00000000_00000000_00000000);
assert_eq!(before_on_square_file(C4), 0b00000000_00000111_00000000_00000000);
// 4 highest files.
assert_eq!(before_on_square_file(H4), 0b00000111_00000000_00000000_00000000 << 32);
assert_eq!(before_on_square_file(H7), 0b00111111_00000000_00000000_00000000 << 32);
assert_eq!(before_on_square_file(H8), 0b01111111_00000000_00000000_00000000 << 32);
fn test_count_bits() {
assert_eq!(count_bits(Board::new_empty().combined()), 0);
assert_eq!(count_bits(Board::new().combined()), 32);
}
#[test]
fn test_after_on_file() {
assert_eq!(after_on_square_file(A1), 0b00000000_00000000_00000000_11111110);
assert_eq!(after_on_square_file(A2), 0b00000000_00000000_00000000_11111100);
assert_eq!(after_on_square_file(A4), 0b00000000_00000000_00000000_11110000);
assert_eq!(after_on_square_file(A8), 0b00000000_00000000_00000000_00000000);
assert_eq!(after_on_square_file(B1), 0b00000000_00000000_11111110_00000000);
assert_eq!(after_on_square_file(C1), 0b00000000_11111110_00000000_00000000);
assert_eq!(after_on_square_file(C4), 0b00000000_11110000_00000000_00000000);
assert_eq!(after_on_square_file(C8), 0b00000000_00000000_00000000_00000000);
fn test_before_on_file() {
// Only should the 4 lowest files for readability.
assert_eq!(before_on_file(FILE_A, RANK_1), 0b00000000_00000000_00000000_00000000);
assert_eq!(before_on_file(FILE_A, RANK_2), 0b00000000_00000000_00000000_00000001);
assert_eq!(before_on_file(FILE_A, RANK_4), 0b00000000_00000000_00000000_00000111);
assert_eq!(before_on_file(FILE_A, RANK_8), 0b00000000_00000000_00000000_01111111);
assert_eq!(before_on_file(FILE_B, RANK_1), 0b00000000_00000000_00000000_00000000);
assert_eq!(before_on_file(FILE_C, RANK_1), 0b00000000_00000000_00000000_00000000);
assert_eq!(before_on_file(FILE_C, RANK_4), 0b00000000_00000111_00000000_00000000);
// 4 highest files.
assert_eq!(after_on_square_file(H4), 0b11110000_00000000_00000000_00000000 << 32);
assert_eq!(after_on_square_file(H7), 0b10000000_00000000_00000000_00000000 << 32);
assert_eq!(after_on_square_file(H8), 0b00000000_00000000_00000000_00000000 << 32);
assert_eq!(before_on_file(FILE_H, RANK_4), 0b00000111_00000000_00000000_00000000 << 32);
assert_eq!(before_on_file(FILE_H, RANK_7), 0b00111111_00000000_00000000_00000000 << 32);
assert_eq!(before_on_file(FILE_H, RANK_8), 0b01111111_00000000_00000000_00000000 << 32);
}
#[test]
fn test_after_on_square_file() {
assert_eq!(after_on_file(FILE_A, RANK_1), 0b00000000_00000000_00000000_11111110);
assert_eq!(after_on_file(FILE_A, RANK_2), 0b00000000_00000000_00000000_11111100);
assert_eq!(after_on_file(FILE_A, RANK_4), 0b00000000_00000000_00000000_11110000);
assert_eq!(after_on_file(FILE_A, RANK_8), 0b00000000_00000000_00000000_00000000);
assert_eq!(after_on_file(FILE_B, RANK_1), 0b00000000_00000000_11111110_00000000);
assert_eq!(after_on_file(FILE_C, RANK_1), 0b00000000_11111110_00000000_00000000);
assert_eq!(after_on_file(FILE_C, RANK_4), 0b00000000_11110000_00000000_00000000);
assert_eq!(after_on_file(FILE_C, RANK_8), 0b00000000_00000000_00000000_00000000);
// 4 highest files.
assert_eq!(after_on_file(FILE_H, RANK_4), 0b11110000_00000000_00000000_00000000 << 32);
assert_eq!(after_on_file(FILE_H, RANK_7), 0b10000000_00000000_00000000_00000000 << 32);
assert_eq!(after_on_file(FILE_H, RANK_8), 0b00000000_00000000_00000000_00000000 << 32);
}
// Board
@ -486,8 +627,95 @@ mod tests {
}
#[test]
fn test_num_pieces() {
assert_eq!(Board::new_empty().num_pieces(), 0);
assert_eq!(Board::new().num_pieces(), 32);
fn test_get_bishop_rays() {
let mut b = Board::new_empty();
// A bishop has maximum range when it's in a center square.
b.set_square(D4, WHITE, BISHOP);
let rays_bb = b.get_bishop_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 13);
// Going top-right.
assert!(rays_bb & bit_pos(E5) != 0);
assert!(rays_bb & bit_pos(F6) != 0);
assert!(rays_bb & bit_pos(G7) != 0);
assert!(rays_bb & bit_pos(H8) != 0);
// Going bottom-right.
assert!(rays_bb & bit_pos(E3) != 0);
assert!(rays_bb & bit_pos(F2) != 0);
assert!(rays_bb & bit_pos(G1) != 0);
// Going bottom-left.
assert!(rays_bb & bit_pos(C3) != 0);
assert!(rays_bb & bit_pos(B2) != 0);
assert!(rays_bb & bit_pos(A1) != 0);
// Going top-left.
assert!(rays_bb & bit_pos(C5) != 0);
assert!(rays_bb & bit_pos(B6) != 0);
assert!(rays_bb & bit_pos(A7) != 0);
// When blocking commit to one square with friendly piece, lose 2 moves.
b.set_square(B2, WHITE, PAWN);
let rays_bb = b.get_bishop_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 11);
// When blocking commit to one square with enemy piece, lose only 1 move.
b.set_square(B2, BLACK, PAWN);
let rays_bb = b.get_bishop_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 12);
}
#[test]
fn test_get_knight_moves() {
let mut b = Board::new_empty();
// A knight is never blocked; if it's in the center of the board,
// it can have up to 8 moves.
b.set_square(D4, WHITE, KNIGHT);
let rays_bb = b.get_knight_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 8);
// If on a side if has only 4 moves.
b.set_square(A4, WHITE, KNIGHT);
let rays_bb = b.get_knight_rays(A4, WHITE);
assert_eq!(count_bits(rays_bb), 4);
// And in a corner, only 2 moves.
b.set_square(A1, WHITE, KNIGHT);
let rays_bb = b.get_knight_rays(A1, WHITE);
assert_eq!(count_bits(rays_bb), 2);
// Add 2 friendly pieces and it is totally blocked.
b.set_square(B3, WHITE, PAWN);
b.set_square(C2, WHITE, PAWN);
let rays_bb = b.get_knight_rays(A1, WHITE);
assert_eq!(count_bits(rays_bb), 0);
// If one of those pieces is an enemy, it can be taken.
b.set_square(B3, BLACK, PAWN);
let rays_bb = b.get_knight_rays(A1, WHITE);
assert_eq!(count_bits(rays_bb), 1);
}
#[test]
fn test_get_rook_moves() {
let mut b = Board::new_empty();
b.set_square(D4, WHITE, ROOK);
let rays_bb = b.get_rook_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 14);
b.set_square(D6, BLACK, PAWN);
let rays_bb = b.get_rook_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 12);
b.set_square(D6, WHITE, PAWN);
let rays_bb = b.get_rook_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 11);
}
#[test]
fn test_get_queen_moves() {
let mut b = Board::new_empty();
b.set_square(D4, WHITE, QUEEN);
let rays_bb = b.get_queen_rays(D4, WHITE);
assert_eq!(count_bits(rays_bb), 14 + 13);
}
}

View file

@ -152,12 +152,12 @@ mod tests {
assert!(b.is_empty(D4));
assert_eq!(b.get_color_on(E6), WHITE);
assert_eq!(b.get_piece_on(E6), KNIGHT);
assert_eq!(b.num_pieces(), 2);
assert_eq!(count_bits(b.combined()), 2);
// Sack it with black knight
Move::new(F4, E6).apply_to_board(&mut b);
assert_eq!(b.get_color_on(E6), BLACK);
assert_eq!(b.get_piece_on(E6), KNIGHT);
assert_eq!(b.num_pieces(), 1);
assert_eq!(count_bits(b.combined()), 1);
}
#[test]

View file

@ -29,8 +29,8 @@ impl Node {
}
/// Return player moves from this node.
pub fn get_player_moves(&self, commit: bool) -> Vec<Move> {
rules::get_player_moves(&self.board, &self.game_state, commit)
pub fn get_player_legal_moves(&self) -> Vec<Move> {
rules::get_player_moves(&self.board, &self.game_state, false)
}
/// Compute stats for both players for this node.

View file

@ -53,17 +53,18 @@ impl std::fmt::Display for GameState {
/// Get a list of moves for all pieces of the playing color.
///
/// If `commit` is false, do not check for illegal moves. This is used
/// to avoid endless recursion when checking if a P move is illegal,
/// as it needs to check all possible following enemy moves, e.g. to
/// see if P's king can be taken. Consider a call with true `commit` as
/// a collection of attacked squares instead of legal move collection.
/// If `pseudo_legal` is true, do not check for illegal moves. This is
/// used to avoid endless recursion when checking if a P move is
/// illegal, as it needs to check all possible following enemy moves,
/// e.g. to see if P's king can be taken. Consider a call with true
/// `pseudo_legal` as a collection of attacked squares instead of legal
/// move collection.
pub fn get_player_moves(
board: &Board,
game_state: &GameState,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
let mut moves = Vec::with_capacity(256);
let mut moves = Vec::with_capacity(32);
for r in 0..8 {
for f in 0..8 {
let square = sq(f, r);
@ -72,7 +73,7 @@ pub fn get_player_moves(
}
if board.get_color_on(square) == game_state.color {
moves.append(
&mut get_piece_moves(board, game_state, square, game_state.color, commit)
&mut get_piece_moves(board, game_state, square, game_state.color, pseudo_legal)
);
}
}
@ -86,21 +87,21 @@ pub fn get_player_moves(
/// of the piece on `square`; it could technically be found from the
/// board but that would require an additional lookup and this function
/// is always called in a context where the piece color is known.
pub fn get_piece_moves(
fn get_piece_moves(
board: &Board,
game_state: &GameState,
square: Square,
color: Color,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
match board.get_piece_on(square) {
PAWN => get_pawn_moves(board, game_state, square, color, commit),
BISHOP => get_bishop_moves(board, game_state, square, color, commit),
KNIGHT => get_knight_moves(board, game_state, square, color, commit),
ROOK => get_rook_moves(board, game_state, square, color, commit),
QUEEN => get_queen_moves(board, game_state, square, color, commit),
KING => get_king_moves(board, game_state, square, color, commit),
_ => vec!(),
PAWN => get_pawn_moves(board, game_state, square, color, pseudo_legal),
BISHOP => get_bishop_moves(board, game_state, square, color, pseudo_legal),
KNIGHT => get_knight_moves(board, game_state, square, color, pseudo_legal),
ROOK => get_rook_moves(board, game_state, square, color, pseudo_legal),
QUEEN => get_queen_moves(board, game_state, square, color, pseudo_legal),
KING => get_king_moves(board, game_state, square, color, pseudo_legal),
_ => { panic!("No piece on square.") },
}
}
@ -109,7 +110,7 @@ fn get_pawn_moves(
game_state: &GameState,
square: Square,
color: Color,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square));
let mut moves = vec!();
@ -133,7 +134,7 @@ fn get_pawn_moves(
if (dir > 0 && forward_r == POS_MAX) || (dir < 0 && forward_r == POS_MIN) {
m.promotion = Some(QUEEN)
}
if can_register(commit, board, game_state, &m) {
if pseudo_legal || !is_illegal(board, game_state, &m) {
moves.push(m);
}
}
@ -145,7 +146,7 @@ fn get_pawn_moves(
if !board.is_empty(diag) {
let diag_color = board.get_color_on(diag);
if let Some(m) = get_capture_move(color, square, diag_color, diag, true) {
if can_register(commit, board, game_state, &m) {
if pseudo_legal || !is_illegal(board, game_state, &m) {
moves.push(m);
}
}
@ -158,7 +159,7 @@ fn get_pawn_moves(
if !board.is_empty(diag) {
let diag_color = board.get_color_on(diag);
if let Some(m) = get_capture_move(color, square, diag_color, diag, true) {
if can_register(commit, board, game_state, &m) {
if pseudo_legal || !is_illegal(board, game_state, &m) {
moves.push(m);
}
}
@ -175,7 +176,7 @@ fn get_bishop_moves(
game_state: &GameState,
square: Square,
color: Color,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square));
let mut views = [true; 4]; // Store diagonals where a piece blocks commit.
@ -199,7 +200,7 @@ fn get_bishop_moves(
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, commit) {
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) => { moves.push(m) }
MoveType::Capture(m) => { moves.push(m); views[dir] = false; }
MoveType::CantTakeFriend => { views[dir] = false; }
@ -215,7 +216,7 @@ fn get_knight_moves(
game_state: &GameState,
square: Square,
color: Color,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square));
let mut moves = Vec::with_capacity(8);
@ -230,7 +231,7 @@ fn get_knight_moves(
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, commit) {
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) | MoveType::Capture(m) => moves.push(m),
_ => {}
}
@ -243,7 +244,7 @@ fn get_rook_moves(
game_state: &GameState,
square: Square,
color: Color,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square));
let mut moves = Vec::with_capacity(8);
@ -266,7 +267,7 @@ fn get_rook_moves(
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, commit) {
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) => { moves.push(m) }
MoveType::Capture(m) => { moves.push(m); views[dir] = false; }
MoveType::CantTakeFriend => { views[dir] = false; }
@ -282,12 +283,12 @@ fn get_queen_moves(
game_state: &GameState,
square: Square,
color: Color,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
let mut moves = Vec::with_capacity(16);
// Easy way to get queen moves, but may be a bit quicker if everything was rewritten here.
moves.append(&mut get_bishop_moves(board, game_state, square, color, commit));
moves.append(&mut get_rook_moves(board, game_state, square, color, commit));
moves.append(&mut get_bishop_moves(board, game_state, square, color, pseudo_legal));
moves.append(&mut get_rook_moves(board, game_state, square, color, pseudo_legal));
moves
}
@ -296,7 +297,7 @@ fn get_king_moves(
game_state: &GameState,
square: Square,
color: Color,
commit: bool,
pseudo_legal: bool,
) -> Vec<Move> {
let (f, r) = (sq_file(square), sq_rank(square));
let mut moves = Vec::with_capacity(8);
@ -311,14 +312,14 @@ fn get_king_moves(
}
let ray_square = sq(ray_f, ray_r);
match get_move_type(board, game_state, square, ray_square, color, commit) {
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
MoveType::Simple(m) | MoveType::Capture(m) => moves.push(m),
_ => {}
}
}
// Stop here for uncommitted moves.
if !commit {
// Stop here for pseudo legal moves as castling is not considered along with them.
if pseudo_legal {
return moves
}
@ -370,7 +371,7 @@ fn get_king_moves(
}
let castle = castling_side_mask & castling_color_mask;
let m = Move::get_castle_move(castle);
if can_register(commit, board, game_state, &m) {
if pseudo_legal || !is_illegal(board, game_state, &m) {
moves.push(m);
}
}
@ -390,7 +391,7 @@ fn get_move_type(
) -> MoveType {
if board.is_empty(ray_square) {
let m = Move::new(square, ray_square);
if can_register(commit, board, game_state, &m) {
if is_legal(commit, board, game_state, &m) {
MoveType::Simple(m)
} else {
MoveType::CantRegister
@ -398,7 +399,7 @@ fn get_move_type(
} else {
let ray_color = board.get_color_on(ray_square);
if let Some(m) = get_capture_move(color, square, ray_color, ray_square, false) {
if can_register(commit, board, game_state, &m) {
if is_legal(commit, board, game_state, &m) {
MoveType::Capture(m)
} else {
MoveType::CantRegister
@ -420,15 +421,15 @@ enum MoveType {
CantTakeFriend,
}
/// Return true if `commit` is false, or the move is not illegal,
/// Return true if `pseudo_legal` is true, or the move is not illegal,
///
/// Committing a move means that it can be safely played afterwards.
/// Sometimes it is not what is needed to accept a move in a collection
/// of moves, e.g. when simply checking if some moves would make a
/// previous move illegal.
#[inline]
fn can_register(commit: bool, board: &Board, game_state: &GameState, m: &Move) -> bool {
!commit || !is_illegal(board, game_state, m)
fn is_legal(pseudo_legal: bool, board: &Board, game_state: &GameState, m: &Move) -> bool {
pseudo_legal || !is_illegal(board, game_state, m)
}
/// Return a move from `square1` to `square2` if colors are opposite.
@ -486,7 +487,7 @@ fn is_attacked(board: &Board, game_state: &GameState, square: Square) -> bool {
let mut enemy_game_state = game_state.clone();
enemy_game_state.color = opposite(game_state.color);
// Do not attempt to commit moves, just check for attacked squares.
let enemy_moves = get_player_moves(board, &enemy_game_state, false);
let enemy_moves = get_player_moves(board, &enemy_game_state, true);
for m in enemy_moves.iter() {
if square == m.dest {
return true
@ -505,7 +506,7 @@ mod tests {
let gs = GameState::new();
// At first move, white has 16 pawn moves and 4 knight moves.
let moves = get_player_moves(&b, &gs, true);
let moves = get_player_moves(&b, &gs, false);
assert_eq!(moves.len(), 20);
}
@ -516,12 +517,12 @@ mod tests {
// Check that a pawn (here white queen's pawn) can move forward if the road is free.
b.set_square(D3, WHITE, PAWN);
let moves = get_piece_moves(&b, &gs, D3, WHITE, true);
let moves = get_piece_moves(&b, &gs, D3, WHITE, false);
assert!(moves.len() == 1 && moves.contains(&Move::new(D3, D4)));
// Check that a pawn (here white king's pawn) can move 2 square forward on first move.
b.set_square(E2, WHITE, PAWN);
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
assert_eq!(moves.len(), 2);
assert!(moves.contains(&Move::new(E2, E3)));
assert!(moves.contains(&Move::new(E2, E4)));
@ -529,23 +530,23 @@ mod tests {
// Check that a pawn cannot move forward if a piece is blocking its path.
// 1. black pawn 2 square forward; only 1 square forward available from start pos.
b.set_square(E4, BLACK, PAWN);
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
assert!(moves.len() == 1 && moves.contains(&Move::new(E2, E3)));
// 2. black pawn 1 square forward; no square available.
b.set_square(E3, BLACK, PAWN);
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
assert_eq!(moves.len(), 0);
// 3. remove the e4 black pawn; the white pawn should not be able to jump above e3 pawn.
b.clear_square(E4);
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
assert_eq!(moves.len(), 0);
// Check that a pawn can take a piece diagonally.
b.set_square(F3, BLACK, PAWN);
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
assert!(moves.len() == 1 && moves.contains(&Move::new(E2, F3)));
b.set_square(D3, BLACK, PAWN);
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
assert_eq!(moves.len(), 2);
assert!(moves.contains( &Move::new(E2, F3) ));
assert!(moves.contains( &Move::new(E2, D3) ));
@ -553,7 +554,7 @@ mod tests {
// Check that a pawn moving to the last rank leads to queen promotion.
// 1. by simply moving forward.
b.set_square(A7, WHITE, PAWN);
let moves = get_piece_moves(&b, &gs, A7, WHITE, true);
let moves = get_piece_moves(&b, &gs, A7, WHITE, false);
assert!(moves.len() == 1 && moves.contains(&Move::new_promotion(A7, A8, QUEEN)));
}
@ -564,7 +565,7 @@ mod tests {
// A bishop has maximum range when it's in a center square.
b.set_square(D4, WHITE, BISHOP);
let moves = get_piece_moves(&b, &gs, D4, WHITE, true);
let moves = get_piece_moves(&b, &gs, D4, WHITE, false);
assert_eq!(moves.len(), 13);
// Going top-right.
assert!(moves.contains(&Move::new(D4, E5)));
@ -586,11 +587,11 @@ mod tests {
// When blocking commit to one square with friendly piece, lose 2 moves.
b.set_square(B2, WHITE, PAWN);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 11);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 11);
// When blocking commit to one square with enemy piece, lose only 1 move.
b.set_square(B2, BLACK, PAWN);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 12);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 12);
}
#[test]
@ -601,20 +602,20 @@ mod tests {
// A knight never has blocked commit; if it's in the center of the board, it can have up to
// 8 moves.
b.set_square(D4, WHITE, KNIGHT);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 8);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 8);
// If on a side if has only 4 moves.
b.set_square(A4, WHITE, KNIGHT);
assert_eq!(get_piece_moves(&b, &gs, A4, WHITE, true).len(), 4);
assert_eq!(get_piece_moves(&b, &gs, A4, WHITE, false).len(), 4);
// And in a corner, only 2 moves.
b.set_square(A1, WHITE, KNIGHT);
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, true).len(), 2);
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, false).len(), 2);
// Add 2 friendly pieces and it is totally blocked.
b.set_square(B3, WHITE, PAWN);
b.set_square(C2, WHITE, PAWN);
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, true).len(), 0);
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, false).len(), 0);
}
#[test]
@ -623,11 +624,11 @@ mod tests {
let gs = GameState::new();
b.set_square(D4, WHITE, ROOK);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 14);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 14);
b.set_square(D6, BLACK, PAWN);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 12);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 12);
b.set_square(D6, WHITE, PAWN);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 11);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 11);
}
#[test]
@ -636,7 +637,7 @@ mod tests {
let gs = GameState::new();
b.set_square(D4, WHITE, QUEEN);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 14 + 13);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 14 + 13);
}
#[test]
@ -646,23 +647,23 @@ mod tests {
// King can move 1 square in any direction.
let mut b = Board::new_empty();
b.set_square(D4, WHITE, KING);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 8);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 8);
b.set_square(E5, WHITE, PAWN);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 7);
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 7);
// If castling is available, other moves are possible: 5 moves + 2 castles.
let mut b = Board::new_empty();
b.set_square(E1, WHITE, KING);
b.set_square(A1, WHITE, ROOK);
b.set_square(H1, WHITE, ROOK);
assert_eq!(get_piece_moves(&b, &gs, E1, WHITE, true).len(), 5 + 2);
assert_eq!(get_piece_moves(&b, &gs, E1, WHITE, false).len(), 5 + 2);
// Castling works as well for black.
gs.color = BLACK;
b.set_square(E8, BLACK, KING);
b.set_square(A8, BLACK, ROOK);
b.set_square(H8, BLACK, ROOK);
assert_eq!(get_piece_moves(&b, &gs, E8, BLACK, true).len(), 5 + 2);
assert_eq!(get_piece_moves(&b, &gs, E8, BLACK, false).len(), 5 + 2);
}
#[test]
@ -677,7 +678,7 @@ mod tests {
// No castling available.
gs.castling = 0;
// 5 moves in absolute but only 2 are legal.
let all_wh_moves = get_piece_moves(&b, &gs, E1, WHITE, true);
let all_wh_moves = get_piece_moves(&b, &gs, E1, WHITE, false);
assert_eq!(all_wh_moves.len(), 2);
}

View file

@ -62,7 +62,7 @@ impl BoardStats {
self.reset();
let color = game_state.color;
// Compute mobility for all pieces.
self.mobility = get_player_moves(board, game_state, true).len() as i32;
self.mobility = get_player_moves(board, game_state, false).len() as i32;
// Compute amount of each piece.
for file in 0..8 {
for rank in 0..8 {
@ -172,8 +172,6 @@ mod tests {
mobility: 20,
};
let mut stats = BoardStats::new_from(&b, &gs);
eprintln!("{}", stats.0);
eprintln!("{}", stats.1);
assert!(stats.0 == stats.1);
assert!(stats.0 == initial_stats);