Files
zeal/src/board/game.rs

435 lines
16 KiB
Rust

use crate::{
board::fen::from_fen,
movegen::r#move::{Move, MoveType},
search::{transposition_table::TranspositionTable, MAX_TT_SIZE},
};
use String as FenError;
use super::{
board::{Board, Color, PieceType},
history::{History, MoveParameters},
mailbox::Mailbox,
square::Square,
state::Castle,
zobrist::{zobrist_keys, ZobristHash},
};
impl PartialEq for Game {
fn eq(&self, other: &Self) -> bool {
self.board.pieces == other.board.pieces
&& self.board.color == other.board.color
&& self.board.state == other.board.state
}
}
#[derive(Debug, Clone, Eq)]
pub struct Game {
pub board: Board,
pub history: History,
pub mailbox: Mailbox,
pub hash: ZobristHash,
pub tt: TranspositionTable,
}
impl Game {
pub fn new() -> Self {
Self {
board: Board::startpos(),
history: History::new(),
mailbox: Mailbox::from_board(&Board::startpos()),
hash: zobrist_keys().calculate_hash(&Board::startpos()),
tt: TranspositionTable::new(MAX_TT_SIZE),
}
}
pub fn from_fen(fen: &str) -> Result<Self, FenError> {
from_fen(fen)
}
pub const fn current_player(&self) -> Color {
self.board.state.current_player()
}
pub const fn next_player(&self) -> Color {
self.board.state.next_player()
}
pub fn make_move(&mut self, mv: &Move) {
self.history
.push_move_parameters(MoveParameters::build(self, mv));
let board = &mut self.board;
let hash = &mut self.hash;
let mailbox = &mut self.mailbox;
let color = board.state.current_player();
let pawn_move = board.is_pawn_move(mv.src);
let mut en_passant_square = None;
let ep_capture = match color {
Color::White => mv.dst.saturating_sub(8),
Color::Black => mv.dst + 8,
};
let old_castling_ability = board.state.castling_ability;
let piece_at_src = mailbox
.piece_at(mv.src)
.unwrap_or_else(|| panic!("Expected piece at: {}", mv.src));
let piece_at_dst = mailbox.piece_at(mv.dst);
match &mv.move_type {
MoveType::Quiet => {
board.move_piece(mv.src, mv.dst, piece_at_src.0);
hash.update_quiet(mv.src, mv.dst, piece_at_src.0, color);
hash.drop_en_passant_hash(board.state.en_passant_square());
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
}
MoveType::Capture => {
let piece_at_dst = piece_at_dst.expect("Expected piece at: {mv.dst}");
board.remove_opponent_piece(mv.dst, piece_at_dst.0);
hash.update_capture(mv.src, mv.dst, piece_at_src.0, piece_at_dst.0, color);
board.move_piece(mv.src, mv.dst, piece_at_src.0);
hash.drop_en_passant_hash(board.state.en_passant_square());
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
}
MoveType::EnPassant => {
board.move_piece(mv.src, mv.dst, PieceType::Pawn);
board.remove_opponent_piece(ep_capture, PieceType::Pawn);
hash.update_en_passant(mv.src, mv.dst, ep_capture, color);
hash.drop_en_passant_hash(board.state.en_passant_square());
mailbox.set_piece_at(mv.dst, Some((PieceType::Pawn, color)));
mailbox.set_piece_at(ep_capture, None);
}
MoveType::DoublePush => {
board.move_piece(mv.src, mv.dst, piece_at_src.0);
en_passant_square = match color {
Color::White => Some(mv.src + 8),
Color::Black => Some(mv.src.saturating_sub(8)),
};
hash.update_double_push(mv.src, mv.dst, color, en_passant_square);
hash.drop_en_passant_hash(board.state.en_passant_square());
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
}
MoveType::Promotion(promote) => {
board.remove_own_piece(mv.src, piece_at_src.0);
board.promote_piece(mv.dst, promote);
hash.update_promotion(mv.src, mv.dst, promote, color);
hash.drop_en_passant_hash(board.state.en_passant_square());
mailbox.set_piece_at(mv.dst, Some((promote.into_piece_type(), color)));
}
MoveType::PromotionCapture(promote) => {
let piece_at_dst = piece_at_dst.expect("Expected piece at dst: {mv.dst}");
board.remove_own_piece(mv.src, piece_at_src.0);
board.remove_opponent_piece(mv.dst, piece_at_dst.0);
board.promote_piece(mv.dst, promote);
hash.update_promotion_capture(mv.src, mv.dst, piece_at_dst.0, promote, color);
hash.drop_en_passant_hash(board.state.en_passant_square());
mailbox.set_piece_at(mv.dst, Some((promote.into_piece_type(), color)));
}
MoveType::Castle => {
board.move_piece(mv.src, mv.dst, piece_at_src.0);
let (rook_src, rook_dst) = match mv.dst {
Square::C1 | Square::C8 => (mv.dst - 2, mv.dst + 1),
Square::G1 | Square::G8 => (mv.dst + 1, mv.dst - 1),
_ => return,
};
board.move_piece(rook_src, rook_dst, PieceType::Rook);
board.state.set_castling_ability(color, Castle::None);
hash.update_castle(mv.src, mv.dst, piece_at_src.0, rook_src, rook_dst, color);
hash.drop_en_passant_hash(board.state.en_passant_square());
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
mailbox.set_piece_at(rook_src, None);
mailbox.set_piece_at(rook_dst, Some((PieceType::Rook, color)));
}
}
mailbox.set_piece_at(mv.src, None);
board
.state
.update_game_state(mv, color, pawn_move, en_passant_square);
hash.update_side_to_move_key();
hash.update_castling_ability_keys(old_castling_ability, board.state.castling_ability);
}
pub fn unmake_move(&mut self) {
let board = &mut self.board;
let mailbox = &mut self.mailbox;
let move_parameters = &mut self
.history
.pop_move_parameters()
.expect("History stack is empty");
let color_before_move = board.state.change_side();
board.state.revert_full_move(color_before_move);
board.state.en_passant_square = move_parameters.en_passant_square;
if let Some(hash) = move_parameters.zobrist_hash {
self.hash = hash;
}
if let Some(new_castling_ability) = move_parameters.castling_ability {
board.state.castling_ability = new_castling_ability;
}
if let Some(new_halfmove_clock) = move_parameters.halfmove_clock {
board.state.halfmove_clock = new_halfmove_clock;
}
let mv = move_parameters
.mv
.expect("Expected move parameters from history stack");
let piece_at_dst = mailbox.piece_at(mv.dst).expect("Expected set piece");
match &mv.move_type {
MoveType::Quiet | MoveType::DoublePush => {
board.move_piece(mv.dst, mv.src, piece_at_dst.0);
mailbox.set_piece_at(mv.src, mailbox.piece_at(mv.dst));
mailbox.set_piece_at(mv.dst, None);
}
MoveType::Capture => {
let captured_piece = move_parameters
.captured_piece
.expect("Expected captured piece to unmake Capture");
board.move_piece(mv.dst, mv.src, piece_at_dst.0);
board.insert_opponent_piece(mv.dst, captured_piece.0);
mailbox.set_piece_at(mv.src, move_parameters.moving_piece);
mailbox.set_piece_at(mv.dst, Some(captured_piece));
}
MoveType::Promotion(_) => {
let promoted_piece = move_parameters
.promoted_piece
.expect("Expected promoted piece to unmake Promotion");
board.remove_own_piece(mv.dst, promoted_piece.0);
board.insert_own_piece(mv.src, PieceType::Pawn);
mailbox.set_piece_at(mv.src, Some((PieceType::Pawn, color_before_move)));
mailbox.set_piece_at(mv.dst, None);
}
MoveType::PromotionCapture(_) => {
let promoted_piece = move_parameters
.promoted_piece
.expect("Expected promoted piece to unmake PromotionCapture");
let captured_piece = move_parameters
.captured_piece
.expect("Expected captured piece to unmake PromotionCapture");
board.remove_own_piece(mv.dst, promoted_piece.0);
board.insert_opponent_piece(mv.dst, captured_piece.0);
board.insert_own_piece(mv.src, PieceType::Pawn);
mailbox.set_piece_at(mv.src, Some((PieceType::Pawn, color_before_move)));
mailbox.set_piece_at(mv.dst, Some(captured_piece));
}
MoveType::EnPassant => {
let enemy_pawn_square = match color_before_move {
Color::White => mv.dst - 8,
Color::Black => mv.dst + 8,
};
board.move_piece(mv.dst, mv.src, piece_at_dst.0);
board.insert_opponent_piece(enemy_pawn_square, PieceType::Pawn);
mailbox.set_piece_at(mv.src, Some((PieceType::Pawn, color_before_move)));
mailbox.set_piece_at(
enemy_pawn_square,
Some((PieceType::Pawn, color_before_move.opponent())),
);
}
MoveType::Castle => {
let (rook_src, rook_dst) = match mv.dst {
Square::C1 | Square::C8 => (mv.dst - 2, mv.dst + 1),
Square::G1 | Square::G8 => (mv.dst + 1, mv.dst - 1),
_ => return,
};
board.move_piece(mv.dst, mv.src, piece_at_dst.0);
board.remove_own_piece(rook_dst, PieceType::Rook);
board.insert_own_piece(rook_src, PieceType::Rook);
mailbox.set_piece_at(mv.src, mailbox.piece_at(mv.dst));
mailbox.set_piece_at(mv.dst, None);
mailbox.set_piece_at(rook_src, Some((PieceType::Rook, color_before_move)));
mailbox.set_piece_at(rook_dst, None);
}
}
}
}
impl Default for Game {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::{
board::{fen::from_fen, square::Square},
movegen::r#move::{MoveType, Promote},
};
use super::*;
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
const FEN_QUIET: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2BQ3/1P1PNPP1/R3K2R b KQk - 1 1";
#[test]
fn test_make_move_quiet() -> Result<(), String> {
let mut game = from_fen(FEN)?;
let f3e3 = Move::new(Square::F3, Square::E3);
game.make_move(&f3e3);
assert_eq!(game, from_fen(FEN_QUIET)?);
Ok(())
}
const FEN_CAPTURE: &str = "1r2k2r/2P1pq1p/2npb3/1p3QpP/p3P3/P2B4/1P1PNPP1/R3K2R b KQk - 0 1";
#[test]
fn test_make_move_capture() -> Result<(), String> {
let mut game = from_fen(FEN)?;
let f3f5 = Move::with_type(Square::F3, Square::F5, MoveType::Capture);
game.make_move(&f3f5);
assert_eq!(game, from_fen(FEN_CAPTURE)?);
Ok(())
}
const FEN_EN_PASSANT: &str =
"1r2k2r/2P1pq1p/2npb1P1/1p3p2/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
#[test]
fn test_make_move_en_passant() -> Result<(), String> {
let mut game = from_fen(FEN)?;
let h5g6 = Move::with_type(Square::H5, Square::G6, MoveType::EnPassant);
game.make_move(&h5g6);
assert_eq!(game, from_fen(FEN_EN_PASSANT)?);
Ok(())
}
const FEN_DOUBLE_PUSH: &str =
"1r2k2r/2P1pq1p/2npb3/1p3ppP/pP2P3/P2B1Q2/3PNPP1/R3K2R b KQk b3 0 1";
#[test]
fn test_make_move_double_push() -> Result<(), String> {
let mut game = from_fen(FEN)?;
let b2b4 = Move::with_type(Square::B2, Square::B4, MoveType::DoublePush);
game.make_move(&b2b4);
assert_eq!(game, from_fen(FEN_DOUBLE_PUSH)?);
Ok(())
}
const FEN_PROMOTION: &str = "1rQ1k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
#[test]
fn test_make_move_promotion() -> Result<(), String> {
let mut game = from_fen(FEN)?;
let c7c8 = Move::with_type(Square::C7, Square::C8, MoveType::Promotion(Promote::Queen));
game.make_move(&c7c8);
assert_eq!(game, from_fen(FEN_PROMOTION)?);
Ok(())
}
const FEN_PROMOTION_CAPTURE: &str =
"1Q2k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
#[test]
fn test_make_move_promotion_capture() -> Result<(), String> {
let mut game = from_fen(FEN)?;
let c7b8 = Move::with_type(
Square::C7,
Square::B8,
MoveType::PromotionCapture(Promote::Queen),
);
game.make_move(&c7b8);
assert_eq!(game, from_fen(FEN_PROMOTION_CAPTURE)?);
Ok(())
}
const FEN_CASTLE: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R4RK1 b k - 1 1";
#[test]
fn test_make_move_castle() -> Result<(), String> {
let mut game = from_fen(FEN)?;
let e1g1 = Move::with_type(Square::E1, Square::G1, MoveType::Castle);
game.make_move(&e1g1);
assert_eq!(game, from_fen(FEN_CASTLE)?);
Ok(())
}
const FEN_1: &str = "1r2k2r/2P1p1qp/2npb3/1p3p2/p3P1pP/P2B1Q2/1P1PNPP1/R3K2R w KQk - 0 1";
const FEN_2: &str = "1r2k2r/2P1p1qp/2npb3/1p3p2/pP2P1pP/P2B1Q2/3PNPP1/R3K2R b KQk b3 0 1";
#[test]
fn test_unmake_quiet_and_double_push() -> Result<(), String> {
let mut game = from_fen(FEN_1)?;
let game_before_make = game.clone();
let mv = Move::with_type(Square::B2, Square::B3, MoveType::Quiet);
game.make_move(&mv);
game.unmake_move();
assert_eq!(game_before_make, game);
let mv = Move::with_type(Square::B2, Square::B4, MoveType::DoublePush);
game.make_move(&mv);
game.unmake_move();
assert_eq!(game_before_make, game);
Ok(())
}
#[test]
fn test_unmake_capture_and_promotion() -> Result<(), String> {
let mut game = from_fen(FEN_1)?;
let game_before_make = game.clone();
let mv = Move::with_type(Square::D3, Square::B5, MoveType::Capture);
game.make_move(&mv);
game.unmake_move();
assert_eq!(game_before_make, game);
let mv = Move::with_type(Square::C7, Square::C8, MoveType::Promotion(Promote::Queen));
game.make_move(&mv);
game.unmake_move();
assert_eq!(game_before_make, game);
let mv = Move::with_type(
Square::C7,
Square::B8,
MoveType::PromotionCapture(Promote::Queen),
);
game.make_move(&mv);
game.unmake_move();
assert_eq!(game_before_make, game);
Ok(())
}
#[test]
fn test_unmake_en_passant() -> Result<(), String> {
let mut game = from_fen(FEN_2)?;
let game_before_make = game.clone();
let mv = Move::with_type(Square::A4, Square::B3, MoveType::EnPassant);
game.make_move(&mv);
game.unmake_move();
assert_eq!(game_before_make, game);
Ok(())
}
#[test]
fn test_unmake_castle() -> Result<(), String> {
let mut game = from_fen(FEN_1)?;
let game_before_make = game.clone();
let mv = Move::with_type(Square::E1, Square::C1, MoveType::Castle);
game.make_move(&mv);
game.unmake_move();
assert_eq!(game_before_make, game);
Ok(())
}
}