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 .
|
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
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 {
|
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;
|
||||||
|
|
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.
|
/// 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,17 +192,98 @@ 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)]
|
||||||
|
@ -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;
|
|
||||||
while cbb > 0 {
|
|
||||||
count += cbb & 1;
|
|
||||||
cbb >>= 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.
|
/// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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.
|
||||||
|
|
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.
|
/// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Reference in a new issue