use crate::engine::Engine;
use crate::game::algebraic_notation::parse_move_to_algebraic_notation;
use crate::game::chess_board::ChessBoard;
use crate::game::chess_move::ChessMove;
use crate::game::color::Color;
use crate::game::pgn_metadata::PGNMetadata;
use crate::game::state::GameState;
use crate::game::status::GameStatus;
use crate::prelude::{Piece, Square};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::sync::Arc;
pub mod algebraic_notation;
pub mod bit_board;
pub mod castling_rights;
pub mod chess_board;
pub mod chess_move;
pub mod color;
pub mod pgn_metadata;
pub mod piece;
pub mod square;
pub mod state;
pub mod status;
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Game {
state: GameState,
status: GameStatus,
legal_moves: HashSet<ChessMove>,
move_history: Vec<ChessMove>,
algebraic_history: Vec<String>,
pgn_metadata: PGNMetadata,
origin_fen: Option<String>,
}
impl Game {
pub fn new(engine: &Arc<Engine>, pgn_metadata: PGNMetadata) -> Self {
let state = GameState::default();
let (legal_moves, status) = engine.generate_moves(&state);
Self {
state,
status,
legal_moves: legal_moves.iter().copied().collect(),
move_history: Vec::new(),
algebraic_history: Vec::new(),
pgn_metadata,
origin_fen: None,
}
}
pub fn play_move(&mut self, engine: &Arc<Engine>, chess_move: ChessMove) -> bool {
if !self.legal_moves.contains(&chess_move) {
return false;
}
if self.origin_fen.is_none() {
if let Some(algebraic) = parse_move_to_algebraic_notation(
engine,
&self.state,
&chess_move,
self.state.side_to_move,
) {
self.algebraic_history.push(algebraic);
} else {
self.algebraic_history.push("".to_string());
}
self.move_history.push(chess_move);
}
self.state.play_move(chess_move);
let (legal_moves, status) = engine.generate_moves(&self.state);
self.legal_moves = legal_moves.iter().copied().collect();
self.status = status;
true
}
pub fn is_move_playable(&self, chess_move: ChessMove) -> bool {
self.legal_moves.contains(&chess_move)
}
pub fn find_legal_move(
&self,
from: Square,
to: Square,
promotion: Option<Piece>,
) -> Option<ChessMove> {
self.legal_moves
.iter()
.find(|chess_move| {
chess_move.get_from() == from.get_value()
&& chess_move.get_to() == to.get_value()
&& chess_move.get_type().promotion_piece() == promotion
})
.copied()
}
pub fn play_move_from_to(
&mut self,
engine: &Arc<Engine>,
from: Square,
to: Square,
promotion: Option<Piece>,
) -> bool {
if let Some(chess_move) = self.find_legal_move(from, to, promotion) {
self.play_move(engine, chess_move)
} else {
false
}
}
pub fn status(&self) -> GameStatus {
self.status
}
pub fn winner(&self) -> Option<Color> {
if self.status == GameStatus::Checkmate {
Some(self.state.side_to_move.opposite())
} else {
None
}
}
pub fn side_to_move(&self) -> Color {
self.state.side_to_move
}
pub fn half_moves(&self) -> u8 {
self.state.half_moves
}
pub fn full_moves(&self) -> u16 {
self.state.full_moves
}
pub fn legal_moves(&self) -> &HashSet<ChessMove> {
&self.legal_moves
}
pub fn legal_moves_algebraic(&self, engine: &Arc<Engine>) -> HashMap<String, ChessMove> {
self.legal_moves
.iter()
.filter_map(|chess_move| {
Some((
parse_move_to_algebraic_notation(
engine,
&self.state,
chess_move,
self.state.side_to_move,
)?,
*chess_move,
))
})
.collect()
}
pub fn legal_move_squares(&self) -> HashMap<Square, Vec<Square>> {
self.legal_moves
.iter()
.fold(HashMap::new(), |mut acc, chess_move| {
let from = Square::new(chess_move.get_from());
let to = Square::new(chess_move.get_to());
acc.entry(from).or_default().push(to);
acc
})
}
pub fn board(&self) -> ChessBoard {
self.state.board
}
pub fn move_history(&self) -> &Vec<ChessMove> {
&self.move_history
}
pub fn latest_move(&self) -> Option<ChessMove> {
self.move_history.last().copied()
}
pub fn latest_move_algebraic(&self) -> Option<String> {
Some(self.algebraic_history.last()?.to_string())
}
pub fn algebraic_history(&self) -> &Vec<String> {
&self.algebraic_history
}
pub fn get_result_pgn(&self) -> Option<&str> {
if let Some(winner) = self.winner() {
match winner {
Color::White => Some("1-0"),
Color::Black => Some("0-1"),
}
} else if self.status.is_draw() {
Some("½–½")
} else {
None
}
}
pub fn get_pgn(&self) -> String {
let mut pgn = self
.pgn_metadata
.format(self.get_result_pgn(), self.origin_fen.as_deref());
if !pgn.is_empty() && !self.algebraic_history.is_empty() {
pgn.push('\n');
}
for (i, algebraic_move) in self.algebraic_history.iter().enumerate() {
let new_round = i % 2 == 0;
let round = (i / 2) + 1;
if new_round {
pgn.push_str(&format!("{}.", round));
}
pgn.push_str(&format!("{} ", algebraic_move));
}
if let Some(result) = self.get_result_pgn() {
pgn.push_str(result);
}
pgn
}
pub fn get_fen_string(&self) -> String {
self.state.get_fen_string()
}
pub fn from_fen_string(engine: &Arc<Engine>, fen_string: &str) -> Result<Self, Box<dyn Error>> {
let state = GameState::from_fen_string(fen_string)?;
let (legal_moves, status) = engine.generate_moves(&state);
Ok(Self {
state,
status,
legal_moves: legal_moves.iter().copied().collect(),
move_history: Vec::new(),
algebraic_history: Vec::new(),
pgn_metadata: PGNMetadata::default(),
origin_fen: Some(fen_string.to_string()),
})
}
pub fn set_pgn_meta_data(&mut self, pgn_meta_data: PGNMetadata) {
self.pgn_metadata = pgn_meta_data;
}
pub fn archive(&self) -> ArchivedGame {
ArchivedGame {
pgn: self.pgn_metadata.clone(),
origin_fen: self.origin_fen.clone(),
played_moves: self.move_history.clone(),
}
}
}
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ArchivedGame {
pub pgn: PGNMetadata,
pub origin_fen: Option<String>,
pub played_moves: Vec<ChessMove>,
}
impl ArchivedGame {
pub fn move_count(&self) -> u16 {
self.played_moves.len() as u16
}
pub fn restore(&self, engine: &Arc<Engine>) -> Game {
self.replay_till(engine, self.move_count())
}
pub fn replay_till(&self, engine: &Arc<Engine>, half_moves: u16) -> Game {
let mut game = if let Some(fen) = &self.origin_fen {
Game::from_fen_string(engine, fen)
.unwrap_or_else(|_| Game::new(engine, self.pgn.clone()))
} else {
Game::new(engine, self.pgn.clone())
};
for chess_move in self.played_moves.iter().take(half_moves as usize) {
game.play_move(engine, *chess_move);
}
game
}
pub fn replay_iter(&self, engine: &Arc<Engine>) -> impl Iterator<Item = Game> + '_ {
let engine = engine.clone();
(0..=self.move_count()).map(move |move_count| self.replay_till(&engine, move_count))
}
}