board: compute move ray bitboards
This commit is contained in:
parent
8b0f4c9255
commit
5efdd5407b
17
README.md
17
README.md
|
@ -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
29
res/scripts/gen_knight_rays.py
Executable 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())))
|
|
@ -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;
|
||||
|
|
324
src/board.rs
324
src/board.rs
|
@ -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)])
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.
|
||||
|
|
133
src/rules.rs
133
src/rules.rs
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Reference in a new issue