bitstackchess 0.1.1

A bitboard‐based chess game engine with 10 × u128 move history
Documentation
//!  • encode_square: rank/file → 6‐bit index.
//!  • encode_piece: (color, is_pawn, spawn_rank/spawn_side, kind) → 5‐bit PID.
//!  • init_chess_positions: produce Vec of (piece_id, square_id) for starting position.

/// Encode (rank, file) in 0..=7 as 6‐bit: (rank << 3) | file.
pub fn encode_square(rank: u8, file: u8) -> u8 {
    assert!(rank < 8 && file < 8, "rank/file must be 0..7");
    (rank << 3) | file
}

/// Encode a 5‐bit piece ID per the scheme:
///  • Bit 4: color (0=White, 1=Black)  
///  • Bit 3: is_pawn (1=pawn, 0=other)  
///  • Bits 2..0: if pawn → spawn_rank; else → (spawn_side<<2)|(kind).
pub fn encode_piece(color: u8, is_pawn: bool, spawn_side_or_rank: u8, kind_or_unused: u8) -> u8 {
    let bit_color = (color & 1) << 4;
    let bit_pawn = (is_pawn as u8) << 3;
    let lower3 = if is_pawn {
        spawn_side_or_rank & 0b111
    } else {
        let side_bit = (spawn_side_or_rank & 1) << 2;
        let kind_bits = kind_or_unused & 0b11;
        side_bit | kind_bits
    };
    bit_color | bit_pawn | lower3
}

/// Build the 32 (piece_id, square_id) entries for standard chess start.
/// Each of the 32 pieces has a unique PID in 0..31:
///
///   • White pawns:        PIDs  0.. 7 at rank=1 (files 0..7)  
///   • White rooks/knights/bishops/queen/king:
///       –  8:  a1 (rook)  
///       –  9:  h1 (rook)  
///       – 10:  b1 (knight)  
///       – 11:  g1 (knight)  
///       – 12:  c1 (bishop)  
///       – 13:  f1 (bishop)  
///       – 14:  d1 (queen)  
///       – 15:  e1 (king)  
///   • Black pawns:        PIDs 16..23 at rank=6 (files 0..7)  
///   • Black rooks/knights/bishops/queen/king:
///       – 24:  a8 (rook)  
///       – 25:  h8 (rook)  
///       – 26:  b8 (knight)  
///       – 27:  g8 (knight)  
///       – 28:  c8 (bishop)  
///       – 29:  f8 (bishop)  
///       – 30:  d8 (queen)  
///       – 31:  e8 (king)
pub fn init_chess_positions() -> Vec<(u8, u8)> {
    let mut v = Vec::with_capacity(32);

    // 1) White pawns (pid 0..7) on rank=1 (squares 8..15)
    for file in 0..8 {
        let pid = file as u8; // 0..7
        let sq = encode_square(1, file);
        v.push((pid, sq));
    }

    // 2) White rooks
    v.push((8, encode_square(0, 0))); // a1
    v.push((9, encode_square(0, 7))); // h1

    // 3) White knights
    v.push((10, encode_square(0, 1))); // b1
    v.push((11, encode_square(0, 6))); // g1

    // 4) White bishops
    v.push((12, encode_square(0, 2))); // c1
    v.push((13, encode_square(0, 5))); // f1

    // 5) White queen & king
    v.push((14, encode_square(0, 3))); // d1
    v.push((15, encode_square(0, 4))); // e1

    // 6) Black pawns (pid 16..23) on rank=6 (squares 48..55)
    for file in 0..8 {
        let pid = 16 + file as u8; // 16..23
        let sq = encode_square(6, file);
        v.push((pid, sq));
    }

    // 7) Black rooks
    v.push((24, encode_square(7, 0))); // a8
    v.push((25, encode_square(7, 7))); // h8

    // 8) Black knights
    v.push((26, encode_square(7, 1))); // b8
    v.push((27, encode_square(7, 6))); // g8

    // 9) Black bishops
    v.push((28, encode_square(7, 2))); // c8
    v.push((29, encode_square(7, 5))); // f8

    // 10) Black queen & king
    v.push((30, encode_square(7, 3))); // d8
    v.push((31, encode_square(7, 4))); // e8

    v
}

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

    #[test]
    fn test_encode_square_basics() {
        assert_eq!(encode_square(0, 0), 0b000_000);
        assert_eq!(encode_square(0, 7), 0b000_111);
        assert_eq!(encode_square(7, 0), 0b111_000);
        assert_eq!(encode_square(7, 7), 0b111_111);
        assert_eq!(encode_square(3, 4), (3 << 3) | 4);
    }

    #[test]
    fn test_encode_piece_non_pawn() {
        // Correct expectations:
        //  • encode_piece(0,false,0,0) → 0x00
        //  • encode_piece(1,false,1,0) → 0x14  (20)
        //  • encode_piece(0,false,1,1) → 0x05   // bishop west
        //  • encode_piece(1,false,0,2) → 0x12   (18)
        assert_eq!(encode_piece(0, false, 0, 0), 0x00);
        assert_eq!(encode_piece(1, false, 1, 0), 0x14);
        assert_eq!(encode_piece(0, false, 1, 1), 0x05);
        assert_eq!(encode_piece(1, false, 0, 2), 0x12);
    }

    #[test]
    fn test_encode_piece_pawn() {
        assert_eq!(encode_piece(0, true, 1, 0), 0b0_1_001); // 0x09
        assert_eq!(encode_piece(1, true, 6, 0), 0b1_1_110); // 0x1E
    }

    #[test]
    fn test_init_chess_positions() {
        let pos = init_chess_positions();
        assert_eq!(pos.len(), 32);

        // Build a map from PID -> Vec<square>
        let mut map: std::collections::BTreeMap<u8, Vec<u8>> =
            std::collections::BTreeMap::new();
        for &(pid, sq) in &pos {
            map.entry(pid).or_default().push(sq);
        }

        // White pawn PIDs 0..7 on squares 8..15
        let mut white_pawn_sqs: Vec<u8> = (0..8).map(|f| encode_square(1, f)).collect();
        white_pawn_sqs.sort();
        let mut found_wp: Vec<u8> = (0..8)
            .flat_map(|pid| map.get(&pid).unwrap().clone())
            .collect();
        found_wp.sort();
        assert_eq!(found_wp, white_pawn_sqs);

        // White rooks: PID  8 -> a1=0; PID  9 -> h1=7
        assert_eq!(map.get(&8), Some(&vec![encode_square(0, 0)]));
        assert_eq!(map.get(&9), Some(&vec![encode_square(0, 7)]));

        // White knights: PID 10 -> b1=1; PID 11 -> g1=6
        assert_eq!(map.get(&10), Some(&vec![encode_square(0, 1)]));
        assert_eq!(map.get(&11), Some(&vec![encode_square(0, 6)]));

        // White bishops: PID 12 -> c1=2; PID 13 -> f1=5
        assert_eq!(map.get(&12), Some(&vec![encode_square(0, 2)]));
        assert_eq!(map.get(&13), Some(&vec![encode_square(0, 5)]));

        // White queen (PID=14) on d1=3; White king (PID=15) on e1=4
        assert_eq!(map.get(&14), Some(&vec![encode_square(0, 3)]));
        assert_eq!(map.get(&15), Some(&vec![encode_square(0, 4)]));

        // Black pawn PIDs 16..23 on squares 48..55
        let mut black_pawn_sqs: Vec<u8> = (0..8).map(|f| encode_square(6, f)).collect();
        black_pawn_sqs.sort();
        let mut found_bp: Vec<u8> = (16..24)
            .flat_map(|pid| map.get(&pid).unwrap().clone())
            .collect();
        found_bp.sort();
        assert_eq!(found_bp, black_pawn_sqs);

        // Black rooks: PID 24 -> a8=56; PID 25 -> h8=63
        assert_eq!(map.get(&24), Some(&vec![encode_square(7, 0)]));
        assert_eq!(map.get(&25), Some(&vec![encode_square(7, 7)]));

        // Black knights: PID 26 -> b8=57; PID 27 -> g8=62
        assert_eq!(map.get(&26), Some(&vec![encode_square(7, 1)]));
        assert_eq!(map.get(&27), Some(&vec![encode_square(7, 6)]));

        // Black bishops: PID 28 -> c8=58; PID 29 -> f8=61
        assert_eq!(map.get(&28), Some(&vec![encode_square(7, 2)]));
        assert_eq!(map.get(&29), Some(&vec![encode_square(7, 5)]));

        // Black queen (30) on d8=59; Black king (31) on e8=60
        assert_eq!(map.get(&30), Some(&vec![encode_square(7, 3)]));
        assert_eq!(map.get(&31), Some(&vec![encode_square(7, 4)]));
    }
}