435 lines
16 KiB
Rust
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(())
|
|
}
|
|
}
|