pub type Figure = u16;
#[inline]
#[must_use]
pub fn xor(a: Figure, b: Figure) -> Figure {
a ^ b
}
#[inline]
#[must_use]
pub fn is_even(f: Figure) -> bool {
f.count_ones().is_multiple_of(2)
}
#[must_use]
pub fn pack(bits: &[bool]) -> Figure {
bits.iter()
.enumerate()
.fold(0u16, |acc, (i, &b)| if b { acc | (1 << i) } else { acc })
}
#[must_use]
pub fn unpack(f: Figure, k: usize) -> Vec<bool> {
(0..k).map(|i| (f >> i) & 1 == 1).collect()
}
#[must_use]
pub fn transpose4(mothers: [Figure; 4]) -> [Figure; 4] {
let mut out = [0u16; 4];
for (d, slot) in out.iter_mut().enumerate() {
for (i, &m) in mothers.iter().enumerate() {
if (m >> d) & 1 == 1 {
*slot |= 1 << i;
}
}
}
out
}
#[derive(Debug, Clone, Copy)]
pub struct Shield {
pub mothers: [Figure; 4],
pub daughters: [Figure; 4],
pub nieces: [Figure; 4],
pub witnesses: [Figure; 2],
pub judge: Figure,
}
#[must_use]
pub fn geomancy_shield(mothers: [Figure; 4]) -> Shield {
let d = transpose4(mothers);
let n = [
xor(mothers[0], mothers[1]),
xor(mothers[2], mothers[3]),
xor(d[0], d[1]),
xor(d[2], d[3]),
];
let wr = xor(n[0], n[1]);
let wl = xor(n[2], n[3]);
Shield {
mothers,
daughters: d,
nieces: n,
witnesses: [wr, wl],
judge: xor(wr, wl),
}
}
#[must_use]
pub fn sikidy_columns(mothers: [Figure; 4]) -> [Figure; 16] {
let mut c = [0u16; 16];
c[0..4].copy_from_slice(&mothers);
let t = transpose4(mothers);
c[4..8].copy_from_slice(&t);
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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn even_figures_count_is_eight() {
let n = (0u16..16).filter(|&f| is_even(f)).count();
assert_eq!(n, 8);
}
#[test]
fn pack_unpack_roundtrip() {
for f in 0u16..64 {
assert_eq!(pack(&unpack(f, 6)), f);
}
}
#[test]
fn geomancy_judge_always_even() {
for combo in 0u32..(16 * 16 * 16 * 16) {
let m = [
(combo & 0xF) as u16,
((combo >> 4) & 0xF) as u16,
((combo >> 8) & 0xF) as u16,
((combo >> 12) & 0xF) as u16,
];
let s = geomancy_shield(m);
assert!(is_even(s.judge), "法官应恒为偶图, mothers={m:?}");
}
}
#[test]
fn sikidy_c15_always_even() {
for combo in 0u32..(16 * 16 * 16 * 16) {
let m = [
(combo & 0xF) as u16,
((combo >> 4) & 0xF) as u16,
((combo >> 8) & 0xF) as u16,
((combo >> 12) & 0xF) as u16,
];
let c = sikidy_columns(m);
assert!(is_even(c[14]), "Sikidy C15 应恒为偶, mothers={m:?}");
}
}
#[test]
fn the_derived_figures_are_pinned_to_concrete_values() {
assert_eq!(xor(0b1010, 0b0110), 0b1100);
assert_eq!(xor(0b1111, 0b0000), 0b1111);
assert_eq!(pack(&[true, false, true, true]), 0b1101);
assert_eq!(unpack(0b1101, 4), vec![true, false, true, true]);
let mothers = [0b0001u16, 0b0011, 0b0111, 0b1111];
assert_eq!(transpose4(mothers), [15, 14, 12, 8]);
let s = geomancy_shield(mothers);
assert_eq!(s.daughters, [15, 14, 12, 8]);
assert_eq!(s.nieces, [2, 8, 1, 4]); assert_eq!(s.witnesses, [10, 5]);
assert_eq!(s.judge, 15);
assert_eq!(
sikidy_columns(mothers),
[1, 3, 7, 15, 15, 14, 12, 8, 4, 1, 8, 2, 5, 10, 15, 14]
);
}
#[test]
fn transpose_agrees_with_the_bit_vector_definition() {
for combo in 0u32..(16 * 16 * 16 * 16) {
let m = [
(combo & 0xF) as u16,
((combo >> 4) & 0xF) as u16,
((combo >> 8) & 0xF) as u16,
((combo >> 12) & 0xF) as u16,
];
let rows: Vec<Vec<bool>> = m.iter().map(|&f| unpack(f, 4)).collect();
let by_definition: [Figure; 4] =
core::array::from_fn(|d| pack(&(0..4).map(|i| rows[i][d]).collect::<Vec<_>>()));
assert_eq!(transpose4(m), by_definition, "mothers={m:?}");
}
}
#[test]
fn transpose_is_involution() {
for combo in [0x0123u32, 0xFEDC, 0xABCD] {
let m = [
(combo & 0xF) as u16,
((combo >> 4) & 0xF) as u16,
((combo >> 8) & 0xF) as u16,
((combo >> 12) & 0xF) as u16,
];
assert_eq!(transpose4(transpose4(m)), m);
}
}
use proptest::prelude::*;
proptest! {
#[test]
fn prop_pack_unpack_roundtrip(bits in prop::collection::vec(any::<bool>(), 0..16)) {
prop_assert_eq!(unpack(pack(&bits), bits.len()), bits);
}
#[test]
fn prop_is_even_matches_popcount_parity(f in any::<u16>()) {
prop_assert_eq!(is_even(f), f.count_ones() % 2 == 0);
}
#[test]
fn prop_transpose4_is_involution(combo in any::<u16>()) {
let m = [combo & 0xF, (combo >> 4) & 0xF, (combo >> 8) & 0xF, (combo >> 12) & 0xF];
prop_assert_eq!(transpose4(transpose4(m)), m);
}
}
}