rules: use new structures
This commit is contained in:
parent
91e1fbbe21
commit
4cea0e34e9
15
src/board.rs
15
src/board.rs
|
@ -103,6 +103,18 @@ pub const H6: Square = 61;
|
||||||
pub const H7: Square = 62;
|
pub const H7: Square = 62;
|
||||||
pub const H8: Square = 63;
|
pub const H8: Square = 63;
|
||||||
|
|
||||||
|
/// Get square from file and rank, both starting from 0.
|
||||||
|
#[inline]
|
||||||
|
pub const fn sq(file: i8, rank: i8) -> Square { file * 8 + rank }
|
||||||
|
|
||||||
|
/// Get file from square.
|
||||||
|
#[inline]
|
||||||
|
pub const fn sq_file(square: Square) -> i8 { square / 8 }
|
||||||
|
|
||||||
|
/// Get rank from square.
|
||||||
|
#[inline]
|
||||||
|
pub const fn sq_rank(square: Square) -> i8 { square % 8 }
|
||||||
|
|
||||||
/// Get bit mask of `p` in a bitboard.
|
/// Get bit mask of `p` in a bitboard.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn bit_pos(square: Square) -> u64 { 1 << square }
|
pub const fn bit_pos(square: Square) -> u64 { 1 << square }
|
||||||
|
@ -299,7 +311,6 @@ impl Board {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::notation;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_opposite() {
|
fn test_opposite() {
|
||||||
|
@ -327,7 +338,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_new_from_fen() {
|
fn test_new_from_fen() {
|
||||||
let b1 = Board::new();
|
let b1 = Board::new();
|
||||||
let b2 = Board::new_from_fen(notation::FEN_START);
|
let b2 = Board::new_from_fen(crate::fen::FEN_START);
|
||||||
assert!(b1 == b2);
|
assert!(b1 == b2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::board;
|
use crate::board;
|
||||||
use crate::movement::{self, Move};
|
use crate::movement::Move;
|
||||||
use crate::rules;
|
use crate::rules;
|
||||||
use crate::stats;
|
use crate::stats;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ impl Node {
|
||||||
|
|
||||||
/// Apply a move to this node.
|
/// Apply a move to this node.
|
||||||
pub fn apply_move(&mut self, m: &Move) {
|
pub fn apply_move(&mut self, m: &Move) {
|
||||||
movement::apply_move_to(&mut self.board, &mut self.game_state, m);
|
m.apply_to(&mut self.board, &mut self.game_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return player moves from this node.
|
/// Return player moves from this node.
|
||||||
|
|
449
src/rules.rs
449
src/rules.rs
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
use crate::board::*;
|
use crate::board::*;
|
||||||
use crate::castling::*;
|
use crate::castling::*;
|
||||||
use crate::movement::{self, Move};
|
use crate::fen;
|
||||||
use crate::notation;
|
use crate::movement::Move;
|
||||||
|
|
||||||
|
const POS_MIN: i8 = 0;
|
||||||
|
const POS_MAX: i8 = 7;
|
||||||
|
|
||||||
/// Characteristics of the state of a game.
|
/// Characteristics of the state of a game.
|
||||||
///
|
///
|
||||||
|
@ -46,7 +49,7 @@ impl std::fmt::Display for GameState {
|
||||||
- halfmove: {}\n\
|
- halfmove: {}\n\
|
||||||
- fullmove: {}",
|
- fullmove: {}",
|
||||||
color_to_string(self.color), self.castling,
|
color_to_string(self.color), self.castling,
|
||||||
notation::en_passant_to_string(self.en_passant),
|
fen::en_passant_to_string(self.en_passant),
|
||||||
self.halfmove, self.fullmove
|
self.halfmove, self.fullmove
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -67,49 +70,57 @@ pub fn get_player_moves(
|
||||||
let mut moves = Vec::with_capacity(256);
|
let mut moves = Vec::with_capacity(256);
|
||||||
for r in 0..8 {
|
for r in 0..8 {
|
||||||
for f in 0..8 {
|
for f in 0..8 {
|
||||||
let p = (f, r);
|
let square = sq(f, r);
|
||||||
if is_empty(board, &p) {
|
if board.is_empty(square) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_color(get_square(board, &p), game_state.color) {
|
if board.get_color(square) == game_state.color {
|
||||||
moves.append(&mut get_piece_moves(board, &p, game_state, commit));
|
moves.append(
|
||||||
|
&mut get_piece_moves(board, game_state, square, game_state.color, commit)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moves
|
moves
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of moves for the piece at position `at`.
|
/// 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.
|
||||||
pub fn get_piece_moves(
|
pub fn get_piece_moves(
|
||||||
board: &Board,
|
board: &Board,
|
||||||
at: &Pos,
|
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
|
square: Square,
|
||||||
|
color: Color,
|
||||||
commit: bool,
|
commit: bool,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
match get_square(board, at) {
|
match board.get_piece(square) {
|
||||||
p if is_piece(p, SQ_P) => get_pawn_moves(board, at, p, game_state, commit),
|
PAWN => get_pawn_moves(board, game_state, square, color, commit),
|
||||||
p if is_piece(p, SQ_B) => get_bishop_moves(board, at, p, game_state, commit),
|
BISHOP => get_bishop_moves(board, game_state, square, color, commit),
|
||||||
p if is_piece(p, SQ_N) => get_knight_moves(board, at, p, game_state, commit),
|
KNIGHT => get_knight_moves(board, game_state, square, color, commit),
|
||||||
p if is_piece(p, SQ_R) => get_rook_moves(board, at, p, game_state, commit),
|
ROOK => get_rook_moves(board, game_state, square, color, commit),
|
||||||
p if is_piece(p, SQ_Q) => get_queen_moves(board, at, p, game_state, commit),
|
QUEEN => get_queen_moves(board, game_state, square, color, commit),
|
||||||
p if is_piece(p, SQ_K) => get_king_moves(board, at, p, game_state, commit),
|
KING => get_king_moves(board, game_state, square, color, commit),
|
||||||
_ => vec!(),
|
_ => vec!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pawn_moves(
|
fn get_pawn_moves(
|
||||||
board: &Board,
|
board: &Board,
|
||||||
at: &Pos,
|
|
||||||
piece: u8,
|
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
|
square: Square,
|
||||||
|
color: Color,
|
||||||
commit: bool,
|
commit: bool,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let (f, r) = *at;
|
let (f, r) = (sq_file(square), sq_rank(square));
|
||||||
let mut moves = vec!();
|
let mut moves = vec!();
|
||||||
// Direction: positive for white, negative for black.
|
// Direction: positive for white, negative for black.
|
||||||
let dir: i8 = if is_white(piece) { 1 } else { -1 };
|
let dir = if color == WHITE { 1 } else { -1 };
|
||||||
// Check 1 or 2 square forward.
|
// Check 1 or 2 square forward.
|
||||||
let move_len = if (is_white(piece) && r == 1) || (is_black(piece) && r == 6) { 2 } else { 1 };
|
let move_len = if (color == WHITE && r == 1) || (color == BLACK && r == 6) { 2 } else { 1 };
|
||||||
for i in 1..=move_len {
|
for i in 1..=move_len {
|
||||||
let forward_r = r + dir * i;
|
let forward_r = r + dir * i;
|
||||||
if dir > 0 && forward_r > POS_MAX {
|
if dir > 0 && forward_r > POS_MAX {
|
||||||
|
@ -118,16 +129,14 @@ fn get_pawn_moves(
|
||||||
if dir < 0 && forward_r < POS_MIN {
|
if dir < 0 && forward_r < POS_MIN {
|
||||||
return moves
|
return moves
|
||||||
}
|
}
|
||||||
let forward: Pos = (f, forward_r);
|
let forward: Square = sq(f, forward_r);
|
||||||
// If forward square is empty (and we are not jumping over an occupied square), add it.
|
// If forward square is empty (and we are not jumping over an occupied square), add it.
|
||||||
if is_empty(board, &forward) && (i == 1 || is_empty(board, &(f, forward_r - dir))) {
|
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.
|
// Pawns that get to the opposite rank automatically promote as queens.
|
||||||
let prom = if (dir > 0 && forward_r == POS_MAX) || (dir < 0 && forward_r == POS_MIN) {
|
if (dir > 0 && forward_r == POS_MAX) || (dir < 0 && forward_r == POS_MIN) {
|
||||||
Some(SQ_Q)
|
m.promotion = Some(QUEEN)
|
||||||
} else {
|
}
|
||||||
None
|
|
||||||
};
|
|
||||||
let m = (*at, forward, prom);
|
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
|
@ -136,23 +145,30 @@ fn get_pawn_moves(
|
||||||
if i == 1 {
|
if i == 1 {
|
||||||
// First diagonal.
|
// First diagonal.
|
||||||
if f - 1 >= POS_MIN {
|
if f - 1 >= POS_MIN {
|
||||||
let diag: Pos = (f - 1, forward_r);
|
let diag = sq(f - 1, forward_r);
|
||||||
if let Some(m) = move_on_enemy(piece, at, get_square(board, &diag), &diag) {
|
if !board.is_empty(diag) {
|
||||||
|
let diag_color = board.get_color(diag);
|
||||||
|
if let Some(m) = move_if_enemy(color, square, diag_color, diag, true) {
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Second diagonal.
|
// Second diagonal.
|
||||||
if f + 1 <= POS_MAX {
|
if f + 1 <= POS_MAX {
|
||||||
let diag: Pos = (f + 1, forward_r);
|
let diag = sq(f + 1, forward_r);
|
||||||
if let Some(m) = move_on_enemy(piece, at, get_square(board, &diag), &diag) {
|
if !board.is_empty(diag) {
|
||||||
|
let diag_color = board.get_color(diag);
|
||||||
|
if let Some(m) = move_if_enemy(color, square, diag_color, diag, true) {
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// TODO en passant
|
// TODO en passant
|
||||||
}
|
}
|
||||||
moves
|
moves
|
||||||
|
@ -160,12 +176,12 @@ fn get_pawn_moves(
|
||||||
|
|
||||||
fn get_bishop_moves(
|
fn get_bishop_moves(
|
||||||
board: &Board,
|
board: &Board,
|
||||||
at: &Pos,
|
|
||||||
piece: u8,
|
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
|
square: Square,
|
||||||
|
color: Color,
|
||||||
commit: bool,
|
commit: bool,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let (f, r) = at;
|
let (f, r) = (sq_file(square), sq_rank(square));
|
||||||
let mut views = [true; 4]; // Store diagonals where a piece blocks commit.
|
let mut views = [true; 4]; // Store diagonals where a piece blocks commit.
|
||||||
let mut moves = Vec::with_capacity(8);
|
let mut moves = Vec::with_capacity(8);
|
||||||
for dist in 1..=7 {
|
for dist in 1..=7 {
|
||||||
|
@ -173,24 +189,33 @@ fn get_bishop_moves(
|
||||||
if !views[dir] {
|
if !views[dir] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let p = (f + offset.0 * dist, r + offset.1 * dist);
|
|
||||||
// If this position is out of the board, stop looking in that direction.
|
// If this position is out of the board, stop looking in that direction.
|
||||||
if !is_valid_pos(p) {
|
let ray_f = f + offset.0 * dist;
|
||||||
|
if ray_f <= POS_MIN || ray_f >= POS_MAX {
|
||||||
views[dir] = false;
|
views[dir] = false;
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_empty(board, &p) {
|
let ray_r = r + offset.1 * dist;
|
||||||
let m = (*at, p, None);
|
if ray_r <= POS_MIN || ray_r >= POS_MAX {
|
||||||
|
views[dir] = false;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let ray_square = sq(ray_f, ray_r);
|
||||||
|
|
||||||
|
if board.is_empty(ray_square) {
|
||||||
|
let m = Move::new(square, ray_square);
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
|
let ray_color = board.get_color(ray_square);
|
||||||
|
if let Some(m) = move_if_enemy(color, square, ray_color, ray_square, false) {
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
views[dir] = false; // Stop looking in that direction.
|
views[dir] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,40 +224,49 @@ fn get_bishop_moves(
|
||||||
|
|
||||||
fn get_knight_moves(
|
fn get_knight_moves(
|
||||||
board: &Board,
|
board: &Board,
|
||||||
at: &Pos,
|
|
||||||
piece: u8,
|
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
|
square: Square,
|
||||||
|
color: Color,
|
||||||
commit: bool,
|
commit: bool,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let (f, r) = at;
|
let (f, r) = (sq_file(square), sq_rank(square));
|
||||||
let mut moves = Vec::with_capacity(8);
|
let mut moves = Vec::with_capacity(8);
|
||||||
for offset in [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)].iter() {
|
for offset in [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)].iter() {
|
||||||
let p = (f + offset.0, r + offset.1);
|
let ray_f = f + offset.0;
|
||||||
if !is_valid_pos(p) {
|
if ray_f <= POS_MIN || ray_f >= POS_MAX {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_empty(board, &p) {
|
let ray_r = r + offset.1;
|
||||||
let m = (*at, p, None);
|
if ray_r <= POS_MIN || ray_r >= POS_MAX {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let ray_square = sq(ray_f, ray_r);
|
||||||
|
|
||||||
|
if board.is_empty(ray_square) {
|
||||||
|
let m = Move::new(square, ray_square);
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
} else if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
|
} else {
|
||||||
|
let ray_color = board.get_color(ray_square);
|
||||||
|
if let Some(m) = move_if_enemy(color, square, ray_color, ray_square, false) {
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
moves
|
moves
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rook_moves(
|
fn get_rook_moves(
|
||||||
board: &Board,
|
board: &Board,
|
||||||
at: &Pos,
|
|
||||||
piece: u8,
|
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
|
square: Square,
|
||||||
|
color: Color,
|
||||||
commit: bool,
|
commit: bool,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let (f, r) = at;
|
let (f, r) = (sq_file(square), sq_rank(square));
|
||||||
let mut moves = Vec::with_capacity(8);
|
let mut moves = Vec::with_capacity(8);
|
||||||
let mut views = [true; 4]; // Store lines where a piece blocks commit.
|
let mut views = [true; 4]; // Store lines where a piece blocks commit.
|
||||||
for dist in 1..=7 {
|
for dist in 1..=7 {
|
||||||
|
@ -240,19 +274,27 @@ fn get_rook_moves(
|
||||||
if !views[dir] {
|
if !views[dir] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let p = (f + offset.0 * dist, r + offset.1 * dist);
|
|
||||||
// If this position is out of the board, stop looking in that direction.
|
let ray_f = f + offset.0 * dist;
|
||||||
if !is_valid_pos(p) {
|
if ray_f <= POS_MIN || ray_f >= POS_MAX {
|
||||||
views[dir] = false;
|
views[dir] = false;
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_empty(board, &p) {
|
let ray_r = r + offset.1 * dist;
|
||||||
let m = (*at, p, None);
|
if ray_r <= POS_MIN || ray_r >= POS_MAX {
|
||||||
|
views[dir] = false;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let ray_square = sq(ray_f, ray_r);
|
||||||
|
|
||||||
|
if board.is_empty(ray_square) {
|
||||||
|
let m = Move::new(square, ray_square);
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
|
let ray_color = board.get_color(ray_square);
|
||||||
|
if let Some(m) = move_if_enemy(color, square, ray_color, ray_square, false) {
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
|
@ -266,43 +308,52 @@ fn get_rook_moves(
|
||||||
|
|
||||||
fn get_queen_moves(
|
fn get_queen_moves(
|
||||||
board: &Board,
|
board: &Board,
|
||||||
at: &Pos,
|
|
||||||
piece: u8,
|
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
commit: bool
|
square: Square,
|
||||||
|
color: Color,
|
||||||
|
commit: bool,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let mut moves = vec!();
|
let mut moves = Vec::with_capacity(16);
|
||||||
// Easy way to get queen moves, but may be a bit quicker if everything was rewritten here.
|
// Easy way to get queen moves, but may be a bit quicker if everything was rewritten here.
|
||||||
moves.append(&mut get_bishop_moves(board, at, piece, game_state, commit));
|
moves.append(&mut get_bishop_moves(board, game_state, square, color, commit));
|
||||||
moves.append(&mut get_rook_moves(board, at, piece, game_state, commit));
|
moves.append(&mut get_rook_moves(board, game_state, square, color, commit));
|
||||||
moves
|
moves
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_king_moves(
|
fn get_king_moves(
|
||||||
board: &Board,
|
board: &Board,
|
||||||
at: &Pos,
|
|
||||||
piece: u8,
|
|
||||||
game_state: &GameState,
|
game_state: &GameState,
|
||||||
commit: bool
|
square: Square,
|
||||||
|
color: Color,
|
||||||
|
commit: bool,
|
||||||
) -> Vec<Move> {
|
) -> Vec<Move> {
|
||||||
let (f, r) = at;
|
let (f, r) = (sq_file(square), sq_rank(square));
|
||||||
let mut moves = vec!();
|
let mut moves = Vec::with_capacity(8);
|
||||||
for offset in [(-1, 1), (0, 1), (1, 1), (-1, 0), (1, 0), (-1, -1), (0, -1), (1, -1)].iter() {
|
for offset in [(-1, 1), (0, 1), (1, 1), (-1, 0), (1, 0), (-1, -1), (0, -1), (1, -1)].iter() {
|
||||||
let p = (f + offset.0, r + offset.1);
|
let ray_f = f + offset.0;
|
||||||
if !is_valid_pos(p) {
|
if ray_f <= POS_MIN || ray_f >= POS_MAX {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_empty(board, &p) {
|
let ray_r = r + offset.1;
|
||||||
let m = (*at, p, None);
|
if ray_r <= POS_MIN || ray_r >= POS_MAX {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let ray_square = sq(ray_f, ray_r);
|
||||||
|
|
||||||
|
if board.is_empty(ray_square) {
|
||||||
|
let m = Move::new(square, ray_square);
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
} else if let Some(m) = move_on_enemy(piece, at, get_square(board, &p), &p) {
|
} else {
|
||||||
|
let ray_color = board.get_color(ray_square);
|
||||||
|
if let Some(m) = move_if_enemy(color, square, ray_color, ray_square, false) {
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop here for uncommitted moves.
|
// Stop here for uncommitted moves.
|
||||||
if !commit {
|
if !commit {
|
||||||
|
@ -318,7 +369,7 @@ fn get_king_moves(
|
||||||
// 6. The king does not end up in check.
|
// 6. The king does not end up in check.
|
||||||
|
|
||||||
// First get the required castling rank and color mask for the player.
|
// First get the required castling rank and color mask for the player.
|
||||||
let (castling_rank, castling_color_mask) = if is_white(game_state.color) {
|
let (castling_rank, castling_color_mask) = if game_state.color == WHITE {
|
||||||
(0, CASTLING_WH_MASK)
|
(0, CASTLING_WH_MASK)
|
||||||
} else {
|
} else {
|
||||||
(7, CASTLING_BL_MASK)
|
(7, CASTLING_BL_MASK)
|
||||||
|
@ -327,8 +378,8 @@ fn get_king_moves(
|
||||||
// Check for castling if the king is on its castling rank (R1)
|
// Check for castling if the king is on its castling rank (R1)
|
||||||
// and is not in check (R4).
|
// and is not in check (R4).
|
||||||
if
|
if
|
||||||
*r == castling_rank &&
|
r == castling_rank &&
|
||||||
!is_attacked(board, game_state, at)
|
!is_attacked(board, game_state, square)
|
||||||
{
|
{
|
||||||
// Check for both castling sides.
|
// Check for both castling sides.
|
||||||
for (path_files, opt_empty_file, castling_side_mask) in CASTLING_SIDES.iter() {
|
for (path_files, opt_empty_file, castling_side_mask) in CASTLING_SIDES.iter() {
|
||||||
|
@ -337,8 +388,10 @@ fn get_king_moves(
|
||||||
// Check that squares in the king's path are empty and not attacked (R3.1, R5, R6).
|
// Check that squares in the king's path are empty and not attacked (R3.1, R5, R6).
|
||||||
let mut path_is_clear = true;
|
let mut path_is_clear = true;
|
||||||
for path_f in path_files {
|
for path_f in path_files {
|
||||||
let p = (*path_f, castling_rank);
|
let path_square = sq(*path_f, castling_rank);
|
||||||
if !is_empty(board, &p) || is_illegal(board, game_state, &(*at, p, None)) {
|
if
|
||||||
|
!board.is_empty(path_square)
|
||||||
|
|| is_illegal(board, game_state, &Move::new(square, path_square)) {
|
||||||
path_is_clear = false;
|
path_is_clear = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -348,13 +401,13 @@ fn get_king_moves(
|
||||||
}
|
}
|
||||||
// Check that rook jumps over an empty square on queen-side (R3.2).
|
// Check that rook jumps over an empty square on queen-side (R3.2).
|
||||||
if let Some(rook_path_f) = opt_empty_file {
|
if let Some(rook_path_f) = opt_empty_file {
|
||||||
let p = (*rook_path_f, castling_rank);
|
let rook_path_square = sq(*rook_path_f, castling_rank);
|
||||||
if !is_empty(board, &p) {
|
if !board.is_empty(rook_path_square) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let castle = castling_side_mask & castling_color_mask;
|
let castle = castling_side_mask & castling_color_mask;
|
||||||
let m = movement::get_castle_move(castle);
|
let m = Move::get_castle_move(castle);
|
||||||
if can_register(commit, board, game_state, &m) {
|
if can_register(commit, board, game_state, &m) {
|
||||||
moves.push(m);
|
moves.push(m);
|
||||||
}
|
}
|
||||||
|
@ -375,20 +428,25 @@ fn can_register(commit: bool, board: &Board, game_state: &GameState, m: &Move) -
|
||||||
!commit || !is_illegal(board, game_state, m)
|
!commit || !is_illegal(board, game_state, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a move from pos1 to pos2 if piece1 & piece2 are enemies.
|
/// Return a move from `square1` to `square2` if colors are opposite.
|
||||||
fn move_on_enemy(piece1: u8, pos1: &Pos, piece2: u8, pos2: &Pos) -> Option<Move> {
|
fn move_if_enemy(
|
||||||
let color1 = get_color(piece1);
|
color1: Color,
|
||||||
if is_color(piece2, opposite(color1)) {
|
square1: Square,
|
||||||
|
color2: Color,
|
||||||
|
square2: Square,
|
||||||
|
is_pawn: bool,
|
||||||
|
) -> Option<Move> {
|
||||||
|
if color2 == opposite(color1) {
|
||||||
// Automatic queen promotion for pawns moving to the opposite rank.
|
// Automatic queen promotion for pawns moving to the opposite rank.
|
||||||
let prom = if
|
Some(if
|
||||||
is_piece(piece1, SQ_P) &&
|
is_pawn
|
||||||
((is_white(piece1) && pos2.1 == POS_MAX) || (is_black(piece1) && pos2.1 == POS_MIN))
|
&& (color1 == WHITE && sq_rank(square2) == POS_MAX)
|
||||||
|
|| (color1 == BLACK && sq_rank(square2) == POS_MIN)
|
||||||
{
|
{
|
||||||
Some(SQ_Q)
|
Move::new_promotion(square1, square2, QUEEN)
|
||||||
} else {
|
} else {
|
||||||
None
|
Move::new(square1, square2)
|
||||||
};
|
})
|
||||||
Some((*pos1, *pos2, prom))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -396,36 +454,38 @@ fn move_on_enemy(piece1: u8, pos1: &Pos, piece2: u8, pos2: &Pos) -> Option<Move>
|
||||||
|
|
||||||
/// Check if a move is illegal.
|
/// Check if a move is illegal.
|
||||||
fn is_illegal(board: &Board, game_state: &GameState, m: &Move) -> bool {
|
fn is_illegal(board: &Board, game_state: &GameState, m: &Move) -> bool {
|
||||||
if let Some(king_p) = find_king(board, game_state.color) {
|
if let Some(mut king_square) = board.find_king(game_state.color) {
|
||||||
// Rule 1: a move is illegal if the king ends up in check.
|
// Rule 1: a move is illegal if the king ends up in check.
|
||||||
// If king moves, use its new position.
|
// If king moves, use its new position.
|
||||||
let king_p = if m.0 == king_p { m.1 } else { king_p };
|
if m.source == king_square {
|
||||||
|
king_square = m.dest
|
||||||
|
}
|
||||||
let mut hypothetic_board = board.clone();
|
let mut hypothetic_board = board.clone();
|
||||||
movement::apply_move_to_board(&mut hypothetic_board, m);
|
m.apply_to_board(&mut hypothetic_board);
|
||||||
// Check if the move makes the player king in check.
|
// Check if the move makes the player king in check.
|
||||||
if is_attacked(&hypothetic_board, &game_state, &king_p) {
|
if is_attacked(&hypothetic_board, &game_state, king_square) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the piece at position `at` is attacked.
|
/// Return true if the piece on `square` is attacked.
|
||||||
///
|
///
|
||||||
/// Check all possible enemy moves and return true when one of them
|
/// Check all possible enemy moves and return true when one of them
|
||||||
/// ends up attacking the position.
|
/// ends up attacking the position.
|
||||||
///
|
///
|
||||||
/// Beware that the game state must be coherent with the analysed
|
/// Beware that the game state must be coherent with the analysed
|
||||||
/// square, i.e. if the piece at `at` is white, the game state should
|
/// square, i.e. if the piece on `square` is white, the game state
|
||||||
/// tell that it is white turn. If the square at `at` is empty, simply
|
/// should tell that it is white turn. If `square` is empty, simply
|
||||||
/// check if it is getting attacked by the opposite player.
|
/// check if it is getting attacked by the opposite player.
|
||||||
fn is_attacked(board: &Board, game_state: &GameState, at: &Pos) -> bool {
|
fn is_attacked(board: &Board, game_state: &GameState, square: Square) -> bool {
|
||||||
let mut enemy_game_state = game_state.clone();
|
let mut enemy_game_state = game_state.clone();
|
||||||
enemy_game_state.color = opposite(game_state.color);
|
enemy_game_state.color = opposite(game_state.color);
|
||||||
// Do not attempt to commit moves, just check for attacked squares.
|
// Do not attempt to commit moves, just check for attacked squares.
|
||||||
let enemy_moves = get_player_moves(board, &enemy_game_state, false);
|
let enemy_moves = get_player_moves(board, &enemy_game_state, false);
|
||||||
for m in enemy_moves.iter() {
|
for m in enemy_moves.iter() {
|
||||||
if *at == m.1 {
|
if square == m.dest {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,11 +495,10 @@ fn is_attacked(board: &Board, game_state: &GameState, at: &Pos) -> bool {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::notation::parse_move;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_player_moves() {
|
fn test_get_player_moves() {
|
||||||
let b = new();
|
let b = Board::new();
|
||||||
let gs = GameState::new();
|
let gs = GameState::new();
|
||||||
|
|
||||||
// At first move, white has 16 pawn moves and 4 knight moves.
|
// At first move, white has 16 pawn moves and 4 knight moves.
|
||||||
|
@ -449,132 +508,132 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_pawn_moves() {
|
fn test_get_pawn_moves() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let 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.
|
||||||
set_square(&mut b, &pos("d3"), SQ_WH_P);
|
b.set_square(D3, WHITE, PAWN);
|
||||||
let moves = get_piece_moves(&b, &pos("d3"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, D3, WHITE, true);
|
||||||
assert!(moves.len() == 1 && moves.contains( &parse_move("d3d4") ));
|
assert!(moves.len() == 1 && moves.contains(&Move::new(D3, D4)));
|
||||||
|
|
||||||
// Check that a pawn (here white king's pawn) can move 2 square forward on first move.
|
// Check that a pawn (here white king's pawn) can move 2 square forward on first move.
|
||||||
set_square(&mut b, &pos("e2"), SQ_WH_P);
|
b.set_square(E2, WHITE, PAWN);
|
||||||
let moves = get_piece_moves(&b, &pos("e2"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
|
||||||
assert_eq!(moves.len(), 2);
|
assert_eq!(moves.len(), 2);
|
||||||
assert!(moves.contains( &parse_move("e2e3") ));
|
assert!(moves.contains(&Move::new(E2, E3)));
|
||||||
assert!(moves.contains( &parse_move("e2e4") ));
|
assert!(moves.contains(&Move::new(E2, 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.
|
||||||
set_square(&mut b, &pos("e4"), SQ_BL_P);
|
b.set_square(E4, BLACK, PAWN);
|
||||||
let moves = get_piece_moves(&b, &pos("e2"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
|
||||||
assert!(moves.len() == 1 && moves.contains( &parse_move("e2e3") ));
|
assert!(moves.len() == 1 && moves.contains(&Move::new(E2, E3)));
|
||||||
// 2. black pawn 1 square forward; no square available.
|
// 2. black pawn 1 square forward; no square available.
|
||||||
set_square(&mut b, &pos("e3"), SQ_BL_P);
|
b.set_square(E3, BLACK, PAWN);
|
||||||
let moves = get_piece_moves(&b, &pos("e2"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
|
||||||
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.
|
||||||
clear_square(&mut b, &pos("e4"));
|
b.clear_square(E4);
|
||||||
let moves = get_piece_moves(&b, &pos("e2"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
|
||||||
assert_eq!(moves.len(), 0);
|
assert_eq!(moves.len(), 0);
|
||||||
|
|
||||||
// Check that a pawn can take a piece diagonally.
|
// Check that a pawn can take a piece diagonally.
|
||||||
set_square(&mut b, &pos("f3"), SQ_BL_P);
|
b.set_square(F3, BLACK, PAWN);
|
||||||
let moves = get_piece_moves(&b, &pos("e2"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
|
||||||
assert!(moves.len() == 1 && moves.contains( &parse_move("e2f3") ));
|
assert!(moves.len() == 1 && moves.contains(&Move::new(E2, F3)));
|
||||||
set_square(&mut b, &pos("d3"), SQ_BL_P);
|
b.set_square(D3, BLACK, PAWN);
|
||||||
let moves = get_piece_moves(&b, &pos("e2"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, E2, WHITE, true);
|
||||||
assert_eq!(moves.len(), 2);
|
assert_eq!(moves.len(), 2);
|
||||||
assert!(moves.contains( &parse_move("e2f3") ));
|
assert!(moves.contains( &Move::new(E2, F3) ));
|
||||||
assert!(moves.contains( &parse_move("e2d3") ));
|
assert!(moves.contains( &Move::new(E2, D3) ));
|
||||||
|
|
||||||
// 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.
|
||||||
set_square(&mut b, &pos("a7"), SQ_WH_P);
|
b.set_square(A7, WHITE, PAWN);
|
||||||
let moves = get_piece_moves(&b, &pos("a7"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, A7, WHITE, true);
|
||||||
assert!(moves.len() == 1 && moves.contains( &parse_move("a7a8q") ));
|
assert!(moves.len() == 1 && moves.contains(&Move::new_promotion(A7, A8, QUEEN)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_bishop_moves() {
|
fn test_get_bishop_moves() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let 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.
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_B);
|
b.set_square(D4, WHITE, BISHOP);
|
||||||
let moves = get_piece_moves(&b, &pos("d4"), &gs, true);
|
let moves = get_piece_moves(&b, &gs, D4, WHITE, true);
|
||||||
assert_eq!(moves.len(), 13);
|
assert_eq!(moves.len(), 13);
|
||||||
// Going top-right.
|
// Going top-right.
|
||||||
assert!(moves.contains( &parse_move("d4e5") ));
|
assert!(moves.contains(&Move::new(D4, E5)));
|
||||||
assert!(moves.contains( &parse_move("d4f6") ));
|
assert!(moves.contains(&Move::new(D4, F6)));
|
||||||
assert!(moves.contains( &parse_move("d4g7") ));
|
assert!(moves.contains(&Move::new(D4, G7)));
|
||||||
assert!(moves.contains( &parse_move("d4h8") ));
|
assert!(moves.contains(&Move::new(D4, H8)));
|
||||||
// Going bottom-right.
|
// Going bottom-right.
|
||||||
assert!(moves.contains( &parse_move("d4e3") ));
|
assert!(moves.contains(&Move::new(D4, E3)));
|
||||||
assert!(moves.contains( &parse_move("d4f2") ));
|
assert!(moves.contains(&Move::new(D4, F2)));
|
||||||
assert!(moves.contains( &parse_move("d4g1") ));
|
assert!(moves.contains(&Move::new(D4, G1)));
|
||||||
// Going bottom-left.
|
// Going bottom-left.
|
||||||
assert!(moves.contains( &parse_move("d4c3") ));
|
assert!(moves.contains(&Move::new(D4, C3)));
|
||||||
assert!(moves.contains( &parse_move("d4b2") ));
|
assert!(moves.contains(&Move::new(D4, B2)));
|
||||||
assert!(moves.contains( &parse_move("d4a1") ));
|
assert!(moves.contains(&Move::new(D4, A1)));
|
||||||
// Going top-left.
|
// Going top-left.
|
||||||
assert!(moves.contains( &parse_move("d4c5") ));
|
assert!(moves.contains(&Move::new(D4, C5)));
|
||||||
assert!(moves.contains( &parse_move("d4b6") ));
|
assert!(moves.contains(&Move::new(D4, B6)));
|
||||||
assert!(moves.contains( &parse_move("d4a7") ));
|
assert!(moves.contains(&Move::new(D4, 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.
|
||||||
set_square(&mut b, &pos("b2"), SQ_WH_P);
|
b.set_square(B2, WHITE, PAWN);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 11);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).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.
|
||||||
set_square(&mut b, &pos("b2"), SQ_BL_P);
|
b.set_square(B2, BLACK, PAWN);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 12);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_knight_moves() {
|
fn test_get_knight_moves() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let 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.
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_N);
|
b.set_square(D4, WHITE, KNIGHT);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 8);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 8);
|
||||||
|
|
||||||
// If on a side if has only 4 moves.
|
// If on a side if has only 4 moves.
|
||||||
set_square(&mut b, &pos("a4"), SQ_WH_N);
|
b.set_square(A4, WHITE, KNIGHT);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("a4"), &gs, true).len(), 4);
|
assert_eq!(get_piece_moves(&b, &gs, A4, WHITE, true).len(), 4);
|
||||||
|
|
||||||
// And in a corner, only 2 moves.
|
// And in a corner, only 2 moves.
|
||||||
set_square(&mut b, &pos("a1"), SQ_WH_N);
|
b.set_square(A1, WHITE, KNIGHT);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("a1"), &gs, true).len(), 2);
|
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, true).len(), 2);
|
||||||
|
|
||||||
// Add 2 friendly pieces and it is totally blocked.
|
// Add 2 friendly pieces and it is totally blocked.
|
||||||
set_square(&mut b, &pos("b3"), SQ_WH_P);
|
b.set_square(B3, WHITE, PAWN);
|
||||||
set_square(&mut b, &pos("c2"), SQ_WH_P);
|
b.set_square(C2, WHITE, PAWN);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("a1"), &gs, true).len(), 0);
|
assert_eq!(get_piece_moves(&b, &gs, A1, WHITE, true).len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_rook_moves() {
|
fn test_get_rook_moves() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let gs = GameState::new();
|
||||||
|
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_R);
|
b.set_square(D4, WHITE, ROOK);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 14);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 14);
|
||||||
set_square(&mut b, &pos("d6"), SQ_BL_P);
|
b.set_square(D6, BLACK, PAWN);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 12);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 12);
|
||||||
set_square(&mut b, &pos("d6"), SQ_WH_P);
|
b.set_square(D6, WHITE, PAWN);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 11);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_queen_moves() {
|
fn test_get_queen_moves() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let gs = GameState::new();
|
||||||
|
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_Q);
|
b.set_square(D4, WHITE, QUEEN);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 14 + 13);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 14 + 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -582,54 +641,54 @@ mod tests {
|
||||||
let mut gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// King can move 1 square in any direction.
|
// King can move 1 square in any direction.
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_K);
|
b.set_square(D4, WHITE, KING);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 8);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).len(), 8);
|
||||||
set_square(&mut b, &pos("e5"), SQ_WH_P);
|
b.set_square(E5, WHITE, PAWN);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("d4"), &gs, true).len(), 7);
|
assert_eq!(get_piece_moves(&b, &gs, D4, WHITE, true).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 = new_empty();
|
let mut b = Board::new_empty();
|
||||||
set_square(&mut b, &pos("e1"), SQ_WH_K);
|
b.set_square(E1, WHITE, KING);
|
||||||
set_square(&mut b, &pos("a1"), SQ_WH_R);
|
b.set_square(A1, WHITE, ROOK);
|
||||||
set_square(&mut b, &pos("h1"), SQ_WH_R);
|
b.set_square(H1, WHITE, ROOK);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("e1"), &gs, true).len(), 5 + 2);
|
assert_eq!(get_piece_moves(&b, &gs, E1, WHITE, true).len(), 5 + 2);
|
||||||
|
|
||||||
// Castling works as well for black.
|
// Castling works as well for black.
|
||||||
gs.color = SQ_BL;
|
gs.color = BLACK;
|
||||||
set_square(&mut b, &pos("e8"), SQ_BL_K);
|
b.set_square(E8, BLACK, KING);
|
||||||
set_square(&mut b, &pos("a8"), SQ_BL_R);
|
b.set_square(A8, BLACK, ROOK);
|
||||||
set_square(&mut b, &pos("h8"), SQ_BL_R);
|
b.set_square(H8, BLACK, ROOK);
|
||||||
assert_eq!(get_piece_moves(&b, &pos("e8"), &gs, true).len(), 5 + 2);
|
assert_eq!(get_piece_moves(&b, &gs, E8, BLACK, true).len(), 5 + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_filter_illegal_moves() {
|
fn test_filter_illegal_moves() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
let mut gs = GameState::new();
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// Place white's king on first rank.
|
// Place white's king on first rank.
|
||||||
set_square(&mut b, &pos("e1"), SQ_WH_K);
|
b.set_square(E1, WHITE, KING);
|
||||||
// Place black rook in second rank: king can only move left or right.
|
// Place black rook in second rank: king can only move left or right.
|
||||||
set_square(&mut b, &pos("h2"), SQ_BL_R);
|
b.set_square(H2, BLACK, ROOK);
|
||||||
// No castling available.
|
// No castling available.
|
||||||
gs.castling = 0;
|
gs.castling = 0;
|
||||||
// 5 moves in absolute but only 2 are legal.
|
// 5 moves in absolute but only 2 are legal.
|
||||||
let all_wh_moves = get_piece_moves(&b, &pos("e1"), &gs, true);
|
let all_wh_moves = get_piece_moves(&b, &gs, E1, WHITE, true);
|
||||||
assert_eq!(all_wh_moves.len(), 2);
|
assert_eq!(all_wh_moves.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_attacked() {
|
fn test_is_attacked() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
let gs = GameState::new();
|
let gs = GameState::new();
|
||||||
|
|
||||||
// Place a black rook in white pawn's file.
|
// Place a black rook in white pawn's file.
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_P);
|
b.set_square(D4, WHITE, PAWN);
|
||||||
set_square(&mut b, &pos("d6"), SQ_BL_R);
|
b.set_square(D6, BLACK, ROOK);
|
||||||
assert!(is_attacked(&b, &gs, &pos("d4")));
|
assert!(is_attacked(&b, &gs, D4));
|
||||||
// Move the rook on another file, no more attack.
|
// Move the rook on another file, no more attack.
|
||||||
movement::apply_move_to_board(&mut b, &parse_move("d6e6"));
|
Move::new(D6, E6).apply_to_board(&mut b);
|
||||||
assert!(!is_attacked(&b, &gs, &pos("d4")));
|
assert!(!is_attacked(&b, &gs, D4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
89
src/stats.rs
89
src/stats.rs
|
@ -1,7 +1,7 @@
|
||||||
//! Board statistics used for heuristics.
|
//! Board statistics used for heuristics.
|
||||||
|
|
||||||
use crate::board::*;
|
use crate::board::*;
|
||||||
use crate::rules;
|
use crate::rules::GameState;
|
||||||
|
|
||||||
/// Storage for board pieces stats.
|
/// Storage for board pieces stats.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -39,58 +39,16 @@ impl BoardStats {
|
||||||
self.num_isolated_pawns = 0;
|
self.num_isolated_pawns = 0;
|
||||||
self.mobility = 0;
|
self.mobility = 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for BoardStats {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}P {}B {}N {}R {}Q {}K {}dp {}bp {}ip {}m",
|
|
||||||
self.num_pawns, self.num_bishops, self.num_knights, self.num_rooks,
|
|
||||||
self.num_queens, self.num_kings,
|
|
||||||
self.num_doubled_pawns, self.num_backward_pawns, self.num_isolated_pawns,
|
|
||||||
self.mobility
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create two new BoardStats objects from the board, for both sides.
|
|
||||||
///
|
|
||||||
/// See `compute_stats_into` for details.
|
|
||||||
pub fn compute_stats(board: &Board, game_state: &rules::GameState) -> (BoardStats, BoardStats) {
|
|
||||||
let mut stats = (BoardStats::new(), BoardStats::new());
|
|
||||||
compute_stats_into(board, game_state, &mut stats);
|
|
||||||
stats
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute stats for both the current player and its opponent.
|
|
||||||
///
|
|
||||||
/// The playing color will have its stats filled in the first
|
|
||||||
/// BoardStats object, its opponent in the second.
|
|
||||||
pub fn compute_stats_into(
|
|
||||||
board: &Board,
|
|
||||||
game_state: &rules::GameState,
|
|
||||||
stats: &mut (BoardStats, BoardStats)
|
|
||||||
) {
|
|
||||||
let mut gs = game_state.clone();
|
|
||||||
compute_color_stats_into(board, &gs, &mut stats.0);
|
|
||||||
gs.color = opposite(gs.color);
|
|
||||||
compute_color_stats_into(board, &gs, &mut stats.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill `stats` from given `board` and `game_state`.
|
/// Fill `stats` from given `board` and `game_state`.
|
||||||
///
|
///
|
||||||
/// 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_color_stats_into(
|
pub fn compute(&mut self, board: &Board, game_state: &GameState) {
|
||||||
board: &Board,
|
self.reset();
|
||||||
game_state: &rules::GameState,
|
|
||||||
stats: &mut BoardStats,
|
|
||||||
) {
|
|
||||||
stats.reset();
|
|
||||||
let color = game_state.color;
|
let color = game_state.color;
|
||||||
// Compute mobility for all pieces.
|
// Compute mobility for all pieces.
|
||||||
stats.mobility = rules::get_player_moves(board, game_state, true).len() as i32;
|
self.mobility = rules::get_player_moves(board, game_state, true).len() as i32;
|
||||||
// Compute amount of each piece.
|
// Compute amount of each piece.
|
||||||
for (piece, p) in get_piece_iterator(board) {
|
for (piece, p) in get_piece_iterator(board) {
|
||||||
let (pos_f, pos_r) = p;
|
let (pos_f, pos_r) = p;
|
||||||
|
@ -173,6 +131,45 @@ pub fn compute_color_stats_into(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for BoardStats {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}P {}B {}N {}R {}Q {}K {}dp {}bp {}ip {}m",
|
||||||
|
self.num_pawns, self.num_bishops, self.num_knights, self.num_rooks,
|
||||||
|
self.num_queens, self.num_kings,
|
||||||
|
self.num_doubled_pawns, self.num_backward_pawns, self.num_isolated_pawns,
|
||||||
|
self.mobility
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create two new BoardStats objects from the board, for both sides.
|
||||||
|
///
|
||||||
|
/// See `compute_stats_into` for details.
|
||||||
|
pub fn compute_stats(board: &Board, game_state: &GameState) -> (BoardStats, BoardStats) {
|
||||||
|
let mut stats = (BoardStats::new(), BoardStats::new());
|
||||||
|
compute_stats_into(board, game_state, &mut stats);
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute stats for both the current player and its opponent.
|
||||||
|
///
|
||||||
|
/// The playing color will have its stats filled in the first
|
||||||
|
/// BoardStats object, its opponent in the second.
|
||||||
|
pub fn compute_stats_into(
|
||||||
|
board: &Board,
|
||||||
|
game_state: &rules::GameState,
|
||||||
|
stats: &mut (BoardStats, BoardStats)
|
||||||
|
) {
|
||||||
|
let mut gs = game_state.clone();
|
||||||
|
compute_color_stats_into(board, &gs, &mut stats.0);
|
||||||
|
gs.color = opposite(gs.color);
|
||||||
|
compute_color_stats_into(board, &gs, &mut stats.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
Reference in a new issue