monster_chess 0.0.24

A fairy chess movegen library that can be easily extended to new chess-adjacent games.
Documentation
use std::fmt::Debug;

use crate::bitboard::BitBoard;

use super::{
    actions::{
        Action, HistoryMove, HistoryState, HistoryUpdate, IndexedPreviousBoard, PreviousBoard, Move, ActionInfo, TurnInfo,
    },
    game::Game,
    AttackDirections, AttackLookup, Board, BoardState, PieceType, update_turns, reverse_turns,
};

/// `PieceSymbol` shows how a piece will be displayed in FEN.
/// 
/// If it's `Char`, it'll be displayed based on what char it is, and uppercase or lowercase depending on the team. 
/// For teams of three or more, you'd have to specify the team number after the piece.
/// 
/// If it's `TeamSymbol`, the piece is displayed with a different char depending on the team.
/// This is more useful for games with very few pieces, like Ataxx with only one piece type.
pub enum PieceSymbol {
    Char(char),
    TeamSymbol(Vec<char>),
}

/// This is a concept for the base value of `move_type` or `info`, representing a move with normal info. 
const NORMAL_MOVE: u16 = 0;

/// `Piece` is the essential trait that all of `monster-chess` is built on top of.
/// 
/// It defines the following:
/// - How a piece will be displayed in the FEN.
/// - What `info` means for a given piece (`info` describing extra information that `from` and `to` cannot express.)
/// - If the piece has an attack lookup, and how to generate that lookup table.
/// - What BitBoard moves are generated by the piece.
/// - What should happen when the move is made, and when it is undone.
pub trait Piece<const T: usize> : Debug + Send + Sync {
    /// Gets the `PieceSymbol`, which describes how the piece is represented in the board state of FENs.
    fn get_piece_symbol(&self) -> PieceSymbol;

    /// Formats the move information.
    /// 
    /// For instance, take pawns in chess. 
    /// If it's `0`, it'll be represented as nothing.
    /// However, for `1..5`, it'll show that piece is being promoted to (eg. `"q"`.)
    fn format_info(&self, _board: &Board<T>, _info: ActionInfo) -> String {
        "".to_string()
    }

    /// Parses the information from the move info.
    /// 
    /// For instance, take pawns in chess.
    /// If it's nothing, it knows it'll be `0`. 
    /// However, for say, `"q"`, it'll know that's the 4th promotion piece and output `4`.
    fn parse_info(&self, _board: &Board<T>, _info: String) -> u32 {
        0
    }

    /// Whether or not this `Piece` takes advantage of lookup tables.
    fn can_lookup(&self) -> bool;

    /// This gets the lookup table for the given piece type, if one exists.
    /// It's best not to override this method, as `monster-chess` will handle it for you.
    fn get_attack_lookup<'a>(
        &self,
        board: &'a Board<T>,
        piece_type: PieceType,
    ) -> Option<&'a AttackLookup<T>> {
        board.attack_lookup.get(piece_type as usize)
    }

    /// This gives you a `BitBoard` of possible move squares.
    fn get_moves(
        &self,
        board: &Board<T>,
        from: BitBoard<T>,
        piece_type: PieceType,
        team: u16,
        mode: u16,
    ) -> BitBoard<T>;

    /// This gives you a mask of all possible move squares.
    /// Unless you'll frequently need to check for if a piece can move to a given square, it's best not to reimplement this.
    fn can_move_mask(
        &self,
        board: &Board<T>,
        from: BitBoard<T>,
        _from_bit: u16,
        piece_type: PieceType,
        team: u16,
        mode: u16,
        _to: BitBoard<T>,
    ) -> BitBoard<T> {
        self.get_moves(board, from, piece_type, team, mode)
    }

    /// This gives you all of the lookup squares from a given position.
    /// It returns an `AttackDirections<T>`, because you may have multiple lookup boards for any given position.
    /// Take slider pieces in chess.
    /// You have to apply move generation to every individual ray, so you can see if a piece is in the way.
    /// For those pieces then, there's a lookup board for every ray.
    #[allow(unused_variables)]
    fn generate_lookup_moves(&self, board: &Board<T>, from: BitBoard<T>) -> AttackDirections<T> {
        Vec::new()
    }

    /// This makes a capture move, assuming that captures work just like they do in chess.
    /// If you aren't overriding `make_move` and you need custom capture logic, make sure to override this.
    fn make_capture_move(
        &self,
        board: &mut Board<T>,
        action: &Action,
        piece_type: PieceType,
        from: BitBoard<T>,
        to: BitBoard<T>,
        turn_info: TurnInfo
    ) -> Option<HistoryMove<T>> {
        let color = action.team as usize;
        let piece_type = piece_type as usize;
        let captured_color: usize = if (to & board.state.teams[0]).is_set() {
            0
        } else {
            1
        };
        let mut captured_piece_type: PieceType = 0;
        for i in 0..(board.game.pieces.len()) {
            if (board.state.pieces[i] & to).is_set() {
                captured_piece_type = i as u16;
                break;
            }
        }
        
        

        let history_move = HistoryMove {
            action: Move::Action(*action),
            first_history_move: board.retrieve_first_history_move(Move::Action(*action)),
            turn_info,
            state: HistoryState::Any {
                all_pieces: PreviousBoard(board.state.all_pieces),
                first_move: PreviousBoard(board.state.first_move),
                updates: vec![
                    HistoryUpdate::Team(IndexedPreviousBoard(color, board.state.teams[color])),
                    HistoryUpdate::Team(IndexedPreviousBoard(
                        captured_color,
                        board.state.teams[captured_color],
                    )),
                    HistoryUpdate::Piece(IndexedPreviousBoard(
                        piece_type,
                        board.state.pieces[piece_type],
                    )),
                    HistoryUpdate::Piece(IndexedPreviousBoard(
                        captured_piece_type as usize,
                        board.state.pieces[captured_piece_type as usize],
                    )),
                ],
            }
        };

        board.state.teams[captured_color] ^= to;
        board.state.teams[color] ^= from;
        board.state.teams[color] |= to;

        board.state.pieces[captured_piece_type as usize] ^= to;
        board.state.pieces[piece_type] ^= from;
        board.state.pieces[piece_type] |= to;

        board.state.all_pieces ^= from;

        board.state.first_move &= !from;
        board.state.first_move &= !to;
        // We actually don't need to swap the blockers. A blocker will still exist on `to`, just not on `from`.

        Some(history_move)
    }

    /// This makes a normal (non-capturing) move, assuming that normal moves work just like they do in chess.
    /// If you aren't overriding `make_move` and you need custom normal move logic, make sure to override this.
    fn make_normal_move(
        &self,
        board: &mut Board<T>,
        action: &Action,
        piece_type: PieceType,
        from: BitBoard<T>,
        to: BitBoard<T>,
        turn_info: TurnInfo
    ) -> Option<HistoryMove<T>> {
        let color = action.team as usize;
        let piece_type = piece_type as usize;

        let history_move = HistoryMove {
            action: Move::Action(*action),
            first_history_move: board.retrieve_first_history_move(Move::Action(*action)),
            turn_info,
            state: HistoryState::Single {
                team: IndexedPreviousBoard(color, board.state.teams[color]),
                piece: IndexedPreviousBoard(piece_type, board.state.pieces[piece_type]),
                all_pieces: PreviousBoard(board.state.all_pieces),
                first_move: PreviousBoard(board.state.first_move),
            },
        };

        board.state.teams[color] ^= from;
        board.state.teams[color] |= to;

        board.state.pieces[piece_type] ^= from;
        board.state.pieces[piece_type] |= to;

        board.state.all_pieces ^= from;
        board.state.all_pieces |= to;

        board.state.first_move &= !from;

        Some(history_move)
    }

    /// An implementation of `make_move`, provided move-making follows the same logic as chess.
    /// 
    /// If the distinction between normal moves and capture moves is the same as chess, consider keeping `make_move` untouched, and overriding `make_normal_move` and `make_capture_move`.
    /// Otherwise, it may be best to implement `make_move` yourself.
    fn make_move(&self, board: &mut Board<T>, action: &Action) -> Option<HistoryMove<T>> {
        if let Some(from) = action.from {
            let from = BitBoard::from_lsb(from);
            let to = BitBoard::from_lsb(action.to);

            let turn_info = board.get_turn_info();            
            update_turns(&mut board.state, &board.game, &Move::Action(*action));

            let history_move = if (board.state.all_pieces & to).is_empty() {
                self.make_normal_move(board, action, action.piece_type, from, to, turn_info)
            } else {
                self.make_capture_move(board, action, action.piece_type, from, to, turn_info)
            };

            history_move
        } else {
            None
        }
    }

    /// An implementation of `undo_move`, resetting the board to its previous state.
    /// It's not recommended to override `undo_move`, as it generalizes to all games and all types of move-making.
    fn undo_move(&self, state: &mut BoardState<T>, game: &Game<T>, history_move: &HistoryMove<T>) {
        reverse_turns(state, game, &history_move);

        match &history_move.state {
            HistoryState::Single {
                all_pieces,
                first_move,
                team,
                piece,
            } => {
                state.all_pieces = all_pieces.0;
                state.first_move = first_move.0;
                state.teams[team.0] = team.1;
                state.pieces[piece.0] = piece.1;
            }
            HistoryState::Any {
                first_move,
                all_pieces,
                updates,
            } => {
                state.all_pieces = all_pieces.0;
                state.first_move = first_move.0;
                for change in updates {
                    match change {
                        HistoryUpdate::Team(team) => {
                            state.teams[team.0] = team.1;
                        }
                        HistoryUpdate::Piece(piece) => {
                            state.pieces[piece.0] = piece.1;
                        }
                    }
                }
            }
            HistoryState::None => {}
        }
    }

    /// An implementation of `add_actions`, mapping every bit in `get_moves` to a new action.
    /// 
    /// It may be best to override `add_actions` if you need to handle custom types of moves with their own `info`.
    fn add_actions(
        &self,
        actions: &mut Vec<Move>,
        board: &Board<T>,
        piece_type: PieceType,
        from: u16,
        team: u16,
        mode: u16,
    ) {
        let from_board = BitBoard::from_lsb(from);

        let bit_actions = self.get_moves(board, from_board, piece_type, team, mode)
            & !board.state.teams[team as usize] & !board.state.gaps;

        if bit_actions.is_empty() {
            return;
        }

        for bit in bit_actions.iter_set_bits(board.state.squares) {
            actions.push(Move::Action(Action {
                from: Some(from),
                to: bit,
                team,
                info: NORMAL_MOVE,
                move_type: NORMAL_MOVE,
                piece_type,
            }));
        }
    }
}