use crate::{
core::{Color, GameStatus, Move, Piece, Position, Variant, VariantBuilder},
errors::{FenError, MoveError, PGNError},
logic::Game,
parsing::{
fen::get_minified_fen,
pgn::{parse_multiple_pgn, parse_pgn},
},
utils::os::{read_file, write_file},
};
#[derive(Debug, Clone)]
pub struct StandardChess {
game: Game,
}
impl Default for StandardChess {
fn default() -> StandardChess {
StandardChess {
game: Game::default(),
}
}
}
impl VariantBuilder for StandardChess {
fn name() -> &'static str {
"Standard"
}
fn new(game: Game) -> StandardChess {
StandardChess { game }
}
fn from_fen(fen: &str) -> Result<StandardChess, FenError> {
Ok(StandardChess {
game: Game::from_fen(fen)?,
})
}
fn from_pgn(pgn: &str) -> Result<StandardChess, PGNError> {
parse_pgn(pgn)
}
fn load(path: &str) -> Result<StandardChess, PGNError> {
let pgn = read_file(path)?;
StandardChess::from_pgn(&pgn)
}
fn load_all(path: &str) -> Result<Vec<Self>, PGNError> {
let pgn = read_file(path)?;
parse_multiple_pgn(&pgn)
}
}
impl Variant for StandardChess {
fn move_piece(&mut self, move_str: &str) -> Result<GameStatus, MoveError> {
self.game.move_piece(move_str)
}
fn undo(&mut self) {
self.game.undo()
}
fn redo(&mut self) {
self.game.redo()
}
fn pgn(&self) -> String {
self.game.pgn()
}
fn fen(&self) -> String {
self.game.fen()
}
fn get_piece_at(&self, pos: Position) -> Option<Piece> {
self.game.get_piece_at(pos)
}
fn get_legal_moves(&self, pos: Position) -> Vec<Move> {
self.game.get_legal_moves(pos)
}
fn save(&self, path: &str, overwrite: bool) -> Result<(), std::io::Error> {
write_file(path, self.pgn().as_str(), !overwrite)?;
Ok(())
}
fn resign(&mut self, color: Color) {
self.game.resign(color)
}
fn draw(&mut self) {
self.game.draw_by_agreement()
}
fn lost_on_time(&mut self, color: Color) {
self.game.lost_on_time(color)
}
fn get_minified_fen(&self) -> String {
get_minified_fen(&self.fen())
}
fn get_last_move(&self) -> Option<crate::core::Move> {
self.game.get_last_move()
}
fn is_white_turn(&self) -> bool {
self.game.is_white_turn
}
fn get_halfmove_clock(&self) -> u32 {
self.game.halfmove_clock
}
fn get_fullmove_number(&self) -> u32 {
self.game.fullmove_number
}
fn get_castling_rights(&self) -> String {
let mut castling_rights = String::new();
if self.game.castling_rights == 0 {
castling_rights.push('-');
} else {
if self.game.castling_rights & 0b1000 != 0 {
castling_rights.push('K');
}
if self.game.castling_rights & 0b0100 != 0 {
castling_rights.push('Q');
}
if self.game.castling_rights & 0b0010 != 0 {
castling_rights.push('k');
}
if self.game.castling_rights & 0b0001 != 0 {
castling_rights.push('q');
}
}
castling_rights
}
fn get_en_passant(&self) -> Option<Position> {
self.game.en_passant
}
fn get_starting_fen(&self) -> String {
self.game.starting_fen.clone()
}
fn get_status(&self) -> GameStatus {
self.game.status
}
}
#[cfg(test)]
mod tests {
use crate::core::WinReason;
use super::*;
#[test]
fn test_standard_chess_name() {
assert_eq!(StandardChess::name(), "Standard");
}
#[test]
fn test_default() {
let variant = StandardChess::default();
assert_eq!(
variant.fen(),
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
);
}
#[test]
fn test_new() {
let game = Game::default();
let variant = StandardChess::new(game.clone());
assert_eq!(variant.fen(), game.fen());
}
#[test]
fn test_from_fen() {
let fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
let variant = StandardChess::from_fen(fen).unwrap();
assert_eq!(variant.fen(), fen);
}
#[test]
fn test_from_pgn() {
let pgn = "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6";
let variant = StandardChess::from_pgn(pgn).unwrap();
assert!(variant.pgn().contains("1. e4 e5 2. Nf3 Nc6 3. Bb5 a6"));
}
#[test]
fn test_load() {
let path = "data/standard/ex1.pgn";
let variant = StandardChess::load(path).unwrap();
assert!(variant.pgn().contains("1. e4 c6 2. d4 d5 3. exd5 cxd5"));
}
#[test]
fn test_load_all() {
let path = "data/standard/ex3.pgn";
let variants = StandardChess::load_all(path).unwrap();
assert_eq!(variants.len(), 20);
}
#[test]
fn test_move_piece() {
let mut variant = StandardChess::default();
let status = variant.move_piece("e4").unwrap();
assert_eq!(status, GameStatus::InProgress);
assert_eq!(
variant.fen(),
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
);
}
#[test]
fn test_get_piece_at() {
let variant = StandardChess::default();
let piece = variant.get_piece_at(Position::from_string("e2").unwrap());
assert!(piece.is_some());
assert_eq!(piece.unwrap().to_string(), "P");
}
#[test]
fn test_get_legal_moves() {
let variant = StandardChess::default();
let legal_moves = variant.get_legal_moves(Position::from_string("e2").unwrap());
assert!(legal_moves.iter().any(|m| m.to_string() == "e4"));
assert!(legal_moves.iter().any(|m| m.to_string() == "e3"));
}
#[test]
fn test_undo_redo() {
let mut variant = StandardChess::default();
variant.move_piece("e4").unwrap();
variant.undo();
assert_eq!(
variant.fen(),
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
);
variant.redo();
assert_eq!(
variant.fen(),
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
);
}
#[test]
fn test_save() {
let mut variant = StandardChess::default();
let path = "data/standard/test_save.pgn";
variant.move_piece("e4").unwrap();
variant.save(path, true).unwrap();
let loaded_variant = StandardChess::load(path).unwrap();
assert_eq!(variant.fen(), loaded_variant.fen());
std::fs::remove_file(path).unwrap();
}
#[test]
fn test_resign() {
let mut variant = StandardChess::default();
variant.resign(Color::White);
assert_eq!(
variant.get_status(),
GameStatus::BlackWins(WinReason::Resignation)
);
}
#[test]
fn test_draw() {
let mut variant = StandardChess::default();
variant.draw();
assert_eq!(
variant.get_status(),
GameStatus::Draw(crate::core::DrawReason::Agreement)
);
}
#[test]
fn test_lost_on_time() {
let mut variant = StandardChess::default();
variant.lost_on_time(Color::Black);
assert_eq!(variant.get_status(), GameStatus::WhiteWins(WinReason::Time));
}
#[test]
fn test_minified_fen() {
let variant = StandardChess::default();
let minified_fen = variant.get_minified_fen();
assert_eq!(minified_fen, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR");
}
#[test]
fn test_get_last_move() {
let mut variant = StandardChess::default();
assert_eq!(variant.get_last_move(), None);
variant.move_piece("e4").unwrap();
let last_move = variant.get_last_move();
assert!(last_move.is_some());
assert_eq!(last_move.unwrap().to_string(), "e4");
}
#[test]
fn test_is_white_turn() {
let mut variant = StandardChess::default();
assert!(variant.is_white_turn());
variant.move_piece("e4").unwrap();
assert!(!variant.is_white_turn());
}
#[test]
fn test_get_castling_rights() {
let mut variant = StandardChess::default();
let castling_rights = variant.get_castling_rights();
assert_eq!(castling_rights, "KQkq");
variant = StandardChess::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1")
.unwrap();
let castling_rights = variant.get_castling_rights();
assert_eq!(castling_rights, "-");
}
#[test]
fn test_get_en_passant() {
let mut variant = StandardChess::default();
assert_eq!(variant.get_en_passant(), None);
variant.move_piece("e4").unwrap();
variant.move_piece("d5").unwrap();
variant.move_piece("e5").unwrap();
variant.move_piece("f5").unwrap();
assert_eq!(
variant.get_en_passant().unwrap(),
Position::new(5, 5).unwrap()
);
}
#[test]
fn test_get_halfmove_clock() {
let variant = StandardChess::default();
assert_eq!(variant.get_halfmove_clock(), 0);
}
#[test]
fn test_get_fullmove_number() {
let variant = StandardChess::default();
assert_eq!(variant.get_fullmove_number(), 1);
}
#[test]
fn test_get_starting_fen() {
let variant = StandardChess::default();
let starting_fen = variant.get_starting_fen();
assert_eq!(
starting_fen,
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
);
}
#[test]
fn test_get_status() {
let variant = StandardChess::default();
assert_eq!(variant.get_status(), GameStatus::InProgress);
}
}