2020-05-31 02:41:02 +02:00
|
|
|
//! Functions to determine legal moves.
|
|
|
|
|
|
|
|
use crate::board::*;
|
2020-06-14 20:32:40 +02:00
|
|
|
use crate::castling::*;
|
2020-06-19 19:29:10 +02:00
|
|
|
use crate::fen;
|
|
|
|
use crate::movement::Move;
|
|
|
|
|
2020-06-20 03:42:20 +02:00
|
|
|
pub const POS_MIN: i8 = 0;
|
|
|
|
pub const POS_MAX: i8 = 7;
|
2020-05-31 02:41:02 +02:00
|
|
|
|
2020-06-05 18:46:08 +02:00
|
|
|
/// Characteristics of the state of a game.
|
|
|
|
///
|
|
|
|
/// It does not include various parameters such as clocks that are
|
|
|
|
/// more aimed for engine analysis than typical rules checking.
|
2020-06-06 00:47:07 +02:00
|
|
|
///
|
|
|
|
/// - `color`: current player's turn
|
|
|
|
/// - `castling`: which castling options are available; updated throughout the game.
|
2020-06-14 01:33:11 +02:00
|
|
|
/// - `en_passant`: position of a pawn that can be taken using en passant attack.
|
|
|
|
/// - `halfmove`: eh not sure
|
|
|
|
/// - `fullmove`: same
|
|
|
|
#[derive(Debug, PartialEq, Clone, Hash)]
|
2020-06-05 18:46:08 +02:00
|
|
|
pub struct GameState {
|
2020-06-19 02:44:33 +02:00
|
|
|
pub color: Color,
|
2020-06-05 18:46:08 +02:00
|
|
|
pub castling: u8,
|
2020-06-19 02:44:33 +02:00
|
|
|
pub en_passant: Option<Square>,
|
2020-06-05 18:46:08 +02:00
|
|
|
pub halfmove: i32,
|
|
|
|
pub fullmove: i32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GameState {
|
|
|
|
pub const fn new() -> GameState {
|
|
|
|
GameState {
|
2020-06-19 02:44:33 +02:00
|
|
|
color: WHITE,
|
2020-06-05 18:46:08 +02:00
|
|
|
castling: CASTLING_MASK,
|
|
|
|
en_passant: None,
|
|
|
|
halfmove: 0,
|
|
|
|
fullmove: 1,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-11 19:33:18 +02:00
|
|
|
impl std::fmt::Display for GameState {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
write!(
|
|
|
|
f,
|
2020-06-20 21:05:54 +02:00
|
|
|
"[color: {}, castling: {:04b}, en_passant: {}, halfmove: {}, fullmove: {}]",
|
2020-06-11 20:40:08 +02:00
|
|
|
color_to_string(self.color), self.castling,
|
2020-06-19 19:29:10 +02:00
|
|
|
fen::en_passant_to_string(self.en_passant),
|
2020-06-11 19:33:18 +02:00
|
|
|
self.halfmove, self.fullmove
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
/// Get a list of moves for all pieces of the playing color.
|
|
|
|
///
|
2020-06-21 00:33:05 +02:00
|
|
|
/// 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.
|
2020-06-14 20:32:40 +02:00
|
|
|
pub fn get_player_moves(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-14 20:32:40 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-21 00:33:05 +02:00
|
|
|
let mut moves = Vec::with_capacity(32);
|
2020-05-31 02:41:02 +02:00
|
|
|
for r in 0..8 {
|
|
|
|
for f in 0..8 {
|
2020-06-19 19:29:10 +02:00
|
|
|
let square = sq(f, r);
|
|
|
|
if board.is_empty(square) {
|
2020-05-31 19:00:56 +02:00
|
|
|
continue
|
|
|
|
}
|
2020-06-20 03:42:20 +02:00
|
|
|
if board.get_color_on(square) == game_state.color {
|
2020-06-19 19:29:10 +02:00
|
|
|
moves.append(
|
2020-06-21 00:33:05 +02:00
|
|
|
&mut get_piece_moves(board, game_state, square, game_state.color, pseudo_legal)
|
2020-06-19 19:29:10 +02:00
|
|
|
);
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
moves
|
|
|
|
}
|
|
|
|
|
2020-06-19 19:29:10 +02:00
|
|
|
/// Get a list of moves for the piece of `color` on `square`.
|
|
|
|
///
|
|
|
|
/// Use `board` and `game_state` to get the moves. `color` is the color
|
|
|
|
/// 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.
|
2020-06-21 00:33:05 +02:00
|
|
|
fn get_piece_moves(
|
2020-06-14 20:32:40 +02:00
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-19 19:29:10 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-14 20:32:40 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-20 03:42:20 +02:00
|
|
|
match board.get_piece_on(square) {
|
2020-06-21 00:33:05 +02:00
|
|
|
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.") },
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-21 15:57:58 +02:00
|
|
|
// fn get_pawn_moves(
|
|
|
|
// board: &Board,
|
|
|
|
// game_state: &GameState,
|
|
|
|
// square: Square,
|
|
|
|
// color: Color,
|
|
|
|
// pseudo_legal: bool,
|
|
|
|
// ) -> Vec<Move> {
|
|
|
|
// let (f, r) = (sq_file(square), sq_rank(square));
|
|
|
|
// let mut moves = vec!();
|
|
|
|
// // Direction: positive for white, negative for black.
|
|
|
|
// let dir = if color == WHITE { 1 } else { -1 };
|
|
|
|
// // Check 1 or 2 square forward.
|
|
|
|
// let move_len = if (color == WHITE && r == 1) || (color == BLACK && r == 6) { 2 } else { 1 };
|
|
|
|
// for i in 1..=move_len {
|
|
|
|
// let forward_r = r + dir * i;
|
|
|
|
// if dir > 0 && forward_r > POS_MAX {
|
|
|
|
// return moves
|
|
|
|
// }
|
|
|
|
// if dir < 0 && forward_r < POS_MIN {
|
|
|
|
// return moves
|
|
|
|
// }
|
|
|
|
// let forward: Square = sq(f, forward_r);
|
|
|
|
// // If forward square is empty (and we are not jumping over an occupied square), add it.
|
|
|
|
// if board.is_empty(forward) && (i == 1 || board.is_empty(sq(f, forward_r - dir))) {
|
|
|
|
// let mut m = Move::new(square, forward);
|
|
|
|
// // Pawns that get to the opposite rank automatically promote as queens.
|
|
|
|
// if (dir > 0 && forward_r == POS_MAX) || (dir < 0 && forward_r == POS_MIN) {
|
|
|
|
// m.promotion = Some(QUEEN)
|
|
|
|
// }
|
|
|
|
// if pseudo_legal || !is_illegal(board, game_state, &m) {
|
|
|
|
// moves.push(m);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// // Check diagonals for pieces to attack.
|
|
|
|
// if i == 1 {
|
|
|
|
// // First diagonal.
|
|
|
|
// if f - 1 >= POS_MIN {
|
|
|
|
// let diag = sq(f - 1, forward_r);
|
|
|
|
// 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 pseudo_legal || !is_illegal(board, game_state, &m) {
|
|
|
|
// moves.push(m);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// // Second diagonal.
|
|
|
|
// if f + 1 <= POS_MAX {
|
|
|
|
// let diag = sq(f + 1, forward_r);
|
|
|
|
// 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 pseudo_legal || !is_illegal(board, game_state, &m) {
|
|
|
|
// moves.push(m);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// // TODO en passant
|
|
|
|
// }
|
|
|
|
// moves
|
|
|
|
// }
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
fn get_pawn_moves(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-19 19:29:10 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-06 00:47:07 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-21 15:57:58 +02:00
|
|
|
get_moves_from_bb(
|
|
|
|
board,
|
|
|
|
game_state,
|
|
|
|
board.get_pawn_progresses(square, color) | board.get_pawn_captures(square, color),
|
|
|
|
square,
|
|
|
|
color,
|
|
|
|
PAWN,
|
|
|
|
pseudo_legal
|
|
|
|
)
|
|
|
|
// TODO en passant
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
fn get_bishop_moves(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-19 19:29:10 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-06 00:47:07 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-21 01:26:04 +02:00
|
|
|
get_moves_from_bb(
|
|
|
|
board,
|
|
|
|
game_state,
|
|
|
|
board.get_bishop_rays(square, color),
|
|
|
|
square,
|
|
|
|
color,
|
|
|
|
BISHOP,
|
|
|
|
pseudo_legal
|
|
|
|
)
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
fn get_knight_moves(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-19 19:29:10 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-06 00:47:07 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-21 01:26:04 +02:00
|
|
|
get_moves_from_bb(
|
|
|
|
board,
|
|
|
|
game_state,
|
|
|
|
board.get_knight_rays(square, color),
|
|
|
|
square,
|
|
|
|
color,
|
|
|
|
KNIGHT,
|
|
|
|
pseudo_legal
|
|
|
|
)
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
fn get_rook_moves(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-19 19:29:10 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-06 00:47:07 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-21 01:26:04 +02:00
|
|
|
get_moves_from_bb(
|
|
|
|
board,
|
|
|
|
game_state,
|
|
|
|
board.get_rook_rays(square, color),
|
|
|
|
square,
|
|
|
|
color,
|
|
|
|
ROOK,
|
|
|
|
pseudo_legal
|
|
|
|
)
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
fn get_queen_moves(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-19 19:29:10 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-06 00:47:07 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-21 01:26:04 +02:00
|
|
|
get_moves_from_bb(
|
|
|
|
board,
|
|
|
|
game_state,
|
|
|
|
board.get_queen_rays(square, color),
|
|
|
|
square,
|
|
|
|
color,
|
|
|
|
QUEEN,
|
|
|
|
pseudo_legal
|
|
|
|
)
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
fn get_king_moves(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-19 19:29:10 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 00:33:05 +02:00
|
|
|
pseudo_legal: bool,
|
2020-06-06 00:47:07 +02:00
|
|
|
) -> Vec<Move> {
|
2020-06-21 01:26:04 +02:00
|
|
|
let mut moves = get_moves_from_bb(
|
|
|
|
board,
|
|
|
|
game_state,
|
|
|
|
board.get_king_rays(square, color),
|
|
|
|
square,
|
|
|
|
color,
|
|
|
|
KING,
|
|
|
|
pseudo_legal
|
|
|
|
);
|
2020-06-06 00:47:07 +02:00
|
|
|
|
2020-06-21 00:33:05 +02:00
|
|
|
// Stop here for pseudo legal moves as castling is not considered along with them.
|
|
|
|
if pseudo_legal {
|
2020-06-06 00:47:07 +02:00
|
|
|
return moves
|
|
|
|
}
|
|
|
|
|
|
|
|
// Castling. Here are the rules that should ALL be respected:
|
|
|
|
// 1. The king and the chosen rook are on the player's first rank.
|
|
|
|
// 2. Neither the king nor the chosen rook has previously moved.
|
|
|
|
// 3. There are no pieces between the king and the chosen rook.
|
|
|
|
// 4. The king is not currently in check.
|
|
|
|
// 5. The king does not pass through a square that is attacked by an enemy piece.
|
|
|
|
// 6. The king does not end up in check.
|
|
|
|
|
|
|
|
// First get the required castling rank and color mask for the player.
|
2020-06-19 19:29:10 +02:00
|
|
|
let (castling_rank, castling_color_mask) = if game_state.color == WHITE {
|
2020-06-06 00:47:07 +02:00
|
|
|
(0, CASTLING_WH_MASK)
|
|
|
|
} else {
|
|
|
|
(7, CASTLING_BL_MASK)
|
|
|
|
};
|
|
|
|
|
2020-06-21 01:26:04 +02:00
|
|
|
let r = sq_rank(square);
|
2020-06-06 01:27:19 +02:00
|
|
|
// Check for castling if the king is on its castling rank (R1)
|
2020-06-11 19:32:47 +02:00
|
|
|
// and is not in check (R4).
|
2020-06-06 00:47:07 +02:00
|
|
|
if
|
2020-06-19 19:29:10 +02:00
|
|
|
r == castling_rank &&
|
|
|
|
!is_attacked(board, game_state, square)
|
2020-06-06 00:47:07 +02:00
|
|
|
{
|
|
|
|
// Check for both castling sides.
|
2020-06-11 19:32:47 +02:00
|
|
|
for (path_files, opt_empty_file, castling_side_mask) in CASTLING_SIDES.iter() {
|
2020-06-06 00:47:07 +02:00
|
|
|
// Check for castling availability for this color and side.
|
2020-06-06 01:27:19 +02:00
|
|
|
if (game_state.castling & castling_color_mask & castling_side_mask) != 0 {
|
2020-06-11 19:32:47 +02:00
|
|
|
// Check that squares in the king's path are empty and not attacked (R3.1, R5, R6).
|
|
|
|
let mut path_is_clear = true;
|
|
|
|
for path_f in path_files {
|
2020-06-19 19:29:10 +02:00
|
|
|
let path_square = sq(*path_f, castling_rank);
|
|
|
|
if
|
|
|
|
!board.is_empty(path_square)
|
|
|
|
|| is_illegal(board, game_state, &Move::new(square, path_square)) {
|
2020-06-11 19:32:47 +02:00
|
|
|
path_is_clear = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !path_is_clear {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Check that rook jumps over an empty square on queen-side (R3.2).
|
|
|
|
if let Some(rook_path_f) = opt_empty_file {
|
2020-06-19 19:29:10 +02:00
|
|
|
let rook_path_square = sq(*rook_path_f, castling_rank);
|
|
|
|
if !board.is_empty(rook_path_square) {
|
2020-06-11 19:32:47 +02:00
|
|
|
continue;
|
2020-06-06 00:47:07 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-11 19:32:47 +02:00
|
|
|
let castle = castling_side_mask & castling_color_mask;
|
2020-06-19 19:29:10 +02:00
|
|
|
let m = Move::get_castle_move(castle);
|
2020-06-21 00:33:05 +02:00
|
|
|
if pseudo_legal || !is_illegal(board, game_state, &m) {
|
2020-06-11 19:32:47 +02:00
|
|
|
moves.push(m);
|
|
|
|
}
|
2020-06-06 00:47:07 +02:00
|
|
|
}
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
moves
|
|
|
|
}
|
|
|
|
|
2020-06-21 01:26:04 +02:00
|
|
|
/// Get moves from this ray bitboard.
|
2020-06-21 15:57:58 +02:00
|
|
|
///
|
|
|
|
/// Inspect all moves from the bitboard and produce a Move for each
|
|
|
|
/// legal move, or all moves if `pseudo_legal` is true. Pawns that
|
|
|
|
/// reach the last rank are promoted as queens.
|
2020-06-21 01:26:04 +02:00
|
|
|
fn get_moves_from_bb(
|
2020-06-20 19:56:00 +02:00
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
2020-06-21 01:26:04 +02:00
|
|
|
bitboard: Bitboard,
|
2020-06-20 19:56:00 +02:00
|
|
|
square: Square,
|
|
|
|
color: Color,
|
2020-06-21 01:26:04 +02:00
|
|
|
piece: Piece,
|
|
|
|
pseudo_legal: bool
|
|
|
|
) -> Vec<Move> {
|
|
|
|
let mut moves = Vec::with_capacity(count_bits(bitboard).into());
|
|
|
|
for ray_square in 0..NUM_SQUARES {
|
|
|
|
if ray_square == square || bitboard & bit_pos(ray_square) == 0 {
|
|
|
|
continue
|
2020-06-20 19:56:00 +02:00
|
|
|
}
|
2020-06-21 01:26:04 +02:00
|
|
|
if let Some(mut m) = inspect_move(board, game_state, square, ray_square, pseudo_legal) {
|
|
|
|
// Automatic queen promotion for pawns moving to the opposite rank.
|
|
|
|
if
|
|
|
|
piece == PAWN
|
|
|
|
&& (color == WHITE && sq_rank(ray_square) == RANK_8)
|
|
|
|
|| (color == BLACK && sq_rank(ray_square) == RANK_1)
|
|
|
|
{
|
|
|
|
m.promotion = Some(QUEEN);
|
2020-06-20 19:56:00 +02:00
|
|
|
}
|
2020-06-21 01:26:04 +02:00
|
|
|
moves.push(m);
|
2020-06-20 19:56:00 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-21 01:26:04 +02:00
|
|
|
moves
|
2020-06-20 19:56:00 +02:00
|
|
|
}
|
|
|
|
|
2020-06-21 01:26:04 +02:00
|
|
|
/// Accept or ignore a move from `square` to `ray_square`.
|
|
|
|
///
|
|
|
|
/// This function checks that the move is legal, unless `pseudo_legal`
|
|
|
|
/// is true. It assumes that `ray_square` is either empty or an enemy
|
|
|
|
/// piece, but not a friend piece: they should have been filtered.
|
2020-06-06 00:47:07 +02:00
|
|
|
///
|
2020-06-21 01:26:04 +02:00
|
|
|
/// This function does not set promotions for pawns reaching last rank.
|
|
|
|
fn inspect_move(
|
|
|
|
board: &Board,
|
|
|
|
game_state: &GameState,
|
|
|
|
square: Square,
|
|
|
|
ray_square: Square,
|
|
|
|
pseudo_legal: bool
|
|
|
|
) -> Option<Move> {
|
|
|
|
let m = Move::new(square, ray_square);
|
|
|
|
if pseudo_legal || !is_illegal(board, game_state, &m) {
|
|
|
|
Some(m)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2020-06-06 00:47:07 +02:00
|
|
|
}
|
|
|
|
|
2020-06-19 19:29:10 +02:00
|
|
|
/// Return a move from `square1` to `square2` if colors are opposite.
|
2020-06-20 19:56:00 +02:00
|
|
|
fn get_capture_move(
|
2020-06-19 19:29:10 +02:00
|
|
|
color1: Color,
|
|
|
|
square1: Square,
|
|
|
|
color2: Color,
|
|
|
|
square2: Square,
|
|
|
|
is_pawn: bool,
|
|
|
|
) -> Option<Move> {
|
|
|
|
if color2 == opposite(color1) {
|
2020-06-04 19:19:24 +02:00
|
|
|
// Automatic queen promotion for pawns moving to the opposite rank.
|
2020-06-19 19:29:10 +02:00
|
|
|
Some(if
|
|
|
|
is_pawn
|
|
|
|
&& (color1 == WHITE && sq_rank(square2) == POS_MAX)
|
|
|
|
|| (color1 == BLACK && sq_rank(square2) == POS_MIN)
|
2020-06-04 19:19:24 +02:00
|
|
|
{
|
2020-06-19 19:29:10 +02:00
|
|
|
Move::new_promotion(square1, square2, QUEEN)
|
2020-06-04 19:19:24 +02:00
|
|
|
} else {
|
2020-06-19 19:29:10 +02:00
|
|
|
Move::new(square1, square2)
|
|
|
|
})
|
2020-05-31 02:41:02 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
/// Check if a move is illegal.
|
|
|
|
fn is_illegal(board: &Board, game_state: &GameState, m: &Move) -> bool {
|
2020-06-19 19:29:10 +02:00
|
|
|
if let Some(mut king_square) = board.find_king(game_state.color) {
|
2020-06-20 19:56:00 +02:00
|
|
|
let mut hypothetic_board = board.clone();
|
|
|
|
m.apply_to_board(&mut hypothetic_board);
|
|
|
|
// A move is illegal if the king ends up in check.
|
2020-06-06 00:47:07 +02:00
|
|
|
// If king moves, use its new position.
|
2020-06-19 19:29:10 +02:00
|
|
|
if m.source == king_square {
|
|
|
|
king_square = m.dest
|
|
|
|
}
|
2020-05-31 19:00:56 +02:00
|
|
|
// Check if the move makes the player king in check.
|
2020-06-19 19:29:10 +02:00
|
|
|
if is_attacked(&hypothetic_board, &game_state, king_square) {
|
2020-06-06 00:47:07 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
2020-05-31 19:00:56 +02:00
|
|
|
}
|
|
|
|
|
2020-06-19 19:29:10 +02:00
|
|
|
/// Return true if the piece on `square` is attacked.
|
2020-06-05 18:46:08 +02:00
|
|
|
///
|
2020-06-06 00:47:07 +02:00
|
|
|
/// Check all possible enemy moves and return true when one of them
|
|
|
|
/// ends up attacking the position.
|
|
|
|
///
|
2020-06-05 18:46:08 +02:00
|
|
|
/// Beware that the game state must be coherent with the analysed
|
2020-06-19 19:29:10 +02:00
|
|
|
/// square, i.e. if the piece on `square` is white, the game state
|
|
|
|
/// should tell that it is white turn. If `square` is empty, simply
|
2020-06-06 00:47:07 +02:00
|
|
|
/// check if it is getting attacked by the opposite player.
|
2020-06-19 19:29:10 +02:00
|
|
|
fn is_attacked(board: &Board, game_state: &GameState, square: Square) -> bool {
|
2020-06-06 00:47:07 +02:00
|
|
|
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.
|
2020-06-21 00:33:05 +02:00
|
|
|
let enemy_moves = get_player_moves(board, &enemy_game_state, true);
|
2020-05-31 19:00:56 +02:00
|
|
|
for m in enemy_moves.iter() {
|
2020-06-19 19:29:10 +02:00
|
|
|
if square == m.dest {
|
2020-05-31 19:00:56 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2020-05-31 02:41:02 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2020-06-06 00:47:07 +02:00
|
|
|
|
2020-05-31 02:41:02 +02:00
|
|
|
#[test]
|
2020-05-31 19:00:56 +02:00
|
|
|
fn test_get_player_moves() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let b = Board::new();
|
2020-06-06 00:47:07 +02:00
|
|
|
let gs = GameState::new();
|
2020-06-05 18:46:08 +02:00
|
|
|
|
2020-05-31 02:41:02 +02:00
|
|
|
// At first move, white has 16 pawn moves and 4 knight moves.
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_player_moves(&b, &gs, false);
|
2020-05-31 02:41:02 +02:00
|
|
|
assert_eq!(moves.len(), 20);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-05-31 19:00:56 +02:00
|
|
|
fn test_get_pawn_moves() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
2020-06-06 00:47:07 +02:00
|
|
|
let gs = GameState::new();
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// Check that a pawn (here white queen's pawn) can move forward if the road is free.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D3, WHITE, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, D3, WHITE, false);
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.len() == 1 && moves.contains(&Move::new(D3, D4)));
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// Check that a pawn (here white king's pawn) can move 2 square forward on first move.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(E2, WHITE, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
|
2020-05-31 02:41:02 +02:00
|
|
|
assert_eq!(moves.len(), 2);
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.contains(&Move::new(E2, E3)));
|
|
|
|
assert!(moves.contains(&Move::new(E2, E4)));
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// Check that a pawn cannot move forward if a piece is blocking its path.
|
2020-06-04 18:58:00 +02:00
|
|
|
// 1. black pawn 2 square forward; only 1 square forward available from start pos.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(E4, BLACK, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.len() == 1 && moves.contains(&Move::new(E2, E3)));
|
2020-06-04 18:58:00 +02:00
|
|
|
// 2. black pawn 1 square forward; no square available.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(E3, BLACK, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
|
2020-05-31 02:41:02 +02:00
|
|
|
assert_eq!(moves.len(), 0);
|
2020-06-04 18:58:00 +02:00
|
|
|
// 3. remove the e4 black pawn; the white pawn should not be able to jump above e3 pawn.
|
2020-06-21 15:57:58 +02:00
|
|
|
b.clear_square(E4, BLACK, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
|
2020-06-04 18:58:00 +02:00
|
|
|
assert_eq!(moves.len(), 0);
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// Check that a pawn can take a piece diagonally.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(F3, BLACK, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.len() == 1 && moves.contains(&Move::new(E2, F3)));
|
|
|
|
b.set_square(D3, BLACK, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
|
2020-05-31 02:41:02 +02:00
|
|
|
assert_eq!(moves.len(), 2);
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.contains( &Move::new(E2, F3) ));
|
|
|
|
assert!(moves.contains( &Move::new(E2, D3) ));
|
2020-06-04 19:19:24 +02:00
|
|
|
|
|
|
|
// Check that a pawn moving to the last rank leads to queen promotion.
|
|
|
|
// 1. by simply moving forward.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(A7, WHITE, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, A7, WHITE, false);
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.len() == 1 && moves.contains(&Move::new_promotion(A7, A8, QUEEN)));
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-05-31 19:00:56 +02:00
|
|
|
fn test_get_bishop_moves() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
2020-06-06 00:47:07 +02:00
|
|
|
let gs = GameState::new();
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// A bishop has maximum range when it's in a center square.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D4, WHITE, BISHOP);
|
2020-06-21 00:33:05 +02:00
|
|
|
let moves = get_piece_moves(&b, &gs, D4, WHITE, false);
|
2020-05-31 02:41:02 +02:00
|
|
|
assert_eq!(moves.len(), 13);
|
|
|
|
// Going top-right.
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.contains(&Move::new(D4, E5)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, F6)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, G7)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, H8)));
|
2020-05-31 02:41:02 +02:00
|
|
|
// Going bottom-right.
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.contains(&Move::new(D4, E3)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, F2)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, G1)));
|
2020-05-31 02:41:02 +02:00
|
|
|
// Going bottom-left.
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.contains(&Move::new(D4, C3)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, B2)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, A1)));
|
2020-05-31 02:41:02 +02:00
|
|
|
// Going top-left.
|
2020-06-19 19:29:10 +02:00
|
|
|
assert!(moves.contains(&Move::new(D4, C5)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, B6)));
|
|
|
|
assert!(moves.contains(&Move::new(D4, A7)));
|
2020-05-31 02:41:02 +02:00
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
// When blocking commit to one square with friendly piece, lose 2 moves.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(B2, WHITE, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 11);
|
2020-05-31 02:41:02 +02:00
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
// When blocking commit to one square with enemy piece, lose only 1 move.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(B2, BLACK, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 12);
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-05-31 19:00:56 +02:00
|
|
|
fn test_get_knight_moves() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
2020-06-06 00:47:07 +02:00
|
|
|
let gs = GameState::new();
|
2020-05-31 02:41:02 +02:00
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
// A knight never has blocked commit; if it's in the center of the board, it can have up to
|
2020-05-31 02:41:02 +02:00
|
|
|
// 8 moves.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D4, WHITE, KNIGHT);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 8);
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// If on a side if has only 4 moves.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(A4, WHITE, KNIGHT);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, A4, WHITE, false).len(), 4);
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// And in a corner, only 2 moves.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(A1, WHITE, KNIGHT);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, false).len(), 2);
|
2020-05-31 02:41:02 +02:00
|
|
|
|
|
|
|
// Add 2 friendly pieces and it is totally blocked.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(B3, WHITE, PAWN);
|
|
|
|
b.set_square(C2, WHITE, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, false).len(), 0);
|
2020-05-31 19:00:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_rook_moves() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
2020-06-06 00:47:07 +02:00
|
|
|
let gs = GameState::new();
|
2020-05-31 19:00:56 +02:00
|
|
|
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D4, WHITE, ROOK);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 14);
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D6, BLACK, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 12);
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D6, WHITE, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 11);
|
2020-05-31 19:00:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_queen_moves() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
2020-06-06 00:47:07 +02:00
|
|
|
let gs = GameState::new();
|
2020-05-31 19:00:56 +02:00
|
|
|
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D4, WHITE, QUEEN);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 14 + 13);
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-05-31 19:00:56 +02:00
|
|
|
fn test_get_king_moves() {
|
2020-06-06 00:47:07 +02:00
|
|
|
let mut gs = GameState::new();
|
2020-05-31 02:41:02 +02:00
|
|
|
|
2020-06-06 00:47:07 +02:00
|
|
|
// King can move 1 square in any direction.
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
|
|
|
b.set_square(D4, WHITE, KING);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 8);
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(E5, WHITE, PAWN);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, false).len(), 7);
|
2020-06-06 00:47:07 +02:00
|
|
|
|
|
|
|
// If castling is available, other moves are possible: 5 moves + 2 castles.
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
|
|
|
b.set_square(E1, WHITE, KING);
|
|
|
|
b.set_square(A1, WHITE, ROOK);
|
|
|
|
b.set_square(H1, WHITE, ROOK);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, E1, WHITE, false).len(), 5 + 2);
|
2020-06-06 00:47:07 +02:00
|
|
|
|
|
|
|
// Castling works as well for black.
|
2020-06-19 19:29:10 +02:00
|
|
|
gs.color = BLACK;
|
|
|
|
b.set_square(E8, BLACK, KING);
|
|
|
|
b.set_square(A8, BLACK, ROOK);
|
|
|
|
b.set_square(H8, BLACK, ROOK);
|
2020-06-21 00:33:05 +02:00
|
|
|
assert_eq!(get_piece_moves(&b, &gs, E8, BLACK, false).len(), 5 + 2);
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-05-31 19:00:56 +02:00
|
|
|
fn test_filter_illegal_moves() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
2020-06-06 01:27:19 +02:00
|
|
|
let mut gs = GameState::new();
|
2020-05-31 02:41:02 +02:00
|
|
|
|
2020-05-31 19:00:56 +02:00
|
|
|
// Place white's king on first rank.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(E1, WHITE, KING);
|
2020-05-31 19:00:56 +02:00
|
|
|
// Place black rook in second rank: king can only move left or right.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(H2, BLACK, ROOK);
|
2020-06-06 01:27:19 +02:00
|
|
|
// No castling available.
|
|
|
|
gs.castling = 0;
|
|
|
|
// 5 moves in absolute but only 2 are legal.
|
2020-06-21 00:33:05 +02:00
|
|
|
let all_wh_moves = get_piece_moves(&b, &gs, E1, WHITE, false);
|
2020-06-06 01:27:19 +02:00
|
|
|
assert_eq!(all_wh_moves.len(), 2);
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-05-31 19:00:56 +02:00
|
|
|
fn test_is_attacked() {
|
2020-06-19 19:29:10 +02:00
|
|
|
let mut b = Board::new_empty();
|
2020-06-06 00:47:07 +02:00
|
|
|
let gs = GameState::new();
|
2020-05-31 02:41:02 +02:00
|
|
|
|
2020-05-31 19:00:56 +02:00
|
|
|
// Place a black rook in white pawn's file.
|
2020-06-19 19:29:10 +02:00
|
|
|
b.set_square(D4, WHITE, PAWN);
|
|
|
|
b.set_square(D6, BLACK, ROOK);
|
|
|
|
assert!(is_attacked(&b, &gs, D4));
|
2020-05-31 19:00:56 +02:00
|
|
|
// Move the rook on another file, no more attack.
|
2020-06-19 19:29:10 +02:00
|
|
|
Move::new(D6, E6).apply_to_board(&mut b);
|
|
|
|
assert!(!is_attacked(&b, &gs, D4));
|
2020-05-31 02:41:02 +02:00
|
|
|
}
|
|
|
|
}
|