1pub type Figure = u16;
9
10#[inline]
12#[must_use]
13pub fn xor(a: Figure, b: Figure) -> Figure {
14 a ^ b
15}
16
17#[inline]
19#[must_use]
20pub fn is_even(f: Figure) -> bool {
21 f.count_ones().is_multiple_of(2)
22}
23
24#[must_use]
26pub fn pack(bits: &[bool]) -> Figure {
27 bits.iter()
28 .enumerate()
29 .fold(0u16, |acc, (i, &b)| if b { acc | (1 << i) } else { acc })
30}
31
32#[must_use]
34pub fn unpack(f: Figure, k: usize) -> Vec<bool> {
35 (0..k).map(|i| (f >> i) & 1 == 1).collect()
36}
37
38#[must_use]
41pub fn transpose4(mothers: [Figure; 4]) -> [Figure; 4] {
42 let mut out = [0u16; 4];
43 for (d, slot) in out.iter_mut().enumerate() {
44 for (i, &m) in mothers.iter().enumerate() {
45 if (m >> d) & 1 == 1 {
46 *slot |= 1 << i;
47 }
48 }
49 }
50 out
51}
52
53#[derive(Debug, Clone, Copy)]
55pub struct Shield {
56 pub mothers: [Figure; 4],
58 pub daughters: [Figure; 4],
60 pub nieces: [Figure; 4],
62 pub witnesses: [Figure; 2],
64 pub judge: Figure,
66}
67
68#[must_use]
70pub fn geomancy_shield(mothers: [Figure; 4]) -> Shield {
71 let d = transpose4(mothers);
72 let n = [
73 xor(mothers[0], mothers[1]),
74 xor(mothers[2], mothers[3]),
75 xor(d[0], d[1]),
76 xor(d[2], d[3]),
77 ];
78 let wr = xor(n[0], n[1]);
79 let wl = xor(n[2], n[3]);
80 Shield {
81 mothers,
82 daughters: d,
83 nieces: n,
84 witnesses: [wr, wl],
85 judge: xor(wr, wl),
86 }
87}
88
89#[must_use]
92pub fn sikidy_columns(mothers: [Figure; 4]) -> [Figure; 16] {
93 let mut c = [0u16; 16];
94 c[0..4].copy_from_slice(&mothers);
95 let t = transpose4(mothers);
96 c[4..8].copy_from_slice(&t);
97 c[8] = xor(c[6], c[7]); c[9] = xor(c[4], c[5]); c[10] = xor(c[2], c[3]); c[11] = xor(c[0], c[1]); c[12] = xor(c[8], c[9]); c[13] = xor(c[10], c[11]); c[14] = xor(c[12], c[13]); c[15] = xor(c[14], c[0]); c
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn even_figures_count_is_eight() {
114 let n = (0u16..16).filter(|&f| is_even(f)).count();
116 assert_eq!(n, 8);
117 }
118
119 #[test]
120 fn pack_unpack_roundtrip() {
121 for f in 0u16..64 {
122 assert_eq!(pack(&unpack(f, 6)), f);
123 }
124 }
125
126 #[test]
127 fn geomancy_judge_always_even() {
128 for combo in 0u32..(16 * 16 * 16 * 16) {
130 let m = [
131 (combo & 0xF) as u16,
132 ((combo >> 4) & 0xF) as u16,
133 ((combo >> 8) & 0xF) as u16,
134 ((combo >> 12) & 0xF) as u16,
135 ];
136 let s = geomancy_shield(m);
137 assert!(is_even(s.judge), "法官应恒为偶图, mothers={m:?}");
138 }
139 }
140
141 #[test]
142 fn sikidy_c15_always_even() {
143 for combo in 0u32..(16 * 16 * 16 * 16) {
145 let m = [
146 (combo & 0xF) as u16,
147 ((combo >> 4) & 0xF) as u16,
148 ((combo >> 8) & 0xF) as u16,
149 ((combo >> 12) & 0xF) as u16,
150 ];
151 let c = sikidy_columns(m);
152 assert!(is_even(c[14]), "Sikidy C15 应恒为偶, mothers={m:?}");
153 }
154 }
155
156 #[test]
164 fn the_derived_figures_are_pinned_to_concrete_values() {
165 assert_eq!(xor(0b1010, 0b0110), 0b1100);
166 assert_eq!(xor(0b1111, 0b0000), 0b1111);
167 assert_eq!(pack(&[true, false, true, true]), 0b1101);
168 assert_eq!(unpack(0b1101, 4), vec![true, false, true, true]);
169
170 let mothers = [0b0001u16, 0b0011, 0b0111, 0b1111];
171 assert_eq!(transpose4(mothers), [15, 14, 12, 8]);
172
173 let s = geomancy_shield(mothers);
174 assert_eq!(s.daughters, [15, 14, 12, 8]);
175 assert_eq!(s.nieces, [2, 8, 1, 4]); assert_eq!(s.witnesses, [10, 5]);
177 assert_eq!(s.judge, 15);
178
179 assert_eq!(
180 sikidy_columns(mothers),
181 [1, 3, 7, 15, 15, 14, 12, 8, 4, 1, 8, 2, 5, 10, 15, 14]
182 );
183 }
184
185 #[test]
188 fn transpose_agrees_with_the_bit_vector_definition() {
189 for combo in 0u32..(16 * 16 * 16 * 16) {
190 let m = [
191 (combo & 0xF) as u16,
192 ((combo >> 4) & 0xF) as u16,
193 ((combo >> 8) & 0xF) as u16,
194 ((combo >> 12) & 0xF) as u16,
195 ];
196 let rows: Vec<Vec<bool>> = m.iter().map(|&f| unpack(f, 4)).collect();
197 let by_definition: [Figure; 4] =
198 core::array::from_fn(|d| pack(&(0..4).map(|i| rows[i][d]).collect::<Vec<_>>()));
199 assert_eq!(transpose4(m), by_definition, "mothers={m:?}");
200 }
201 }
202
203 #[test]
204 fn transpose_is_involution() {
205 for combo in [0x0123u32, 0xFEDC, 0xABCD] {
207 let m = [
208 (combo & 0xF) as u16,
209 ((combo >> 4) & 0xF) as u16,
210 ((combo >> 8) & 0xF) as u16,
211 ((combo >> 12) & 0xF) as u16,
212 ];
213 assert_eq!(transpose4(transpose4(m)), m);
214 }
215 }
216
217 use proptest::prelude::*;
218 proptest! {
219 #[test]
220 fn prop_pack_unpack_roundtrip(bits in prop::collection::vec(any::<bool>(), 0..16)) {
221 prop_assert_eq!(unpack(pack(&bits), bits.len()), bits);
222 }
223 #[test]
224 fn prop_is_even_matches_popcount_parity(f in any::<u16>()) {
225 prop_assert_eq!(is_even(f), f.count_ones() % 2 == 0);
226 }
227 #[test]
228 fn prop_transpose4_is_involution(combo in any::<u16>()) {
229 let m = [combo & 0xF, (combo >> 4) & 0xF, (combo >> 8) & 0xF, (combo >> 12) & 0xF];
230 prop_assert_eq!(transpose4(transpose4(m)), m);
231 }
232 }
233}