sisyphus32 1.3.4

Feature-based UCI Chess Engine
Documentation
use std::{collections::HashMap};

use crate::{BitMove, BotGameError, Color, HistoryHeuristic, KillerMoves, Legal, MoveGeneration, MoveList, Piece, Position, ScoringMove, Search, Square, TranspositionTable, Uci};

pub struct BotGame {
    thinking_time: u128,
    player_side: Color,
    position: Position,
    search: Search,
    move_history: Vec<BitMove>,
    legal_moves: MoveList<BitMove>,
}

impl Default for BotGame {
    fn default() -> Self {
        Self::new(Color::Black, 5000)
    }
}

impl BotGame {
    pub fn new(player_side: Color, thinking_time: u128) -> Self {
        KillerMoves::reset();
        HistoryHeuristic::reset();
        TranspositionTable::reset();
        
        Self {
            thinking_time,
            player_side,
            position: Position::starting_position(),
            search: Default::default(),
            move_history: Default::default(),
            legal_moves: MoveGeneration::generate_moves::<BitMove, Legal>(&Position::starting_position())
        }
    }

    pub fn bot_side(&self) -> Color {
        self.player_side.opposite()
    }
    
    pub fn player_side(&self) -> Color {
        self.player_side
    }
    
    pub fn to_move(&self) -> Color {
        self.position.side
    }

    pub fn bot_to_move(&self) -> bool {
        self.bot_side() == self.to_move()
    }
    
    pub fn player_to_move(&self) -> bool {
        self.player_side() == self.to_move()
    }

    pub fn set_thinking_time(&mut self, thinking_time: u128) -> Result<(), BotGameError> {
        self.verify_player_to_move()?;
        self.thinking_time = thinking_time;
        Ok(())
    }

    pub fn bot_play_move(&mut self) -> Result<ScoringMove, BotGameError> {
        self.verify_bot_to_move()?;
        let best_move = self.search.go(&self.position, None, Some(self.thinking_time));
        self.make_move(best_move.bit_move);
        Ok(best_move)
    }

    pub fn player_play_bit_move(&mut self, bit_move: BitMove) -> Result<(), BotGameError> {
        self.verify_player_to_move()?;
        if self.get_legal_moves().contains(&bit_move) {
            self.make_move(bit_move);
            Ok(())
        } else {
            Err(BotGameError::IllegalUciMoveError)
        }
    }

    pub fn player_play_uci_move(&mut self, uci_move: &str) -> Result<(), BotGameError> {
        self.verify_player_to_move()?;
        let bit_move = Uci::parse_move_string(&self.legal_moves, uci_move).map_err(|_| BotGameError::IllegalUciMoveError)?;
        self.make_move(bit_move);
        Ok(())
    }

    fn make_move(&mut self, bit_move: BitMove) {
        self.position.make_move(bit_move);
        self.move_history.push(bit_move);
        self.legal_moves = MoveGeneration::generate_moves::<BitMove, Legal>(&self.position);
    }

    pub fn is_checkmate(&self) -> bool {
        self.get_legal_moves().is_empty()
    }

    pub fn bot_won(&self) -> bool {
        self.is_checkmate() && self.player_to_move()
    }

    pub fn player_won(&self) -> bool {
        self.is_checkmate() && self.bot_to_move()
    }

    pub fn white_won(&self) -> bool {
        self.is_checkmate() && self.to_move() == Color::Black
    }

    pub fn black_won(&self) -> bool {
        self.is_checkmate() && self.to_move() == Color::White
    }

    pub fn player_legal_moves(&self) -> Result<&MoveList<BitMove>, BotGameError>  {
        self.verify_player_to_move()?;
        Ok(self.get_legal_moves())
    }

    pub fn get_legal_moves(&self) -> &MoveList<BitMove> {
        &self.legal_moves
    }

    fn verify_side_to_move(&self, side: Color) -> Result<(), BotGameError> {
        if self.to_move() == side {
            Ok(())
        } else {
            Err(BotGameError::IllegalActionError)
        }
    }

    fn verify_player_to_move(&self) -> Result<(), BotGameError> {
        self.verify_side_to_move(self.player_side())
    }

    fn verify_bot_to_move(&self) -> Result<(), BotGameError> {
        self.verify_side_to_move(self.bot_side())
    }

    #[cfg(feature = "bb_array")]
    pub fn get_2d_board(&self) -> [Option<Piece>; 64] {
        self.position.pps
    }

    #[cfg(feature = "bb_array")]
    pub fn get_piece_map(&self) -> HashMap<Square, Piece> {
        let mut piece_map = HashMap::new();

        for (index, piece) in self.position.pps.iter().enumerate() {
            if let Some(piece) = piece {
                let square = Square::from(index as u8);
                piece_map.insert(square, *piece);
            }
        }

        piece_map
    }

    pub fn get_move_history(&self) -> &[BitMove] {
        &self.move_history
    }

    pub fn get_last_move(&self) -> Option<BitMove> {
        self.get_move_history().last().copied()
    }

    pub fn get_position(&self) -> &Position {
        &self.position
    }

    pub fn in_check(&self) -> bool {
        self.get_position().in_check(self.to_move())
    }

    pub fn get_king_square(&self, side: Color) -> Square {
        match side {
            Color::White => self.position.bitboards[Piece::WK].into(),
            Color::Black => self.position.bitboards[Piece::BK].into(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn making_moves_changes_color() {
        let mut bot_game = BotGame::new(Color::White, 5000);
        bot_game.set_thinking_time(100).unwrap();
        assert_eq!(bot_game.to_move(), Color::White);
        assert!(bot_game.player_to_move());
        bot_game.player_play_bit_move(bot_game.player_legal_moves().unwrap().first()).unwrap();
        assert_eq!(bot_game.to_move(), Color::Black);
        assert!(bot_game.bot_to_move());
        bot_game.bot_play_move().unwrap();
        assert_eq!(bot_game.to_move(), Color::White);
        assert!(bot_game.player_to_move());
    }

    #[test]
    fn initial_bot_game_has_moves() {
        let bot_game = BotGame::new(Color::White, 1000);
        assert!(bot_game.get_legal_moves().len() > 0);
    }

    #[cfg(feature = "bb_array")]
    #[test]
    fn get_2d_board_returns_array_of_tuples() {
        let bot_game = BotGame::new(Color::Black, 5000);
        let piece_positions = bot_game.get_2d_board();
        let piece_map = bot_game.get_piece_map();
        assert_eq!(piece_positions[Square::G8], Some(Piece::BN));
        let piece_set_entry = piece_map.get(&Square::D1);
        assert!(piece_set_entry.is_some_and(|p| *p == Piece::WQ));
    }
}