Skip to main content

quantik_core/
state.rs

1use crate::bitboard::Bitboard;
2use crate::constants::{FLAG_CANON, VERSION};
3use crate::qfen::{bb_from_qfen, bb_to_qfen};
4use crate::symmetry::SymmetryHandler;
5
6/// Serialisable game state wrapping a [`Bitboard`].
7#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
8pub struct State {
9    pub bb: Bitboard,
10}
11
12impl State {
13    pub fn new(bb: Bitboard) -> Self {
14        Self { bb }
15    }
16
17    pub fn empty() -> Self {
18        Self {
19            bb: Bitboard::EMPTY,
20        }
21    }
22
23    // ── binary (18 bytes: version + flags + 8×u16 LE) ───────────────
24
25    pub fn pack(&self, flags: u8) -> [u8; 18] {
26        let mut buf = [0u8; 18];
27        buf[0] = VERSION;
28        buf[1] = flags;
29        let bb_bytes = self.bb.to_le_bytes();
30        buf[2..18].copy_from_slice(&bb_bytes);
31        buf
32    }
33
34    pub fn unpack(data: &[u8]) -> Result<Self, String> {
35        if data.len() < 18 {
36            return Err(format!(
37                "Buffer too small: need 18 bytes, got {}",
38                data.len()
39            ));
40        }
41        if data[0] != VERSION {
42            return Err(format!("Unsupported version {}", data[0]));
43        }
44        let mut bb_buf = [0u8; 16];
45        bb_buf.copy_from_slice(&data[2..18]);
46        Ok(Self {
47            bb: Bitboard::from_le_bytes(&bb_buf),
48        })
49    }
50
51    // ── QFEN ─────────────────────────────────────────────────────────
52
53    pub fn to_qfen(&self) -> String {
54        bb_to_qfen(&self.bb)
55    }
56
57    pub fn from_qfen(qfen: &str) -> Result<Self, String> {
58        bb_from_qfen(qfen).map(|bb| Self { bb })
59    }
60
61    // ── canonical form ───────────────────────────────────────────────
62
63    pub fn canonical_payload(&self) -> [u8; 16] {
64        SymmetryHandler::canonical_payload(&self.bb)
65    }
66
67    pub fn canonical_key(&self) -> [u8; 18] {
68        let mut key = [0u8; 18];
69        key[0] = VERSION;
70        key[1] = FLAG_CANON;
71        key[2..18].copy_from_slice(&self.canonical_payload());
72        key
73    }
74
75    pub fn symmetry_count(&self) -> usize {
76        SymmetryHandler::orbit_size(&self.bb)
77    }
78}
79
80impl Default for State {
81    fn default() -> Self {
82        Self::empty()
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn pack_unpack_roundtrip() {
92        let st = State::from_qfen("A.bC/..../d..B/...a").unwrap();
93        let buf = st.pack(0);
94        let st2 = State::unpack(&buf).unwrap();
95        assert_eq!(st, st2);
96    }
97
98    #[test]
99    fn canonical_key_starts_with_version_flag() {
100        let key = State::empty().canonical_key();
101        assert_eq!(key[0], VERSION);
102        assert_eq!(key[1], FLAG_CANON);
103    }
104
105    #[test]
106    fn qfen_roundtrip() {
107        let qfen = "AbCd/..../..../....";
108        let st = State::from_qfen(qfen).unwrap();
109        assert_eq!(st.to_qfen(), qfen);
110    }
111}