bitstackchess 0.1.1

A bitboard‐based chess game engine with 10 × u128 move history
Documentation
//! O(1) bidirectional mapping between piece IDs (0..31) and squares (0..63).
//! Also maintains `occupied: u64` (bitboard of all occupied squares) and
//! `captured_bits: u32` (bit i = 1 if piece i is off‐board).

pub type Occupied = u64;
pub type CapturedBits = u32;

#[derive(Clone, Debug)]
pub struct PieceMapping {
    /// piece_square[pid] = Some(sq) if piece pid sits on `sq`, else None if captured.
    pub piece_square: [Option<u8>; 32],
    /// square_piece[sq] = Some(pid) if piece pid sits on `sq`, else None if empty.
    pub square_piece: [Option<u8>; 64],
}

impl PieceMapping {
    /// Create an empty board (all pieces off‐board, all squares empty).
    pub fn new_empty() -> PieceMapping {
        PieceMapping {
            piece_square: [None; 32],
            square_piece: [None; 64],
        }
    }

    /// Place a piece `pid` onto square `sq`. Panic if pid already on board or sq is occupied.
    pub fn place_piece(&mut self, pid: u8, sq: u8) {
        let pi = pid as usize;
        let sqi = sq as usize;
        assert!(
            self.piece_square[pi].is_none(),
            "Piece {} already on board",
            pid
        );
        assert!(
            self.square_piece[sqi].is_none(),
            "Square {} is already occupied",
            sq
        );
        self.piece_square[pi] = Some(sq);
        self.square_piece[sqi] = Some(pid);
    }

    /// Remove `pid` from its current square, marking it as captured.
    pub fn remove_piece(&mut self, pid: u8) {
        let pi = pid as usize;
        if let Some(old_sq) = self.piece_square[pi] {
            let sqi = old_sq as usize;
            self.square_piece[sqi] = None;
        }
        self.piece_square[pi] = None;
    }

    /// Move piece `pid` from its current square to `dest`. Panic if dest occupied or pid not on board.
    pub fn move_piece(&mut self, pid: u8, dest: u8) {
        let pi = pid as usize;
        let dest_i = dest as usize;
        let old_sq = self.piece_square[pi]
            .expect(&format!("(move_piece) piece {} not on board", pid)) as usize;
        assert!(
            self.square_piece[dest_i].is_none(),
            "Destination {} occupied",
            dest
        );
        // Vacate old:
        self.square_piece[old_sq] = None;
        // Occupy dest:
        self.square_piece[dest_i] = Some(pid);
        self.piece_square[pi] = Some(dest);
    }

    /// Return Some(pid) if square `sq` occupied, else None.
    pub fn who_on_square(&self, sq: u8) -> Option<u8> {
        self.square_piece[sq as usize]
    }
}

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

    #[test]
    fn mapping_basic() {
        let mut pm = PieceMapping::new_empty();
        pm.place_piece(5, 20);
        assert_eq!(pm.piece_square[5], Some(20));
        assert_eq!(pm.who_on_square(20), Some(5));
        pm.move_piece(5, 30);
        assert_eq!(pm.piece_square[5], Some(30));
        assert_eq!(pm.who_on_square(20), None);
        assert_eq!(pm.who_on_square(30), Some(5));
        pm.remove_piece(5);
        assert_eq!(pm.piece_square[5], None);
        assert_eq!(pm.who_on_square(30), None);
    }
}