use crate::games::grundy::mex;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
thread_local! {
static MEX_MEMO: RefCell<HashMap<(u128, u128), u128>> = RefCell::new(HashMap::new());
}
fn lower_mask(n: u128) -> u128 {
assert!(n < 128, "coin bitmask positions must be < 128");
if n == 0 {
0
} else {
(1u128 << n) - 1
}
}
pub fn nim_mul_mex(x: u128, y: u128) -> u128 {
if x == 0 || y == 0 {
return 0;
}
if let Some(v) = MEX_MEMO.with(|m| m.borrow().get(&(x, y)).copied()) {
return v;
}
let mut seen = HashSet::new();
for i in 0..x {
for j in 0..y {
seen.insert(nim_mul_mex(i, y) ^ nim_mul_mex(x, j) ^ nim_mul_mex(i, j));
}
}
let r = mex(seen.iter().copied());
MEX_MEMO.with(|m| m.borrow_mut().insert((x, y), r));
r
}
pub fn singleton_companions(n: u128) -> Vec<u128> {
assert!(n < 128, "coin bitmask positions must be < 128");
(0..n).map(|i| 1u128 << i).collect()
}
pub fn turtles_companions(n: u128) -> Vec<u128> {
assert!(n < 128, "coin bitmask positions must be < 128");
let mut v = vec![0u128]; v.extend((0..n).map(|i| 1u128 << i));
v
}
pub fn grundy_1d<F: Fn(u128) -> Vec<u128>>(
companions: &F,
n: u128,
memo: &mut HashMap<u128, u128>,
) -> u128 {
let allowed = lower_mask(n);
if let Some(&v) = memo.get(&n) {
return v;
}
let mut seen = HashSet::new();
for s in companions(n) {
assert!(
s & !allowed == 0,
"companion mask contains a coin that is not strictly lower than n"
);
let mut acc = 0u128;
let mut ss = s;
while ss != 0 {
let i = ss.trailing_zeros() as u128;
ss &= ss - 1;
acc ^= grundy_1d(companions, i, memo);
}
seen.insert(acc);
}
let g = mex(seen.iter().copied());
memo.insert(n, g);
g
}
pub fn tartan_grundy<A, B>(
comp_a: &A,
comp_b: &B,
x: u128,
y: u128,
memo: &mut HashMap<(u128, u128), u128>,
) -> u128
where
A: Fn(u128) -> Vec<u128>,
B: Fn(u128) -> Vec<u128>,
{
assert!(x < 128 && y < 128, "tartan bitmask positions must be < 128");
let allowed_x = lower_mask(x);
let allowed_y = lower_mask(y);
if let Some(&v) = memo.get(&(x, y)) {
return v;
}
let mut seen = HashSet::new();
for ta in comp_a(x) {
assert!(
ta & !allowed_x == 0,
"row companion mask contains a coin that is not strictly lower than x"
);
let acells = ta | (1u128 << x); for tb in comp_b(y) {
assert!(
tb & !allowed_y == 0,
"column companion mask contains a coin that is not strictly lower than y"
);
let bcells = tb | (1u128 << y); let mut acc = 0u128;
let mut aa = acells;
while aa != 0 {
let a = aa.trailing_zeros() as u128;
aa &= aa - 1;
let mut bb = bcells;
while bb != 0 {
let b = bb.trailing_zeros() as u128;
bb &= bb - 1;
if a == x && b == y {
continue;
}
acc ^= tartan_grundy(comp_a, comp_b, a, b, memo);
}
}
seen.insert(acc);
}
}
let g = mex(seen.iter().copied());
memo.insert((x, y), g);
g
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::nim_mul;
#[test]
fn game_definition_equals_algebraic_nim_mul() {
for x in 0u128..48 {
for y in 0u128..48 {
assert_eq!(nim_mul_mex(x, y), nim_mul(x, y), "mismatch at ({x}, {y})");
}
}
}
#[test]
fn turning_corners_realizes_the_field_table() {
assert_eq!(nim_mul_mex(2, 2), 3);
assert_eq!(nim_mul_mex(2, 3), 1);
assert_eq!(nim_mul_mex(4, 4), 6);
assert_eq!(nim_mul_mex(2, 4), 8); }
#[test]
fn one_d_grundy_sequences() {
let mut m = HashMap::new();
for n in 0..10 {
assert_eq!(grundy_1d(&singleton_companions, n, &mut m), n); }
let mut m2 = HashMap::new();
for n in 0..10 {
assert_eq!(grundy_1d(&turtles_companions, n, &mut m2), n + 1); }
}
#[test]
#[should_panic(expected = "positions must be < 128")]
fn companion_bitmasks_are_bounded_by_u128_width() {
let _ = singleton_companions(128);
}
#[test]
#[should_panic(expected = "not strictly lower than n")]
fn custom_companion_masks_must_use_lower_coins() {
let bad = |n| vec![1u128 << n];
let mut memo = HashMap::new();
let _ = grundy_1d(&bad, 3, &mut memo);
}
#[test]
fn tartan_square_of_singleton_game_is_turning_corners() {
let mut tm = HashMap::new();
for x in 0u128..6 {
for y in 0u128..6 {
assert_eq!(
tartan_grundy(&singleton_companions, &singleton_companions, x, y, &mut tm),
nim_mul_mex(x, y),
"tartan ≠ Turning Corners at ({x},{y})"
);
}
}
}
#[test]
fn tartan_product_theorem() {
fn check<A: Fn(u128) -> Vec<u128>, B: Fn(u128) -> Vec<u128>>(ga: &A, gb: &B) {
let (mut ma, mut mb, mut tm) = (HashMap::new(), HashMap::new(), HashMap::new());
for x in 0u128..5 {
for y in 0u128..5 {
let direct = tartan_grundy(ga, gb, x, y, &mut tm);
let factored = nim_mul(grundy_1d(ga, x, &mut ma), grundy_1d(gb, y, &mut mb));
assert_eq!(direct, factored, "Tartan theorem failed at ({x},{y})");
}
}
}
check(&singleton_companions, &singleton_companions);
check(&singleton_companions, &turtles_companions);
check(&turtles_companions, &singleton_companions);
check(&turtles_companions, &turtles_companions);
}
}