Skip to main content

quantik_core/
bitboard.rs

1use crate::constants::NUM_PLANES;
2
3/// 128-bit bitboard: 8 planes of u16 for a 4×4 Quantik board.
4///
5/// Layout: `[C0S0, C0S1, C0S2, C0S3, C1S0, C1S1, C1S2, C1S3]`
6/// where C = color (0 = player 0, 1 = player 1) and S = shape (0..3 → A..D).
7#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
8#[repr(C, align(16))]
9pub struct Bitboard {
10    pub planes: [u16; NUM_PLANES],
11}
12
13impl Bitboard {
14    pub const EMPTY: Self = Self {
15        planes: [0; NUM_PLANES],
16    };
17
18    #[inline]
19    pub fn new(planes: [u16; NUM_PLANES]) -> Self {
20        Self { planes }
21    }
22
23    #[inline]
24    pub fn plane_index(player: u8, shape: u8) -> usize {
25        (player as usize) * 4 + (shape as usize)
26    }
27
28    #[inline]
29    pub fn occupied(&self) -> u16 {
30        self.planes.iter().fold(0u16, |acc, &p| acc | p)
31    }
32
33    #[inline]
34    pub fn is_position_occupied(&self, pos: u8) -> bool {
35        self.occupied() & (1u16 << pos) != 0
36    }
37
38    /// Set one bit and return a new bitboard (functional style).
39    #[inline]
40    pub fn with_move(&self, player: u8, shape: u8, position: u8) -> Self {
41        let mut bb = *self;
42        bb.planes[Self::plane_index(player, shape)] |= 1u16 << position;
43        bb
44    }
45
46    /// Total number of pieces for a given player.
47    #[inline]
48    pub fn player_piece_count(&self, player: u8) -> u32 {
49        let base = (player as usize) * 4;
50        (0..4).map(|s| self.planes[base + s].count_ones()).sum()
51    }
52
53    /// Piece count for a specific (player, shape) pair.
54    #[inline]
55    pub fn shape_piece_count(&self, player: u8, shape: u8) -> u32 {
56        self.planes[Self::plane_index(player, shape)].count_ones()
57    }
58
59    /// Pack to 16 little-endian bytes.
60    pub fn to_le_bytes(&self) -> [u8; 16] {
61        let mut buf = [0u8; 16];
62        for (i, &plane) in self.planes.iter().enumerate() {
63            let bytes = plane.to_le_bytes();
64            buf[i * 2] = bytes[0];
65            buf[i * 2 + 1] = bytes[1];
66        }
67        buf
68    }
69
70    /// Unpack from 16 little-endian bytes.
71    pub fn from_le_bytes(buf: &[u8; 16]) -> Self {
72        let mut planes = [0u16; NUM_PLANES];
73        for i in 0..NUM_PLANES {
74            planes[i] = u16::from_le_bytes([buf[i * 2], buf[i * 2 + 1]]);
75        }
76        Self { planes }
77    }
78}
79
80impl Default for Bitboard {
81    fn default() -> Self {
82        Self::EMPTY
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn empty_bitboard() {
92        let bb = Bitboard::EMPTY;
93        assert_eq!(bb.occupied(), 0);
94        assert_eq!(bb.player_piece_count(0), 0);
95        assert_eq!(bb.player_piece_count(1), 0);
96    }
97
98    #[test]
99    fn with_move_sets_bit() {
100        let bb = Bitboard::EMPTY.with_move(0, 0, 5);
101        assert!(bb.is_position_occupied(5));
102        assert!(!bb.is_position_occupied(0));
103        assert_eq!(bb.shape_piece_count(0, 0), 1);
104    }
105
106    #[test]
107    fn roundtrip_bytes() {
108        let bb = Bitboard::new([1, 2, 3, 4, 5, 6, 7, 8]);
109        let bytes = bb.to_le_bytes();
110        let bb2 = Bitboard::from_le_bytes(&bytes);
111        assert_eq!(bb, bb2);
112    }
113
114    #[test]
115    fn plane_index_mapping() {
116        assert_eq!(Bitboard::plane_index(0, 0), 0);
117        assert_eq!(Bitboard::plane_index(0, 3), 3);
118        assert_eq!(Bitboard::plane_index(1, 0), 4);
119        assert_eq!(Bitboard::plane_index(1, 3), 7);
120    }
121}