This repository has been archived on 2023-03-04. You can view files and clone it, but cannot push or open issues or pull requests.
Vatu/src/rules.rs

699 lines
24 KiB
Rust
Raw Normal View History

2020-05-31 02:41:02 +02:00
//! Functions to determine legal moves.
use crate::board::*;
use crate::castling::*;
2020-06-19 19:29:10 +02:00
use crate::fen;
use crate::movement::Move;
pub const POS_MIN: i8 = 0;
pub const POS_MAX: i8 = 7;
2020-05-31 02:41:02 +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.
/// - `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)]
pub struct GameState {
2020-06-19 02:44:33 +02:00
pub color: Color,
pub castling: u8,
2020-06-19 02:44:33 +02:00
pub en_passant: Option<Square>,
pub halfmove: i32,
pub fullmove: i32,
}
impl GameState {
pub const fn new() -> GameState {
GameState {
2020-06-19 02:44:33 +02:00
color: WHITE,
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.
pub fn get_player_moves(
board: &Board,
game_state: &GameState,
2020-06-21 00:33:05 +02:00
pseudo_legal: bool,
) -> 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
}
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(
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,
) -> Vec<Move> {
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-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-19 19:29:10 +02:00
let (f, r) = (sq_file(square), sq_rank(square));
2020-05-31 02:41:02 +02:00
let mut moves = vec!();
2020-06-04 19:19:24 +02:00
// Direction: positive for white, negative for black.
2020-06-19 19:29:10 +02:00
let dir = if color == WHITE { 1 } else { -1 };
2020-05-31 02:41:02 +02:00
// Check 1 or 2 square forward.
2020-06-19 19:29:10 +02:00
let move_len = if (color == WHITE && r == 1) || (color == BLACK && r == 6) { 2 } else { 1 };
2020-05-31 02:41:02 +02:00
for i in 1..=move_len {
2020-06-04 19:19:24 +02:00
let forward_r = r + dir * i;
if dir > 0 && forward_r > POS_MAX {
2020-05-31 02:41:02 +02:00
return moves
}
2020-06-04 19:19:24 +02:00
if dir < 0 && forward_r < POS_MIN {
2020-05-31 02:41:02 +02:00
return moves
}
2020-06-19 19:29:10 +02:00
let forward: Square = sq(f, forward_r);
// If forward square is empty (and we are not jumping over an occupied square), add it.
2020-06-19 19:29:10 +02:00
if board.is_empty(forward) && (i == 1 || board.is_empty(sq(f, forward_r - dir))) {
let mut m = Move::new(square, forward);
2020-06-04 19:19:24 +02:00
// Pawns that get to the opposite rank automatically promote as queens.
2020-06-19 19:29:10 +02:00
if (dir > 0 && forward_r == POS_MAX) || (dir < 0 && forward_r == POS_MIN) {
m.promotion = Some(QUEEN)
}
2020-06-21 00:33:05 +02:00
if pseudo_legal || !is_illegal(board, game_state, &m) {
2020-06-06 00:47:07 +02:00
moves.push(m);
}
2020-05-31 02:41:02 +02:00
}
// Check diagonals for pieces to attack.
if i == 1 {
2020-06-06 00:47:07 +02:00
// First diagonal.
if f - 1 >= POS_MIN {
2020-06-19 19:29:10 +02:00
let diag = sq(f - 1, forward_r);
if !board.is_empty(diag) {
let diag_color = board.get_color_on(diag);
2020-06-20 19:56:00 +02:00
if let Some(m) = get_capture_move(color, square, diag_color, diag, true) {
2020-06-21 00:33:05 +02:00
if pseudo_legal || !is_illegal(board, game_state, &m) {
2020-06-19 19:29:10 +02:00
moves.push(m);
}
2020-06-06 00:47:07 +02:00
}
2020-06-19 19:29:10 +02:00
2020-05-31 02:41:02 +02:00
}
}
2020-06-06 00:47:07 +02:00
// Second diagonal.
if f + 1 <= POS_MAX {
2020-06-19 19:29:10 +02:00
let diag = sq(f + 1, forward_r);
if !board.is_empty(diag) {
let diag_color = board.get_color_on(diag);
2020-06-20 19:56:00 +02:00
if let Some(m) = get_capture_move(color, square, diag_color, diag, true) {
2020-06-21 00:33:05 +02:00
if pseudo_legal || !is_illegal(board, game_state, &m) {
2020-06-19 19:29:10 +02:00
moves.push(m);
}
2020-06-06 00:47:07 +02:00
}
2020-05-31 02:41:02 +02:00
}
}
}
// TODO en passant
}
moves
}
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-19 19:29:10 +02:00
let (f, r) = (sq_file(square), sq_rank(square));
2020-06-06 00:47:07 +02:00
let mut views = [true; 4]; // Store diagonals where a piece blocks commit.
let mut moves = Vec::with_capacity(8);
2020-05-31 02:41:02 +02:00
for dist in 1..=7 {
for (dir, offset) in [(1, -1), (1, 1), (-1, 1), (-1, -1)].iter().enumerate() {
2020-06-06 00:47:07 +02:00
if !views[dir] {
2020-05-31 02:41:02 +02:00
continue
}
2020-06-19 19:29:10 +02:00
// If this position is out of the board, stop looking in that direction.
2020-06-19 19:29:10 +02:00
let ray_f = f + offset.0 * dist;
if ray_f < POS_MIN || ray_f > POS_MAX {
views[dir] = false;
2020-05-31 02:41:02 +02:00
continue
}
2020-06-19 19:29:10 +02:00
let ray_r = r + offset.1 * dist;
if ray_r < POS_MIN || ray_r > POS_MAX {
2020-06-19 19:29:10 +02:00
views[dir] = false;
continue
}
let ray_square = sq(ray_f, ray_r);
2020-06-21 00:33:05 +02:00
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
2020-06-20 19:56:00 +02:00
MoveType::Simple(m) => { moves.push(m) }
MoveType::Capture(m) => { moves.push(m); views[dir] = false; }
MoveType::CantTakeFriend => { views[dir] = false; }
_ => {}
2020-05-31 02:41:02 +02:00
}
}
}
moves
}
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-19 19:29:10 +02:00
let (f, r) = (sq_file(square), sq_rank(square));
let mut moves = Vec::with_capacity(8);
2020-05-31 02:41:02 +02:00
for offset in [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)].iter() {
2020-06-19 19:29:10 +02:00
let ray_f = f + offset.0;
if ray_f < POS_MIN || ray_f > POS_MAX {
2020-06-19 19:29:10 +02:00
continue
}
let ray_r = r + offset.1;
if ray_r < POS_MIN || ray_r > POS_MAX {
2020-05-31 02:41:02 +02:00
continue
}
2020-06-19 19:29:10 +02:00
let ray_square = sq(ray_f, ray_r);
2020-06-21 00:33:05 +02:00
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
2020-06-20 19:56:00 +02:00
MoveType::Simple(m) | MoveType::Capture(m) => moves.push(m),
_ => {}
2020-05-31 02:41:02 +02:00
}
}
moves
}
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-19 19:29:10 +02:00
let (f, r) = (sq_file(square), sq_rank(square));
let mut moves = Vec::with_capacity(8);
2020-06-06 00:47:07 +02:00
let mut views = [true; 4]; // Store lines where a piece blocks commit.
2020-05-31 02:41:02 +02:00
for dist in 1..=7 {
for (dir, offset) in [(0, 1), (1, 0), (0, -1), (-1, 0)].iter().enumerate() {
2020-06-06 00:47:07 +02:00
if !views[dir] {
2020-05-31 02:41:02 +02:00
continue
}
2020-06-19 19:29:10 +02:00
let ray_f = f + offset.0 * dist;
if ray_f < POS_MIN || ray_f > POS_MAX {
2020-06-19 19:29:10 +02:00
views[dir] = false;
continue
}
let ray_r = r + offset.1 * dist;
if ray_r < POS_MIN || ray_r > POS_MAX {
views[dir] = false;
2020-05-31 02:41:02 +02:00
continue
}
2020-06-19 19:29:10 +02:00
let ray_square = sq(ray_f, ray_r);
2020-06-21 00:33:05 +02:00
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
2020-06-20 19:56:00 +02:00
MoveType::Simple(m) => { moves.push(m) }
MoveType::Capture(m) => { moves.push(m); views[dir] = false; }
MoveType::CantTakeFriend => { views[dir] = false; }
_ => {}
2020-05-31 02:41:02 +02:00
}
}
}
moves
}
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-19 19:29:10 +02:00
let mut moves = Vec::with_capacity(16);
2020-05-31 02:41:02 +02:00
// Easy way to get queen moves, but may be a bit quicker if everything was rewritten here.
2020-06-21 00:33:05 +02:00
moves.append(&mut get_bishop_moves(board, game_state, square, color, pseudo_legal));
moves.append(&mut get_rook_moves(board, game_state, square, color, pseudo_legal));
2020-05-31 02:41:02 +02:00
moves
}
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-19 19:29:10 +02:00
let (f, r) = (sq_file(square), sq_rank(square));
let mut moves = Vec::with_capacity(8);
2020-05-31 02:41:02 +02:00
for offset in [(-1, 1), (0, 1), (1, 1), (-1, 0), (1, 0), (-1, -1), (0, -1), (1, -1)].iter() {
2020-06-19 19:29:10 +02:00
let ray_f = f + offset.0;
if ray_f < POS_MIN || ray_f > POS_MAX {
2020-06-19 19:29:10 +02:00
continue
}
let ray_r = r + offset.1;
if ray_r < POS_MIN || ray_r > POS_MAX {
2020-05-31 02:41:02 +02:00
continue
}
2020-06-19 19:29:10 +02:00
let ray_square = sq(ray_f, ray_r);
2020-06-21 00:33:05 +02:00
match get_move_type(board, game_state, square, ray_square, color, pseudo_legal) {
2020-06-20 19:56:00 +02:00
MoveType::Simple(m) | MoveType::Capture(m) => moves.push(m),
_ => {}
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-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-20 19:56:00 +02:00
/// Accept or ignore a move from `square` to `ray_square`.
fn get_move_type(
board: &Board,
game_state: &GameState,
square: Square,
ray_square: Square,
color: Color,
commit: bool
) -> MoveType {
if board.is_empty(ray_square) {
let m = Move::new(square, ray_square);
2020-06-21 00:33:05 +02:00
if is_legal(commit, board, game_state, &m) {
2020-06-20 19:56:00 +02:00
MoveType::Simple(m)
} else {
MoveType::CantRegister
}
} else {
let ray_color = board.get_color_on(ray_square);
if let Some(m) = get_capture_move(color, square, ray_color, ray_square, false) {
2020-06-21 00:33:05 +02:00
if is_legal(commit, board, game_state, &m) {
2020-06-20 19:56:00 +02:00
MoveType::Capture(m)
} else {
MoveType::CantRegister
}
} else {
MoveType::CantTakeFriend
}
}
}
enum MoveType {
/// Move to an empty square.
Simple(Move),
/// Capture an enemy piece.
Capture(Move),
/// May be illegal, or should not be committed, e.g. to test rays.
CantRegister,
/// Can't capture a friend piece.
CantTakeFriend,
}
2020-06-21 00:33:05 +02:00
/// Return true if `pseudo_legal` is true, or the move is not illegal,
2020-06-06 00:47:07 +02:00
///
/// Committing a move means that it can be safely played afterwards.
/// Sometimes it is not what is needed to accept a move in a collection
/// of moves, e.g. when simply checking if some moves would make a
/// previous move illegal.
#[inline]
2020-06-21 00:33:05 +02:00
fn is_legal(pseudo_legal: bool, board: &Board, game_state: &GameState, m: &Move) -> bool {
pseudo_legal || !is_illegal(board, game_state, m)
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-06 00:47:07 +02:00
/// Check all possible enemy moves and return true when one of them
/// ends up attacking the position.
///
/// 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-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.
// 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)));
// 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);
// 3. remove the e4 black pawn; the white pawn should not be able to jump above e3 pawn.
2020-06-19 19:29:10 +02:00
b.clear_square(E4);
2020-06-21 00:33:05 +02:00
let moves = get_piece_moves(&b, &gs, E2, WHITE, false);
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
}
}