use crate::games::Game;
use crate::scalar::Ordinal;
use std::cmp::Ordering;
use std::fmt;
#[derive(Clone, Debug, PartialEq)]
pub struct NimberGame {
grundy: Ordinal,
}
impl NimberGame {
pub fn from_ordinal(o: &Ordinal) -> NimberGame {
NimberGame { grundy: o.clone() }
}
pub fn nim_heap(n: u128) -> NimberGame {
NimberGame {
grundy: Ordinal::from_u128(n),
}
}
pub fn grundy(&self) -> &Ordinal {
&self.grundy
}
pub fn add(&self, other: &NimberGame) -> NimberGame {
NimberGame {
grundy: self.grundy.nim_add(&other.grundy),
}
}
pub fn neg(&self) -> NimberGame {
self.clone()
}
pub fn turning_corners(&self, other: &NimberGame) -> Option<NimberGame> {
self.grundy
.nim_mul(&other.grundy)
.map(|grundy| NimberGame { grundy })
}
#[allow(clippy::should_implement_trait)]
pub fn cmp(&self, other: &NimberGame) -> Ordering {
self.grundy.cmp(&other.grundy)
}
pub fn to_finite_game(&self) -> Option<Game> {
self.grundy.as_finite().map(Game::nim_heap)
}
}
impl fmt::Display for NimberGame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.grundy)
}
}
impl std::ops::Add for NimberGame {
type Output = NimberGame;
fn add(self, rhs: NimberGame) -> NimberGame {
NimberGame::add(&self, &rhs)
}
}
impl std::ops::Neg for NimberGame {
type Output = NimberGame;
fn neg(self) -> NimberGame {
NimberGame::neg(&self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transfinite_bridge() {
let w = Ordinal::omega();
let g = NimberGame::from_ordinal(&w);
assert_eq!(g.grundy(), &w);
assert!(g.to_finite_game().is_none());
assert!(g.add(&g).grundy().is_zero());
assert_eq!(g.neg(), g);
let one = NimberGame::nim_heap(1);
assert_eq!(g.add(&one).grundy(), &w.nim_add(&Ordinal::from_u128(1)));
assert_eq!(g.cmp(&NimberGame::nim_heap(1_000_000)), Ordering::Greater);
let fin = NimberGame::nim_heap(2);
assert!(fin.to_finite_game().unwrap().eq(&Game::nim_heap(2)));
}
#[test]
fn turning_corners_is_nim_multiplication() {
let two = NimberGame::nim_heap(2);
let three = NimberGame::nim_heap(3);
let prod = two.turning_corners(&three).unwrap();
assert_eq!(
prod.grundy().as_finite(),
Ordinal::from_u128(2)
.nim_mul(&Ordinal::from_u128(3))
.unwrap()
.as_finite()
);
let w = NimberGame::from_ordinal(&Ordinal::omega());
let w3 = w
.turning_corners(&w)
.and_then(|w2| w2.turning_corners(&w))
.unwrap();
assert_eq!(w3.grundy(), &Ordinal::from_u128(2));
let ww = NimberGame::from_ordinal(&Ordinal::omega_pow(Ordinal::omega()));
assert_eq!(
ww.turning_corners(&ww).unwrap().grundy(),
&Ordinal::omega_pow(Ordinal::monomial(Ordinal::from_u128(1), 2))
);
let www =
NimberGame::from_ordinal(&Ordinal::omega_pow(Ordinal::omega_pow(Ordinal::omega())));
assert!(www.turning_corners(&www).is_none());
}
#[test]
fn additive_operator_traits_delegate_to_nim_arithmetic() {
let two = NimberGame::nim_heap(2);
let three = NimberGame::nim_heap(3);
assert_eq!(
(two.clone() + three.clone()).grundy(),
&Ordinal::from_u128(2).nim_add(&Ordinal::from_u128(3))
);
assert_eq!(-two.clone(), two);
assert_eq!(two.turning_corners(&three), Some(NimberGame::nim_heap(1)));
}
#[test]
fn mirrors_the_number_game_on_the_shared_cnf() {
let w = NimberGame::from_ordinal(&Ordinal::omega());
assert!(w.add(&w).grundy().is_zero(), "⋆ω + ⋆ω = 0 (XOR, not ω·2)");
let wp1 = w.add(&NimberGame::nim_heap(1));
assert_eq!(format!("{:?}", wp1.grundy()), "*(ω + 1)");
}
}