rules: rework illegal moves detection
This commit is contained in:
parent
54cfd43911
commit
47cc483d9e
|
@ -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);
|
let moves = rules::get_player_moves(&mut self.node.board, &mut self.node.game_state);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,7 @@ impl Engine {
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
// Castling.
|
// Castling.
|
||||||
|
self.node.game_state.castling = 0;
|
||||||
for c in fen.castling.chars() {
|
for c in fen.castling.chars() {
|
||||||
match c {
|
match c {
|
||||||
'K' => self.node.game_state.castling |= castling::CASTLE_WH_K,
|
'K' => self.node.game_state.castling |= castling::CASTLE_WH_K,
|
||||||
|
|
|
@ -95,10 +95,10 @@ impl Move {
|
||||||
} else {
|
} else {
|
||||||
board.move_square(self.dest, self.source);
|
board.move_square(self.dest, self.source);
|
||||||
if let Some(piece) = self.promotion {
|
if let Some(piece) = self.promotion {
|
||||||
board.set_piece(self.source, piece, PAWN)
|
board.set_piece(self.source, piece, PAWN);
|
||||||
}
|
}
|
||||||
if let Some(piece) = self.capture {
|
if let Some(piece) = self.capture {
|
||||||
board.set_square(self.dest, opposite(game_state.color), piece);
|
board.set_square(self.dest, game_state.color, piece);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
game_state.castling = self.old_castles;
|
game_state.castling = self.old_castles;
|
||||||
|
|
|
@ -33,13 +33,13 @@ impl Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return player moves from this node.
|
/// Return player moves from this node.
|
||||||
pub fn get_player_moves(&self) -> Vec<Move> {
|
pub fn get_player_moves(&mut self) -> Vec<Move> {
|
||||||
rules::get_player_moves(&self.board, &self.game_state)
|
rules::get_player_moves(&mut self.board, &mut self.game_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute stats for both players for this node.
|
/// Compute stats for both players for this node.
|
||||||
pub fn compute_stats(&self) -> (stats::BoardStats, stats::BoardStats) {
|
pub fn compute_stats(&mut self) -> (stats::BoardStats, stats::BoardStats) {
|
||||||
stats::BoardStats::new_from(&self.board, &self.game_state)
|
stats::BoardStats::new_from(&mut self.board, &mut self.game_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
224
src/rules.rs
224
src/rules.rs
|
@ -57,10 +57,9 @@ impl std::fmt::Display for GameState {
|
||||||
/// `pseudo_legal` as a collection of attacked squares instead of legal
|
/// `pseudo_legal` as a collection of attacked squares instead of legal
|
||||||
/// move collection.
|
/// move collection.
|
||||||
pub fn get_player_moves(
|
pub fn get_player_moves(
|
||||||
board: &Board,
|
board: &mut Board,
|
||||||
game_state: &GameState,
|
game_state: &mut GameState,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let attacked_bb = board.get_full_rays(opposite(game_state.color)); // FIXME remove, doesn't w
|
|
||||||
let mut moves = Vec::with_capacity(32);
|
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 {
|
||||||
|
@ -70,7 +69,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, attacked_bb)
|
&mut get_piece_moves(board, game_state, square, game_state.color)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,11 +84,10 @@ pub fn get_player_moves(
|
||||||
/// 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.
|
||||||
fn get_piece_moves(
|
fn get_piece_moves(
|
||||||
board: &Board,
|
board: &mut Board,
|
||||||
game_state: &GameState,
|
game_state: &mut GameState,
|
||||||
square: Square,
|
square: Square,
|
||||||
color: Color,
|
color: Color,
|
||||||
attacked_bb: Bitboard,
|
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let piece = board.get_piece_on(square);
|
let piece = board.get_piece_on(square);
|
||||||
let mut moves = Vec::with_capacity(32);
|
let mut moves = Vec::with_capacity(32);
|
||||||
|
@ -111,11 +109,10 @@ fn get_piece_moves(
|
||||||
square,
|
square,
|
||||||
color,
|
color,
|
||||||
piece,
|
piece,
|
||||||
attacked_bb,
|
|
||||||
&mut moves
|
&mut moves
|
||||||
);
|
);
|
||||||
if piece == KING && sq_rank(square) == CASTLE_RANK_BY_COLOR[color] {
|
if piece == KING && sq_rank(square) == CASTLE_RANK_BY_COLOR[color] {
|
||||||
get_king_castles(board, game_state, square, color, attacked_bb, &mut moves);
|
get_king_castles(board, game_state, square, color, &mut moves);
|
||||||
}
|
}
|
||||||
moves
|
moves
|
||||||
}
|
}
|
||||||
|
@ -126,20 +123,19 @@ fn get_piece_moves(
|
||||||
/// legal move. Does not take castle into account. Pawns that reach
|
/// legal move. Does not take castle into account. Pawns that reach
|
||||||
/// the last rank are promoted as queens.
|
/// the last rank are promoted as queens.
|
||||||
fn get_moves_from_bb(
|
fn get_moves_from_bb(
|
||||||
board: &Board,
|
board: &mut Board,
|
||||||
game_state: &GameState,
|
game_state: &mut GameState,
|
||||||
bitboard: Bitboard,
|
bitboard: Bitboard,
|
||||||
square: Square,
|
square: Square,
|
||||||
color: Color,
|
color: Color,
|
||||||
piece: Piece,
|
piece: Piece,
|
||||||
attacked_bb: Bitboard,
|
|
||||||
moves: &mut Vec<Move>
|
moves: &mut Vec<Move>
|
||||||
) {
|
) {
|
||||||
for ray_square in 0..NUM_SQUARES {
|
for ray_square in 0..NUM_SQUARES {
|
||||||
if ray_square == square || bitboard & bit_pos(ray_square) == 0 {
|
if ray_square == square || bitboard & bit_pos(ray_square) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if let Some(mut m) = inspect_move(board, game_state, square, ray_square, attacked_bb) {
|
if let Some(mut m) = inspect_move(board, game_state, square, ray_square) {
|
||||||
// Automatic queen promotion for pawns moving to the opposite rank.
|
// Automatic queen promotion for pawns moving to the opposite rank.
|
||||||
if
|
if
|
||||||
piece == PAWN
|
piece == PAWN
|
||||||
|
@ -161,16 +157,21 @@ fn get_moves_from_bb(
|
||||||
/// `ray_square` is either empty or an enemy piece, but not a friend
|
/// `ray_square` is either empty or an enemy piece, but not a friend
|
||||||
/// piece: they should have been filtered.
|
/// piece: they should have been filtered.
|
||||||
///
|
///
|
||||||
|
/// This function, in case a move is accepted, sets the `capture` field
|
||||||
|
/// if the target square hold a piece.
|
||||||
|
///
|
||||||
/// This function does not set promotions for pawns reaching last rank.
|
/// This function does not set promotions for pawns reaching last rank.
|
||||||
fn inspect_move(
|
fn inspect_move(
|
||||||
board: &Board,
|
board: &mut Board,
|
||||||
game_state: &GameState,
|
game_state: &mut GameState,
|
||||||
square: Square,
|
square: Square,
|
||||||
ray_square: Square,
|
ray_square: Square,
|
||||||
attacked_bb: Bitboard
|
|
||||||
) -> Option<Move> {
|
) -> Option<Move> {
|
||||||
let m = Move::new(square, ray_square);
|
let mut m = Move::new(square, ray_square);
|
||||||
if !is_illegal(board, game_state, &m, attacked_bb) {
|
if !is_illegal(board, game_state, &mut m) {
|
||||||
|
if !board.is_empty(ray_square) {
|
||||||
|
m.capture = Some(board.get_piece_on(ray_square))
|
||||||
|
}
|
||||||
Some(m)
|
Some(m)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -179,20 +180,21 @@ fn inspect_move(
|
||||||
|
|
||||||
/// Check if a move is illegal.
|
/// Check if a move is illegal.
|
||||||
fn is_illegal(
|
fn is_illegal(
|
||||||
board: &Board,
|
board: &mut Board,
|
||||||
game_state: &GameState,
|
game_state: &mut GameState,
|
||||||
m: &Move,
|
m: &mut Move,
|
||||||
attacked_bb: Bitboard
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
let color = game_state.color;
|
||||||
// A move is illegal if the king ends up in check.
|
// A move is illegal if the king ends up in check.
|
||||||
let king_square = if board.get_piece_on(m.source) == KING {
|
m.apply_to(board, game_state);
|
||||||
m.dest
|
if let Some(king) = board.find_king(color) {
|
||||||
|
let attacked_bb = board.get_full_rays(opposite(color));
|
||||||
|
m.unmake(board, game_state);
|
||||||
|
attacked_bb & bit_pos(king) != 0
|
||||||
} else {
|
} else {
|
||||||
if let Some(king) = board.find_king(game_state.color) { king } else { return false }
|
m.unmake(board, game_state);
|
||||||
};
|
false
|
||||||
// FIXME implement unmake move
|
}
|
||||||
let attacked_bb = board.get_full_rays(opposite(game_state.color));
|
|
||||||
attacked_bb & bit_pos(king_square) != 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get possible castles.
|
/// Get possible castles.
|
||||||
|
@ -212,7 +214,6 @@ fn get_king_castles(
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
square: Square,
|
square: Square,
|
||||||
color: Color,
|
color: Color,
|
||||||
attacked_bb: Bitboard,
|
|
||||||
moves: &mut Vec<Move>
|
moves: &mut Vec<Move>
|
||||||
) {
|
) {
|
||||||
let combined_bb = board.combined();
|
let combined_bb = board.combined();
|
||||||
|
@ -230,6 +231,7 @@ fn get_king_castles(
|
||||||
if (game_state.castling & castle_color_mask & castle_side_mask) != 0 {
|
if (game_state.castling & castle_color_mask & castle_side_mask) != 0 {
|
||||||
// Check that squares in the king's path are not attacked (R4, R5, R6).
|
// Check that squares in the king's path are not attacked (R4, R5, R6).
|
||||||
let castle_legality_path = CASTLE_LEGALITY_PATHS[color][castle_side_id];
|
let castle_legality_path = CASTLE_LEGALITY_PATHS[color][castle_side_id];
|
||||||
|
let attacked_bb = board.get_full_rays(opposite(game_state.color));
|
||||||
if attacked_bb & castle_legality_path != 0 {
|
if attacked_bb & castle_legality_path != 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -250,156 +252,166 @@ fn get_king_castles(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Like `get_piece_moves` but generate attacked bitboard.
|
|
||||||
fn get_legal_piece_moves(
|
|
||||||
board: &Board,
|
|
||||||
game_state: &GameState,
|
|
||||||
square: Square,
|
|
||||||
color: Color
|
|
||||||
) -> Vec<Move> {
|
|
||||||
let attacked_bb = board.get_full_rays(opposite(game_state.color));
|
|
||||||
get_piece_moves(board, game_state, square, color, attacked_bb)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_player_moves() {
|
fn test_get_player_moves() {
|
||||||
let b = Board::new();
|
let mut b = Board::new();
|
||||||
let gs = GameState::new();
|
let mut 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);
|
let moves = get_player_moves(&mut b, &mut gs);
|
||||||
assert_eq!(moves.len(), 20);
|
assert_eq!(moves.len(), 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_pawn_moves() {
|
fn test_get_pawn_progress_moves() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// 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_legal_piece_moves(&b, &gs, D3, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, D3, WHITE);
|
||||||
assert_eq!(moves.len(), 1);
|
assert_eq!(moves.len(), 1);
|
||||||
assert!(moves.contains(&Move::new(D3, D4)));
|
assert!(moves.iter().any(|m| m.source == D3 && m.dest == 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_legal_piece_moves(&b, &gs, E2, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
|
||||||
assert_eq!(moves.len(), 2);
|
assert_eq!(moves.len(), 2);
|
||||||
assert!(moves.contains(&Move::new(E2, E3)));
|
assert!(moves.iter().any(|m| m.source == E2 && m.dest == E3));
|
||||||
assert!(moves.contains(&Move::new(E2, E4)));
|
assert!(moves.iter().any(|m| m.source == E2 && m.dest == E4));
|
||||||
|
|
||||||
// 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_legal_piece_moves(&b, &gs, E2, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
|
||||||
assert_eq!(moves.len(), 1);
|
assert_eq!(moves.len(), 1);
|
||||||
assert!(moves.contains(&Move::new(E2, E3)));
|
assert!(moves.iter().any(|m| m.source == E2 && m.dest == 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_legal_piece_moves(&b, &gs, E2, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
|
||||||
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, BLACK, PAWN);
|
b.clear_square(E4, BLACK, PAWN);
|
||||||
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
|
||||||
assert_eq!(moves.len(), 0);
|
assert_eq!(moves.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_pawn_capture_moves() {
|
||||||
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// 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(E2, WHITE, PAWN);
|
||||||
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
|
||||||
assert_eq!(moves.len(), 1);
|
|
||||||
b.set_square(D3, BLACK, PAWN);
|
|
||||||
let moves = get_legal_piece_moves(&b, &gs, E2, WHITE);
|
|
||||||
assert_eq!(moves.len(), 2);
|
assert_eq!(moves.len(), 2);
|
||||||
|
b.set_square(F3, BLACK, PAWN);
|
||||||
|
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
|
||||||
|
assert_eq!(moves.len(), 3);
|
||||||
|
assert!(moves.iter().any(|m| m.source == E2 && m.dest == F3));
|
||||||
|
b.set_square(D3, BLACK, PAWN);
|
||||||
|
let moves = get_piece_moves(&mut b, &mut gs, E2, WHITE);
|
||||||
|
assert_eq!(moves.len(), 4);
|
||||||
|
assert!(moves.iter().any(|m| m.source == E2 && m.dest == F3));
|
||||||
|
assert!(moves.iter().any(|m| m.source == E2 && m.dest == D3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_pawn_promotion_moves() {
|
||||||
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// 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_legal_piece_moves(&b, &gs, A7, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, A7, WHITE);
|
||||||
assert_eq!(moves.len(), 1);
|
assert_eq!(moves.len(), 1);
|
||||||
assert!(moves.contains(&Move::new_promotion(A7, A8, QUEEN)));
|
let m = &moves[0];
|
||||||
|
assert_eq!(m.source, A7);
|
||||||
|
assert_eq!(m.dest, A8);
|
||||||
|
assert_eq!(m.promotion, Some(QUEEN));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_bishop_moves() {
|
fn test_get_bishop_moves() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// 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_legal_piece_moves(&b, &gs, D4, WHITE);
|
let moves = get_piece_moves(&mut b, &mut gs, D4, WHITE);
|
||||||
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.iter().any(|m| m.source == D4 && m.dest == E5));
|
||||||
assert!(moves.contains(&Move::new(D4, F6)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == F6));
|
||||||
assert!(moves.contains(&Move::new(D4, G7)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == G7));
|
||||||
assert!(moves.contains(&Move::new(D4, H8)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == H8));
|
||||||
// Going bottom-right.
|
// Going bottom-right.
|
||||||
assert!(moves.contains(&Move::new(D4, E3)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == E3));
|
||||||
assert!(moves.contains(&Move::new(D4, F2)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == F2));
|
||||||
assert!(moves.contains(&Move::new(D4, G1)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == G1));
|
||||||
// Going bottom-left.
|
// Going bottom-left.
|
||||||
assert!(moves.contains(&Move::new(D4, C3)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == C3));
|
||||||
assert!(moves.contains(&Move::new(D4, B2)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == B2));
|
||||||
assert!(moves.contains(&Move::new(D4, A1)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == A1));
|
||||||
// Going top-left.
|
// Going top-left.
|
||||||
assert!(moves.contains(&Move::new(D4, C5)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == C5));
|
||||||
assert!(moves.contains(&Move::new(D4, B6)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == B6));
|
||||||
assert!(moves.contains(&Move::new(D4, A7)));
|
assert!(moves.iter().any(|m| m.source == D4 && m.dest == A7));
|
||||||
|
|
||||||
// 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_legal_piece_moves(&b, &gs, D4, WHITE).len(), 11);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).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_legal_piece_moves(&b, &gs, D4, WHITE).len(), 12);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_knight_moves() {
|
fn test_get_knight_moves() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// 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_legal_piece_moves(&b, &gs, D4, WHITE).len(), 8);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).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_legal_piece_moves(&b, &gs, A4, WHITE).len(), 4);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, A4, WHITE).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_legal_piece_moves(&b, &gs, A1, WHITE).len(), 2);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, A1, WHITE).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_legal_piece_moves(&b, &gs, A1, WHITE).len(), 0);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, A1, WHITE).len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_rook_moves() {
|
fn test_get_rook_moves() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
b.set_square(D4, WHITE, ROOK);
|
b.set_square(D4, WHITE, ROOK);
|
||||||
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 14);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 14);
|
||||||
b.set_square(D6, BLACK, PAWN);
|
b.set_square(D6, BLACK, PAWN);
|
||||||
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 12);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 12);
|
||||||
b.set_square(D6, WHITE, PAWN);
|
b.set_square(D6, WHITE, PAWN);
|
||||||
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 11);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_queen_moves() {
|
fn test_get_queen_moves() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
b.set_square(D4, WHITE, QUEEN);
|
b.set_square(D4, WHITE, QUEEN);
|
||||||
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 14 + 13);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 14 + 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -409,27 +421,47 @@ 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_legal_piece_moves(&b, &gs, D4, WHITE).len(), 8);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).len(), 8);
|
||||||
b.set_square(E5, WHITE, PAWN);
|
b.set_square(E5, WHITE, PAWN);
|
||||||
assert_eq!(get_legal_piece_moves(&b, &gs, D4, WHITE).len(), 7);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, D4, WHITE).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_legal_piece_moves(&b, &gs, E1, WHITE).len(), 5 + 2);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, E1, WHITE).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_legal_piece_moves(&b, &gs, E8, BLACK).len(), 5 + 2);
|
assert_eq!(get_piece_moves(&mut b, &mut gs, E8, BLACK).len(), 5 + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_filter_illegal_moves() {
|
fn test_is_illegal() {
|
||||||
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
|
gs.castling = 0;
|
||||||
|
|
||||||
|
// Place white's king on first rank.
|
||||||
|
b.set_square(E1, WHITE, KING);
|
||||||
|
// Place black rook in second rank: king can only move left or right.
|
||||||
|
b.set_square(H2, BLACK, ROOK);
|
||||||
|
// Check that the king can't go to a rook controlled square.
|
||||||
|
assert!(is_illegal(&mut b, &mut gs, &mut Move::new(E1, E2)));
|
||||||
|
assert!(is_illegal(&mut b, &mut gs, &mut Move::new(E1, D2)));
|
||||||
|
assert!(is_illegal(&mut b, &mut gs, &mut Move::new(E1, F2)));
|
||||||
|
assert!(!is_illegal(&mut b, &mut gs, &mut Move::new(E1, D1)));
|
||||||
|
assert!(!is_illegal(&mut b, &mut gs, &mut Move::new(E1, F1)));
|
||||||
|
let all_wh_moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||||
|
assert_eq!(all_wh_moves.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_king_moves_legality() {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
let mut gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
|
@ -440,7 +472,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_legal_piece_moves(&b, &gs, E1, WHITE);
|
let all_wh_moves = get_piece_moves(&mut b, &mut gs, E1, WHITE);
|
||||||
assert_eq!(all_wh_moves.len(), 2);
|
assert_eq!(all_wh_moves.len(), 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
src/stats.rs
28
src/stats.rs
|
@ -31,12 +31,12 @@ impl BoardStats {
|
||||||
///
|
///
|
||||||
/// The playing color will have its stats filled in the first
|
/// The playing color will have its stats filled in the first
|
||||||
/// BoardStats object, its opponent in the second.
|
/// BoardStats object, its opponent in the second.
|
||||||
pub fn new_from(board: &Board, game_state: &GameState) -> (BoardStats, BoardStats) {
|
pub fn new_from(board: &mut Board, game_state: &mut GameState) -> (BoardStats, BoardStats) {
|
||||||
let mut stats = (BoardStats::new(), BoardStats::new());
|
let mut stats = (BoardStats::new(), BoardStats::new());
|
||||||
let mut gs = game_state.clone();
|
let mut gs = game_state.clone();
|
||||||
stats.0.compute(board, &gs);
|
stats.0.compute(board, &mut gs);
|
||||||
gs.color = opposite(gs.color);
|
gs.color = opposite(gs.color);
|
||||||
stats.1.compute(board, &gs);
|
stats.1.compute(board, &mut gs);
|
||||||
stats
|
stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ impl BoardStats {
|
||||||
///
|
///
|
||||||
/// Only the current playing side stats are created,
|
/// Only the current playing side stats are created,
|
||||||
/// prepare the game_state accordingly.
|
/// prepare the game_state accordingly.
|
||||||
pub fn compute(&mut self, board: &Board, game_state: &GameState) {
|
pub fn compute(&mut self, board: &mut Board, game_state: &mut GameState) {
|
||||||
self.reset();
|
self.reset();
|
||||||
let color = game_state.color;
|
let color = game_state.color;
|
||||||
// Compute mobility for all pieces.
|
// Compute mobility for all pieces.
|
||||||
|
@ -157,8 +157,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compute_stats() {
|
fn test_compute_stats() {
|
||||||
// Check that initial stats are correct.
|
// Check that initial stats are correct.
|
||||||
let b = Board::new();
|
let mut b = Board::new();
|
||||||
let gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
let initial_stats = BoardStats {
|
let initial_stats = BoardStats {
|
||||||
num_pawns: 8,
|
num_pawns: 8,
|
||||||
num_bishops: 2,
|
num_bishops: 2,
|
||||||
|
@ -171,7 +171,7 @@ mod tests {
|
||||||
num_isolated_pawns: 0,
|
num_isolated_pawns: 0,
|
||||||
mobility: 20,
|
mobility: 20,
|
||||||
};
|
};
|
||||||
let mut stats = BoardStats::new_from(&b, &gs);
|
let mut stats = BoardStats::new_from(&mut b, &mut gs);
|
||||||
assert!(stats.0 == stats.1);
|
assert!(stats.0 == stats.1);
|
||||||
assert!(stats.0 == initial_stats);
|
assert!(stats.0 == initial_stats);
|
||||||
|
|
||||||
|
@ -179,15 +179,15 @@ mod tests {
|
||||||
let mut b = Board::new_empty();
|
let mut b = Board::new_empty();
|
||||||
b.set_square(D4, WHITE, PAWN);
|
b.set_square(D4, WHITE, PAWN);
|
||||||
b.set_square(D6, WHITE, PAWN);
|
b.set_square(D6, WHITE, PAWN);
|
||||||
stats.0.compute(&b, &gs);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 2);
|
assert_eq!(stats.0.num_doubled_pawns, 2);
|
||||||
// Add a pawn on another file, no changes expected.
|
// Add a pawn on another file, no changes expected.
|
||||||
b.set_square(E6, WHITE, PAWN);
|
b.set_square(E6, WHITE, PAWN);
|
||||||
stats.0.compute(&b, &gs);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 2);
|
assert_eq!(stats.0.num_doubled_pawns, 2);
|
||||||
// Add a pawn backward in the d-file: there are now 3 doubled pawns.
|
// Add a pawn backward in the d-file: there are now 3 doubled pawns.
|
||||||
b.set_square(D2, WHITE, PAWN);
|
b.set_square(D2, WHITE, PAWN);
|
||||||
stats.0.compute(&b, &gs);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 3);
|
assert_eq!(stats.0.num_doubled_pawns, 3);
|
||||||
|
|
||||||
// Check that isolated and backward pawns are correctly counted.
|
// Check that isolated and backward pawns are correctly counted.
|
||||||
|
@ -195,19 +195,19 @@ mod tests {
|
||||||
assert_eq!(stats.0.num_backward_pawns, 2); // A bit weird?
|
assert_eq!(stats.0.num_backward_pawns, 2); // A bit weird?
|
||||||
// Protect d4 pawn with a friend in e3: it is not isolated nor backward anymore.
|
// Protect d4 pawn with a friend in e3: it is not isolated nor backward anymore.
|
||||||
b.set_square(E3, WHITE, PAWN);
|
b.set_square(E3, WHITE, PAWN);
|
||||||
stats.0.compute(&b, &gs);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 5);
|
assert_eq!(stats.0.num_doubled_pawns, 5);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 0);
|
assert_eq!(stats.0.num_isolated_pawns, 0);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 1);
|
assert_eq!(stats.0.num_backward_pawns, 1);
|
||||||
// Add an adjacent friend to d2 pawn: no pawns are left isolated or backward.
|
// Add an adjacent friend to d2 pawn: no pawns are left isolated or backward.
|
||||||
b.set_square(C2, WHITE, PAWN);
|
b.set_square(C2, WHITE, PAWN);
|
||||||
stats.0.compute(&b, &gs);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 5);
|
assert_eq!(stats.0.num_doubled_pawns, 5);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 0);
|
assert_eq!(stats.0.num_isolated_pawns, 0);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 0);
|
assert_eq!(stats.0.num_backward_pawns, 0);
|
||||||
// Add an isolated/backward white pawn in a far file.
|
// Add an isolated/backward white pawn in a far file.
|
||||||
b.set_square(A2, WHITE, PAWN);
|
b.set_square(A2, WHITE, PAWN);
|
||||||
stats.0.compute(&b, &gs);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 5);
|
assert_eq!(stats.0.num_doubled_pawns, 5);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 1);
|
assert_eq!(stats.0.num_isolated_pawns, 1);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 1);
|
assert_eq!(stats.0.num_backward_pawns, 1);
|
||||||
|
@ -218,7 +218,7 @@ mod tests {
|
||||||
b.set_square(D4, WHITE, PAWN);
|
b.set_square(D4, WHITE, PAWN);
|
||||||
b.set_square(E5, WHITE, PAWN);
|
b.set_square(E5, WHITE, PAWN);
|
||||||
b.set_square(E3, WHITE, PAWN);
|
b.set_square(E3, WHITE, PAWN);
|
||||||
stats.0.compute(&b, &gs);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 2);
|
assert_eq!(stats.0.num_doubled_pawns, 2);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 0);
|
assert_eq!(stats.0.num_isolated_pawns, 0);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 1);
|
assert_eq!(stats.0.num_backward_pawns, 1);
|
||||||
|
|
Reference in a new issue