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 . docker build -f res/docker/Dockerfile -t vatu .
# Run with the config folder mounted at /config. # Run with the config folder mounted at /config.
docker run -v /tmp/vatu-config:/config -ti vatu 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 TODO
---- ----
- Support time constraints - [X] Support time constraints
- Proper unmake mechanism instead of allocating boards like there is no tomorrow - [ ] Unmake mechanism instead of allocating nodes like there is no tomorrow
- Precompute some pieces moves, maybe - [X] Precompute some pieces moves, maybe (done for knights)
- Transposition table that does not actually slows search down - [ ] Transposition table that does not actually slows search down
- Check Zobrist hashes for previous point - [ ] Check Zobrist hashes for previous point
- Actual bitboard - [X] Actual bitboard
- Multithreading (never) - [ ] 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 { if self.debug {
self.log(format!("Analyzing node:\n{}", &self.node)); 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!("Legal moves: {}", Move::list_to_uci_string(&moves)));
self.log(format!("Move time: {}", self.time_limit)); self.log(format!("Move time: {}", self.time_limit));
} }
@ -119,7 +119,7 @@ impl Analyzer {
} else { } else {
// If no best move could be found, checkmate is unavoidable; send the first legal move. // If no best move could be found, checkmate is unavoidable; send the first legal move.
self.log("Checkmate is unavoidable.".to_string()); 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 }; let m = if moves.len() > 0 { Some(moves[0].clone()) } else { None };
self.report_best_move(m); self.report_best_move(m);
} }
@ -185,7 +185,7 @@ impl Analyzer {
} }
// Get negamax for playable moves. // 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 alpha = alpha;
let mut best_score = MIN_F32; let mut best_score = MIN_F32;
let mut best_move = None; 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. /// Bitboard for color or piece bits.
pub type Bitboard = u64; 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] = [ pub const FILES: [Bitboard; 8] = [
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111, 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111,
0b00000000_00000000_00000000_00000000_00000000_00000000_11111111_00000000, 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) FILES[file as usize] & bits_after(file, rank)
} }
/// Get the bitboard of squares on lower ranks of the `square` file. /// Debug only: count positive bits of the bitboard.
#[inline] #[allow(dead_code)]
pub const fn before_on_square_file(square: Square) -> Bitboard { pub(crate) fn count_bits(bitboard: Bitboard) -> u8 {
before_on_file(sq_file(square), sq_rank(square)) 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. /// Debug only: pretty print a bitboard
#[inline] #[allow(dead_code)]
pub const fn after_on_square_file(square: Square) -> Bitboard { pub(crate) fn draw_bits(bitboard: Bitboard, f: &mut dyn std::io::Write) {
after_on_file(sq_file(square), sq_rank(square)) 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. /// Board representation with color/piece bitboards.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Board { pub struct Board {
@ -245,9 +346,7 @@ impl Board {
} }
board board
} }
}
impl Board {
/// Get combined white/black pieces bitboard. /// Get combined white/black pieces bitboard.
#[inline] #[inline]
pub fn combined(&self) -> Bitboard { pub fn combined(&self) -> Bitboard {
@ -330,16 +429,52 @@ impl Board {
None None
} }
/// Debug only: count number of pieces on board. pub fn get_bishop_rays(&self, square: Square, color: Color) -> Bitboard {
#[allow(dead_code)] // Currently used in tests only. self.get_blockable_rays(square, color, &[(1, 1), (1, -1), (-1, -1), (-1, 1)])
pub(crate) fn num_pieces(&self) -> u8 { }
let mut cbb = self.combined();
let mut count = 0; pub fn get_rook_rays(&self, square: Square, color: Color) -> Bitboard {
while cbb > 0 { self.get_blockable_rays(square, color, &[(1, 0), (0, 1), (-1, 0), (0, -1)])
count += cbb & 1; }
cbb >>= 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
}
}
} }
count as u8 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. /// Debug only: write a text view of the board.
@ -372,7 +507,7 @@ impl Board {
} }
writeln!(f, "{} {}", rank + 1, rank_str).unwrap(); writeln!(f, "{} {}", rank + 1, rank_str).unwrap();
} }
write!(f, " abcdefgh").unwrap(); writeln!(f, " abcdefgh").unwrap();
} }
} }
@ -410,35 +545,41 @@ mod tests {
// Bitboard // Bitboard
#[test] #[test]
fn test_before_on_square_file() { fn test_count_bits() {
// Only should the 4 lowest files for readability. assert_eq!(count_bits(Board::new_empty().combined()), 0);
assert_eq!(before_on_square_file(A1), 0b00000000_00000000_00000000_00000000); assert_eq!(count_bits(Board::new().combined()), 32);
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);
} }
#[test] #[test]
fn test_after_on_file() { fn test_before_on_file() {
assert_eq!(after_on_square_file(A1), 0b00000000_00000000_00000000_11111110); // Only should the 4 lowest files for readability.
assert_eq!(after_on_square_file(A2), 0b00000000_00000000_00000000_11111100); assert_eq!(before_on_file(FILE_A, RANK_1), 0b00000000_00000000_00000000_00000000);
assert_eq!(after_on_square_file(A4), 0b00000000_00000000_00000000_11110000); assert_eq!(before_on_file(FILE_A, RANK_2), 0b00000000_00000000_00000000_00000001);
assert_eq!(after_on_square_file(A8), 0b00000000_00000000_00000000_00000000); assert_eq!(before_on_file(FILE_A, RANK_4), 0b00000000_00000000_00000000_00000111);
assert_eq!(after_on_square_file(B1), 0b00000000_00000000_11111110_00000000); assert_eq!(before_on_file(FILE_A, RANK_8), 0b00000000_00000000_00000000_01111111);
assert_eq!(after_on_square_file(C1), 0b00000000_11111110_00000000_00000000); assert_eq!(before_on_file(FILE_B, RANK_1), 0b00000000_00000000_00000000_00000000);
assert_eq!(after_on_square_file(C4), 0b00000000_11110000_00000000_00000000); assert_eq!(before_on_file(FILE_C, RANK_1), 0b00000000_00000000_00000000_00000000);
assert_eq!(after_on_square_file(C8), 0b00000000_00000000_00000000_00000000); assert_eq!(before_on_file(FILE_C, RANK_4), 0b00000000_00000111_00000000_00000000);
// 4 highest files. // 4 highest files.
assert_eq!(after_on_square_file(H4), 0b11110000_00000000_00000000_00000000 << 32); assert_eq!(before_on_file(FILE_H, RANK_4), 0b00000111_00000000_00000000_00000000 << 32);
assert_eq!(after_on_square_file(H7), 0b10000000_00000000_00000000_00000000 << 32); assert_eq!(before_on_file(FILE_H, RANK_7), 0b00111111_00000000_00000000_00000000 << 32);
assert_eq!(after_on_square_file(H8), 0b00000000_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 // Board
@ -486,8 +627,95 @@ mod tests {
} }
#[test] #[test]
fn test_num_pieces() { fn test_get_bishop_rays() {
assert_eq!(Board::new_empty().num_pieces(), 0); let mut b = Board::new_empty();
assert_eq!(Board::new().num_pieces(), 32);
// 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!(b.is_empty(D4));
assert_eq!(b.get_color_on(E6), WHITE); assert_eq!(b.get_color_on(E6), WHITE);
assert_eq!(b.get_piece_on(E6), KNIGHT); 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 // Sack it with black knight
Move::new(F4, E6).apply_to_board(&mut b); Move::new(F4, E6).apply_to_board(&mut b);
assert_eq!(b.get_color_on(E6), BLACK); assert_eq!(b.get_color_on(E6), BLACK);
assert_eq!(b.get_piece_on(E6), KNIGHT); assert_eq!(b.get_piece_on(E6), KNIGHT);
assert_eq!(b.num_pieces(), 1); assert_eq!(count_bits(b.combined()), 1);
} }
#[test] #[test]

View file

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

View file

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