Skip to main content

competitive/algorithm/game_analyzer/
value.rs

1use super::{Dyadic, PartisanOutcome, Player, game_mex};
2use std::{
3    cmp::Ordering,
4    collections::HashMap,
5    fmt,
6    hash::{DefaultHasher, Hash, Hasher},
7    iter::Sum,
8    ops::{Add, Neg, Sub},
9    rc::Rc,
10};
11
12/// A canonical finite normal-play game. Numbers use `Dyadic<i64>`.
13/// `PartialOrd` is the game order: `None` means fuzzy, not a numeric approximation.
14///
15/// ```
16/// use competitive::algorithm::GameValue;
17/// let zero = GameValue::from(0);
18/// let star = GameValue::nimber(1);
19/// let up = GameValue::from_options([zero.clone()], [star.clone()]);
20/// assert!(up > zero);
21/// assert_eq!(star.partial_cmp(&zero), None);
22/// assert_eq!(star.clone() + star, zero);
23/// ```
24#[derive(Clone)]
25pub struct GameValue(GameValueNode);
26
27#[derive(Clone)]
28enum GameValueNode {
29    NumberUpNimber(Dyadic<i64>, i64, u64),
30    Switches(Rc<GameValueSwitches>),
31    // A numeric translation shares the canonical options of a non-number.
32    Options(Dyadic<i64>, Rc<(Vec<GameValue>, Vec<GameValue>)>),
33}
34
35// Memo keys retain general nodes and compare compact values by their contents.
36impl PartialEq for GameValueNode {
37    fn eq(&self, rhs: &Self) -> bool {
38        match (self, rhs) {
39            (Self::NumberUpNimber(a, k, n), Self::NumberUpNimber(b, l, m)) => {
40                a == b && k == l && n == m
41            }
42            (Self::Switches(a), Self::Switches(b)) => {
43                Rc::ptr_eq(a, b)
44                    || a.number == b.number
45                        && a.up == b.up
46                        && a.nimber == b.nimber
47                        && a.temperatures[a.start..] == b.temperatures[b.start..]
48            }
49            (Self::Options(q, a), Self::Options(r, b)) => q == r && Rc::ptr_eq(a, b),
50            _ => false,
51        }
52    }
53}
54impl Eq for GameValueNode {}
55impl Hash for GameValueNode {
56    fn hash<H>(&self, state: &mut H)
57    where
58        H: Hasher,
59    {
60        match self {
61            Self::NumberUpNimber(q, k, n) => (q, k, n).hash(state),
62            Self::Switches(v) => (v.number, v.up, v.nimber, v.fingerprint).hash(state),
63            Self::Options(q, v) => {
64                Rc::as_ptr(v).hash(state);
65                if q.numerator() != 0 {
66                    q.hash(state);
67                }
68            }
69        }
70    }
71}
72
73#[derive(Clone)]
74struct GameValueSwitches {
75    number: Dyadic<i64>,
76    up: i64,
77    nimber: u64,
78    temperatures: Rc<Vec<Dyadic<i64>>>,
79    start: usize,
80    // XOR of the remaining temperatures' hashes; equality still checks the contents.
81    fingerprint: u64,
82}
83
84impl GameValue {
85    pub fn nimber(value: u64) -> Self {
86        Self::number_up_nimber(0.into(), 0, value)
87    }
88
89    /// Constructs `number + up * ↑ + *nimber` without expanding options.
90    pub fn number_up_nimber(number: Dyadic<i64>, up: i64, nimber: u64) -> Self {
91        Self(GameValueNode::NumberUpNimber(number, up, nimber))
92    }
93
94    pub fn as_number(&self) -> Option<Dyadic<i64>> {
95        match self.0 {
96            GameValueNode::NumberUpNimber(n, 0, 0) => Some(n),
97            _ => None,
98        }
99    }
100
101    pub fn as_nimber(&self) -> Option<u64> {
102        match self.0 {
103            GameValueNode::NumberUpNimber(q, 0, n) if q.numerator() == 0 => Some(n),
104            _ => None,
105        }
106    }
107
108    /// Returns `(q, n)` when the value is `q + *n`.
109    pub fn as_number_nimber(&self) -> Option<(Dyadic<i64>, u64)> {
110        match self.0 {
111            GameValueNode::NumberUpNimber(q, 0, n) => Some((q, n)),
112            _ => None,
113        }
114    }
115
116    /// Returns `(q, k, n)` when the value is `q + k↑ + *n`.
117    pub fn as_number_up_nimber(&self) -> Option<(Dyadic<i64>, i64, u64)> {
118        match self.0 {
119            GameValueNode::NumberUpNimber(q, k, n) => Some((q, k, n)),
120            _ => None,
121        }
122    }
123
124    /// Returns `(q, k, n, temperatures)` for `q + k↑ + *n + Σ{t|-t}`.
125    /// Temperatures are positive and strictly decreasing; the slice may be empty.
126    #[allow(clippy::type_complexity)] // Borrows the components without a separate public type.
127    pub fn as_switch_sum(&self) -> Option<(Dyadic<i64>, i64, u64, &[Dyadic<i64>])> {
128        match &self.0 {
129            GameValueNode::NumberUpNimber(q, k, n) => Some((*q, *k, *n, &[])),
130            GameValueNode::Switches(value) => Some((
131                value.number,
132                value.up,
133                value.nimber,
134                &value.temperatures[value.start..],
135            )),
136            _ => None,
137        }
138    }
139
140    pub fn outcome(&self) -> PartisanOutcome {
141        match self.partial_cmp(&Self::from(0)) {
142            Some(Ordering::Greater) => PartisanOutcome::Left,
143            Some(Ordering::Less) => PartisanOutcome::Right,
144            Some(Ordering::Equal) => PartisanOutcome::Previous,
145            None => PartisanOutcome::Next,
146        }
147    }
148
149    /// Whether the selected starting player can force a win.
150    pub fn wins(&self, player: Player) -> bool {
151        match player {
152            Player::Left => !self.leq(&Self::from(0)),
153            Player::Right => !Self::from(0).leq(self),
154        }
155    }
156
157    /// Removes dominated and reversible options.
158    pub fn from_options(
159        left: impl IntoIterator<Item = Self>,
160        right: impl IntoIterator<Item = Self>,
161    ) -> Self {
162        Self::from_options_memoized(
163            left.into_iter().collect(),
164            right.into_iter().collect(),
165            &mut HashMap::new(),
166        )
167    }
168
169    pub fn options(&self, player: Player) -> impl Iterator<Item = Self> + '_ {
170        let mut offset = Dyadic::from(0);
171        let (single, nimbers, general) = match &self.0 {
172            GameValueNode::NumberUpNimber(n, 0, 0) => {
173                let option = if n.exponent() == 0 {
174                    match player {
175                        Player::Left if n.numerator() > 0 => Some(Dyadic::from(n.numerator() - 1)),
176                        Player::Right if n.numerator() < 0 => Some(Dyadic::from(n.numerator() + 1)),
177                        _ => None,
178                    }
179                } else {
180                    let numerator = (n.numerator() >> 1) + i64::from(player == Player::Right);
181                    Some(Dyadic::new(numerator, n.exponent() - 1))
182                };
183                (option.map(Self::from), None, &[][..])
184            }
185            GameValueNode::NumberUpNimber(q, 0, n) => (None, Some((*q, *n)), &[][..]),
186            GameValueNode::NumberUpNimber(q, k, 1)
187                if matches!((*k, player), (1, Player::Left) | (-1, Player::Right)) =>
188            {
189                (None, Some((*q, 2)), &[][..])
190            }
191            GameValueNode::NumberUpNimber(q, k, n) => {
192                let sign = if *k > 0 { 1 } else { -1 };
193                let moves_to_number = (*k > 0) == (player == Player::Left);
194                let option = if moves_to_number {
195                    Self::from(*q)
196                } else {
197                    Self::number_up_nimber(*q, k - sign, n ^ 1)
198                };
199                (Some(option), None, &[][..])
200            }
201            GameValueNode::Switches(value) => {
202                let temperatures = &value.temperatures[value.start..];
203                let t = if player == Player::Left {
204                    temperatures[0]
205                } else {
206                    -temperatures[0]
207                };
208                let option = if temperatures.len() == 1 {
209                    Self::number_up_nimber(value.number + t, value.up, value.nimber)
210                } else {
211                    Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
212                        number: value.number + t,
213                        start: value.start + 1,
214                        fingerprint: value.fingerprint ^ Self::temperature_hash(temperatures[0]),
215                        ..(**value).clone()
216                    })))
217                };
218                (Some(option), None, &[][..])
219            }
220            GameValueNode::Options(q, options) => {
221                offset = *q;
222                let (left, right) = &**options;
223                let moves = if player == Player::Left { left } else { right };
224                (None, None, moves.as_slice())
225            }
226        };
227        single
228            .into_iter()
229            .chain(
230                nimbers
231                    .into_iter()
232                    .flat_map(|(q, n)| (0..n).map(move |m| Self::number_up_nimber(q, 0, m))),
233            )
234            .chain(general.iter().map(move |g| g.translated(offset)))
235    }
236
237    fn from_options_memoized(
238        mut left: Vec<Self>,
239        mut right: Vec<Self>,
240        cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241    ) -> Self {
242        loop {
243            if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244                let l = left.iter().filter_map(Self::as_number).max();
245                let r = right.iter().filter_map(Self::as_number).min();
246                if let Some(n) = Dyadic::simplest_between(l, r) {
247                    return n.into();
248                }
249                let (l, r) = (l.unwrap(), r.unwrap());
250                if l == r {
251                    return Self::number_up_nimber(l, 0, 1);
252                }
253                let (sum, difference) = (l + r, l - r);
254                let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255                return Self::from_switches(
256                    Dyadic::new(sum.numerator(), sum.exponent() + 1),
257                    0,
258                    0,
259                    vec![temperature],
260                    Self::temperature_hash(temperature),
261                );
262            }
263            if !right.is_empty()
264                && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265            {
266                let nimber = |g: &Self| {
267                    g.as_number_nimber()
268                        .filter(|&(q, _)| q == number)
269                        .map(|(_, n)| n)
270                };
271                let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272                let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273                if let (Some(l), Some(r)) = (l, r) {
274                    let value = game_mex(l);
275                    if value == game_mex(r) {
276                        return Self::number_up_nimber(number, 0, value);
277                    }
278                }
279            }
280            for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281                if replies.len() != 1 {
282                    continue;
283                }
284                if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285                    if moves.len() == 1
286                        && moves[0].as_number() == Some(q)
287                        && (k == 0 || k.signum() == sign)
288                    {
289                        return Self::number_up_nimber(q, k + sign, n ^ 1);
290                    }
291                    if k == 0 && n == 0 && moves.len() == 2 {
292                        let a = moves[0].as_number_nimber();
293                        let b = moves[1].as_number_nimber();
294                        if (a == Some((q, 0)) && b == Some((q, 1)))
295                            || (a == Some((q, 1)) && b == Some((q, 0)))
296                        {
297                            return Self::number_up_nimber(q, sign, 1);
298                        }
299                    }
300                }
301            }
302            if left.len() == 1
303                && right.len() == 1
304                && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305                    (left[0].as_switch_sum(), right[0].as_switch_sum())
306                && k == l
307                && n == m
308                && ts == us
309                && a > b
310            {
311                let (sum, difference) = (a + b, a - b);
312                let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313                if ts.first().is_none_or(|&t| temperature > t) {
314                    return Self::from_switches(
315                        Dyadic::new(sum.numerator(), sum.exponent() + 1),
316                        k,
317                        n,
318                        std::iter::once(temperature)
319                            .chain(ts.iter().copied())
320                            .collect(),
321                        Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322                    );
323                }
324            }
325            let count = left.len() + right.len();
326            Self::remove_dominated(&mut left, Player::Left, cache);
327            Self::remove_dominated(&mut right, Player::Right, cache);
328            if count != left.len() + right.len() {
329                continue;
330            }
331            let options = Rc::new((left, right));
332            let game = Self(GameValueNode::Options(0.into(), options.clone()));
333            // Bypasses preserve the game, so all checks use the same candidate.
334            let mut reversible = Vec::new();
335            for player in [Player::Left, Player::Right] {
336                let moves = match player {
337                    Player::Left => &options.0,
338                    Player::Right => &options.1,
339                };
340                for (i, g) in moves.iter().enumerate() {
341                    if let Some((q, n)) = g.as_number_nimber()
342                        && n > 1
343                    {
344                        let number = Self::from(q);
345                        let (lower, upper) = match player {
346                            Player::Left => (&game, &number),
347                            Player::Right => (&number, &game),
348                        };
349                        // A strict inequality against q excludes every reply q + *m.
350                        if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351                            continue;
352                        }
353                    }
354                    if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355                        Player::Left => r.leq_memoized(&game, cache),
356                        Player::Right => game.leq_memoized(r, cache),
357                    }) {
358                        reversible.push((player, i, reply));
359                    }
360                }
361            }
362            if reversible.is_empty() {
363                return game;
364            }
365            drop(game);
366            (left, right) = Rc::unwrap_or_clone(options);
367            for (player, i, reply) in reversible.into_iter().rev() {
368                let moves = if player == Player::Left {
369                    &mut left
370                } else {
371                    &mut right
372                };
373                moves.remove(i);
374                moves.extend(reply.options(player));
375            }
376        }
377    }
378
379    fn leq(&self, rhs: &Self) -> bool {
380        self.leq_memoized(rhs, &mut HashMap::new())
381    }
382
383    fn leq_memoized(
384        &self,
385        rhs: &Self,
386        cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387    ) -> bool {
388        if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389            (self.as_switch_sum(), rhs.as_switch_sum())
390        {
391            // Both players take the hottest remaining switch. At zero, the tail
392            // decides the winner with the turn reversed once per switch.
393            let mut number = a;
394            let mut odd = false;
395            for t in Self::switch_difference(ts, us) {
396                // The unplayed alternating tail has the sign of the next term.
397                if !odd && number >= b || odd && number <= b {
398                    return odd;
399                }
400                number = if odd { number - t } else { number + t };
401                odd = !odd;
402            }
403            return match number.cmp(&b) {
404                Ordering::Less => true,
405                Ordering::Greater => false,
406                Ordering::Equal => {
407                    let up = if odd { l - k } else { k - l };
408                    let nimber = n ^ m;
409                    let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410                    wins == odd
411                }
412            };
413        }
414        if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415            && Rc::ptr_eq(g, h)
416        {
417            return a <= b;
418        }
419        // Normalize only memo keys: from_options also compares unreduced candidates.
420        let offset = -self.number_offset();
421        let left = self.translated(offset).0;
422        let right = rhs.translated(offset).0;
423        if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424            return value;
425        }
426        // Try explicit options before expanding a compact value.
427        let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428        if self.as_switch_sum().is_some() {
429            sides.swap(0, 1);
430        }
431        let value = sides.into_iter().all(|(game, other, player)| {
432            let mut preferred = |a: &Self, b: &Self| match player {
433                Player::Left => b.leq_memoized(a, cache),
434                Player::Right => a.leq_memoized(b, cache),
435            };
436            if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437                && n != 0
438            {
439                let number = Self::from(q);
440                !preferred(&number, other)
441                    && (preferred(other, &number)
442                        || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443            } else {
444                !game.options(player).any(|g| preferred(&g, other))
445            }
446        });
447        cache.entry(left).or_default().insert(right, value);
448        value
449    }
450
451    fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452        match &self.0 {
453            GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454            GameValueNode::Switches(value) => {
455                Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456                    number: -value.number,
457                    up: -value.up,
458                    ..(**value).clone()
459                })))
460            }
461            GameValueNode::Options(q, options) => {
462                let key = Rc::as_ptr(options) as usize;
463                if let Some(value) = cache.get(&key) {
464                    return value.translated(-*q);
465                }
466                let (left, right) = &**options;
467                let value = Self(GameValueNode::Options(
468                    0.into(),
469                    Rc::new((
470                        right.iter().map(|g| g.negated(cache)).collect(),
471                        left.iter().map(|g| g.negated(cache)).collect(),
472                    )),
473                ));
474                cache.insert(key, value.clone());
475                value.translated(-*q)
476            }
477        }
478    }
479
480    fn sum(
481        &self,
482        rhs: &Self,
483        cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484        comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485    ) -> Self {
486        if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487            (&self.0, &rhs.0)
488        {
489            return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490        }
491        if let Some(q) = self.as_number() {
492            return rhs.translated(q);
493        }
494        if let Some(q) = rhs.as_number() {
495            return self.translated(q);
496        }
497        if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498            (self.as_switch_sum(), rhs.as_switch_sum())
499        {
500            return Self::from_switches(
501                a + b,
502                k + l,
503                n ^ m,
504                Self::switch_difference(ts, us).collect(),
505                self.switch_hash() ^ rhs.switch_hash(),
506            );
507        }
508        // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509        let (a, b) = (self.number_offset(), rhs.number_offset());
510        if a.numerator() != 0 || b.numerator() != 0 {
511            return self
512                .translated(-a)
513                .sum(&rhs.translated(-b), cache, comparisons)
514                .translated(a + b);
515        }
516        let mut key = (self.0.clone(), rhs.0.clone());
517        let swap = match (&key.0, &key.1) {
518            (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519                Rc::as_ptr(a) > Rc::as_ptr(b)
520            }
521            (GameValueNode::Options(..), _) => true,
522            _ => false,
523        };
524        if swap {
525            std::mem::swap(&mut key.0, &mut key.1);
526        }
527        if let Some(value) = cache.get(&key) {
528            return value.clone();
529        }
530        let mut options = |player| {
531            let mut moves = Vec::new();
532            for g in self.options(player) {
533                moves.push(g.sum(rhs, cache, comparisons));
534            }
535            for g in rhs.options(player) {
536                moves.push(self.sum(&g, cache, comparisons));
537            }
538            moves
539        };
540        let value =
541            Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542        cache.insert(key, value.clone());
543        value
544    }
545
546    fn number_offset(&self) -> Dyadic<i64> {
547        match &self.0 {
548            GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549            GameValueNode::Switches(v) => v.number,
550        }
551    }
552
553    fn translated(&self, number: Dyadic<i64>) -> Self {
554        if number.numerator() == 0 {
555            return self.clone();
556        }
557        match &self.0 {
558            GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559            GameValueNode::Switches(v) => {
560                Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561                    number: v.number + number,
562                    ..(**v).clone()
563                })))
564            }
565            GameValueNode::Options(q, options) => {
566                Self(GameValueNode::Options(*q + number, options.clone()))
567            }
568        }
569    }
570
571    fn temperature_hash(t: Dyadic<i64>) -> u64 {
572        let mut hasher = DefaultHasher::new();
573        t.hash(&mut hasher);
574        hasher.finish()
575    }
576
577    fn switch_hash(&self) -> u64 {
578        match &self.0 {
579            GameValueNode::NumberUpNimber(..) => 0,
580            GameValueNode::Switches(v) => v.fingerprint,
581            GameValueNode::Options(..) => unreachable!(),
582        }
583    }
584
585    fn from_switches(
586        q: Dyadic<i64>,
587        k: i64,
588        n: u64,
589        temperatures: Vec<Dyadic<i64>>,
590        fingerprint: u64,
591    ) -> Self {
592        if temperatures.is_empty() {
593            Self::number_up_nimber(q, k, n)
594        } else {
595            Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596                number: q,
597                up: k,
598                nimber: n,
599                fingerprint,
600                temperatures: Rc::new(temperatures),
601                start: 0,
602            })))
603        }
604    }
605
606    fn switch_difference<'a>(
607        mut left: &'a [Dyadic<i64>],
608        mut right: &'a [Dyadic<i64>],
609    ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610        std::iter::from_fn(move || {
611            while let (Some(a), Some(b)) = (left.first(), right.first()) {
612                if a != b {
613                    break;
614                }
615                left = &left[1..];
616                right = &right[1..];
617            }
618            if left.first() < right.first() {
619                std::mem::swap(&mut left, &mut right);
620            }
621            let (&value, tail) = left.split_first()?;
622            left = tail;
623            Some(value)
624        })
625    }
626
627    fn remove_dominated(
628        options: &mut Vec<Self>,
629        player: Player,
630        cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631    ) {
632        let mut retained = vec![true; options.len()];
633        for i in 0..options.len() {
634            let dominated = (0..options.len()).any(|j| {
635                i != j
636                    && retained[j]
637                    && match player {
638                        Player::Left => options[i].leq_memoized(&options[j], cache),
639                        Player::Right => options[j].leq_memoized(&options[i], cache),
640                    }
641            });
642            retained[i] = !dominated;
643        }
644        let mut retained = retained.into_iter();
645        options.retain(|_| retained.next().unwrap());
646    }
647}
648
649impl From<i64> for GameValue {
650    fn from(value: i64) -> Self {
651        Dyadic::from(value).into()
652    }
653}
654impl From<Dyadic<i64>> for GameValue {
655    fn from(value: Dyadic<i64>) -> Self {
656        Self::number_up_nimber(value, 0, 0)
657    }
658}
659impl Neg for GameValue {
660    type Output = Self;
661    fn neg(self) -> Self {
662        self.negated(&mut HashMap::new())
663    }
664}
665impl Add for GameValue {
666    type Output = Self;
667    fn add(self, rhs: Self) -> Self {
668        self.sum(&rhs, &mut HashMap::new(), &mut HashMap::new())
669    }
670}
671impl Sub for GameValue {
672    type Output = Self;
673    fn sub(self, rhs: Self) -> Self {
674        if self.0 == rhs.0 {
675            Self::from(0)
676        } else {
677            self + -rhs
678        }
679    }
680}
681impl Sum for GameValue {
682    fn sum<I>(iter: I) -> Self
683    where
684        I: Iterator<Item = Self>,
685    {
686        let (mut number, mut up, mut nimber) = (Dyadic::from(0), 0, 0);
687        let mut temperatures = Vec::new();
688        let mut fingerprint = 0;
689        let mut general = Self::from(0);
690        for value in iter {
691            if let Some((q, k, n, ts)) = value.as_switch_sum() {
692                number = number + q;
693                up += k;
694                nimber ^= n;
695                temperatures.extend_from_slice(ts);
696                fingerprint ^= value.switch_hash();
697            } else {
698                general = general + value;
699            }
700        }
701        temperatures.sort_unstable_by(|a, b| b.cmp(a));
702        let mut retained = 0;
703        for i in 0..temperatures.len() {
704            if retained > 0 && temperatures[retained - 1] == temperatures[i] {
705                retained -= 1;
706            } else {
707                temperatures[retained] = temperatures[i];
708                retained += 1;
709            }
710        }
711        temperatures.truncate(retained);
712        let value = Self::from_switches(number, up, nimber, temperatures, fingerprint);
713        if general.as_nimber() == Some(0) {
714            value
715        } else {
716            value + general
717        }
718    }
719}
720impl PartialEq for GameValue {
721    fn eq(&self, rhs: &Self) -> bool {
722        match (&self.0, &rhs.0) {
723            (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) => {
724                return a == b && k == l && n == m;
725            }
726            (GameValueNode::Switches(_), GameValueNode::Switches(_)) => {
727                return self.0 == rhs.0;
728            }
729            _ => {}
730        }
731        let mut cache = HashMap::new();
732        self.leq_memoized(rhs, &mut cache) && rhs.leq_memoized(self, &mut cache)
733    }
734}
735impl Eq for GameValue {}
736impl PartialOrd for GameValue {
737    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
738        let mut cache = HashMap::new();
739        match (
740            self.leq_memoized(rhs, &mut cache),
741            rhs.leq_memoized(self, &mut cache),
742        ) {
743            (true, true) => Some(Ordering::Equal),
744            (true, false) => Some(Ordering::Less),
745            (false, true) => Some(Ordering::Greater),
746            (false, false) => None,
747        }
748    }
749}
750impl fmt::Display for GameValue {
751    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752        if let Some((q, k, n, temperatures)) = self.as_switch_sum() {
753            let mut separator = "";
754            if q.numerator() != 0 || k == 0 && n == 0 && temperatures.is_empty() {
755                write!(f, "{q}")?;
756                separator = "+";
757            }
758            if k != 0 {
759                f.write_str(separator)?;
760                if k.unsigned_abs() != 1 {
761                    write!(f, "{}", k.unsigned_abs())?;
762                }
763                f.write_str(if k > 0 { "↑" } else { "↓" })?;
764                separator = "+";
765            }
766            if n != 0 {
767                write!(f, "{separator}*")?;
768                if n != 1 {
769                    write!(f, "{n}")?;
770                }
771                separator = "+";
772            }
773            for t in temperatures {
774                write!(f, "{separator}{{{t}|{}}}", -*t)?;
775                separator = "+";
776            }
777            return Ok(());
778        }
779        f.write_str("{")?;
780        for (separator, player) in [("", Player::Left), ("|", Player::Right)] {
781            f.write_str(separator)?;
782            for (i, g) in self.options(player).enumerate() {
783                if i > 0 {
784                    f.write_str(",")?;
785                }
786                write!(f, "{g}")?;
787            }
788        }
789        f.write_str("}")
790    }
791}
792impl fmt::Debug for GameValue {
793    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
794        fmt::Display::fmt(self, f)
795    }
796}
797
798#[cfg(test)]
799mod tests {
800    use super::*;
801    use crate::tools::{
802        Xorshift,
803        testutil::{integer_boundary_values, sequence_cases},
804    };
805
806    #[test]
807    fn number_nimbers() {
808        let mut rng = Xorshift::default();
809        let nimbers: Vec<_> = integer_boundary_values!(u64)
810            .into_iter()
811            .chain(rng.random_iter(..).take(1024))
812            .collect();
813        for n in nimbers {
814            let random = Dyadic::new(rng.random(-8192..=8192), rng.random(0..=8));
815            for q in [
816                0.into(),
817                (-8192).into(),
818                8192.into(),
819                Dyadic::new(-1, 1),
820                Dyadic::new(1, 1),
821                random,
822            ] {
823                let value = GameValue::from(q) + GameValue::nimber(n);
824                assert_eq!(
825                    GameValue::from_options([q.into()], [q.into()]).as_number_nimber(),
826                    Some((q, 1))
827                );
828                assert_eq!(value.as_number_nimber(), Some((q, n)));
829                assert_eq!(value.as_number(), (n == 0).then_some(q));
830                assert_eq!(value.as_nimber(), (q.numerator() == 0).then_some(n));
831                let prefix = if n != 0 && q.numerator() != 0 {
832                    format!("{q}+")
833                } else {
834                    String::new()
835                };
836                let suffix = match n {
837                    0 => q.to_string(),
838                    1 => "*".to_owned(),
839                    _ => format!("*{n}"),
840                };
841                assert_eq!(value.to_string(), prefix + &suffix);
842                assert_eq!((-value.clone()).as_number_nimber(), Some((-q, n)));
843                assert_eq!(value.clone() - value.clone(), 0.into());
844                let m = rng.rand64();
845                let r = Dyadic::new(rng.random(-8192..=8192), rng.random(0..=8));
846                let other = GameValue::from(r) + GameValue::nimber(m);
847                assert_eq!(
848                    (value.clone() + other.clone()).as_number_nimber(),
849                    Some((q + r, n ^ m))
850                );
851                assert_eq!(
852                    (value.clone() - other).as_number_nimber(),
853                    Some((q - r, n ^ m))
854                );
855                if n != 0 {
856                    for player in [Player::Left, Player::Right] {
857                        assert!(
858                            value
859                                .options(player)
860                                .take(3)
861                                .enumerate()
862                                .all(|(m, g)| { g.as_number_nimber() == Some((q, m as u64)) })
863                        );
864                        assert_eq!(value.options(player).take(3).count(), n.min(3) as usize);
865                    }
866                }
867                for k in [-8192, -2, -1, 0, 1, 2, 8192, rng.random(-8192..=8192)] {
868                    let value = GameValue::number_up_nimber(q, k, n);
869                    let other = GameValue::number_up_nimber(r, -k, m);
870                    assert_eq!((-value.clone()).as_number_up_nimber(), Some((-q, -k, n)));
871                    assert_eq!(
872                        (value.clone() + other).as_number_nimber(),
873                        Some((q + r, n ^ m))
874                    );
875                    if k != 0 {
876                        assert_eq!(
877                            GameValue::from_options(
878                                value.options(Player::Left),
879                                value.options(Player::Right)
880                            )
881                            .as_number_up_nimber(),
882                            Some((q, k, n)),
883                        );
884                    }
885                }
886            }
887            if n != 0 {
888                let star = GameValue::nimber(n);
889                let switch = GameValue::from_options([1.into()], [(-1).into()]);
890                assert_eq!(star.partial_cmp(&switch), None);
891                assert_eq!(switch.partial_cmp(&star), None);
892                let up = GameValue::from_options([0.into()], [star]);
893                assert!(up > GameValue::from(0));
894                assert_eq!(up.clone() - up, 0.into());
895            }
896        }
897    }
898
899    #[test]
900    fn order_and_sum_against_play() {
901        let mut rng = Xorshift::default();
902        let mut games = vec![
903            (vec![], vec![]),
904            (vec![0], vec![]),
905            (vec![], vec![0]),
906            (vec![0], vec![0]),
907        ];
908        // All birthday-two option sets, followed by sampled larger DAGs.
909        for l in 0..16 {
910            for r in 0..16 {
911                games.push((
912                    (0..4).filter(|&i| l >> i & 1 != 0).collect(),
913                    (0..4).filter(|&i| r >> i & 1 != 0).collect(),
914                ));
915            }
916        }
917        for n in games.len()..games.len() + 32 {
918            games.push((
919                (0..3).map(|_| rng.random(0..n)).collect(),
920                (0..3).map(|_| rng.random(0..n)).collect(),
921            ));
922        }
923        // Integers in [-2, 2], then their midpoints, each with a three-bit Nim heap.
924        let mut numbers = vec![(0i64, Vec::new(), Vec::new())];
925        let mut indices = HashMap::from([(0, 0)]);
926        for step in [2, 1] {
927            for q in (step..=4).step_by(step as usize).flat_map(|q| [-q, q]) {
928                if indices.contains_key(&q) {
929                    continue;
930                }
931                let left = (step == 1 || q > 0)
932                    .then(|| indices[&(q - step)])
933                    .into_iter()
934                    .collect();
935                let right = (step == 1 || q < 0)
936                    .then(|| indices[&(q + step)])
937                    .into_iter()
938                    .collect();
939                indices.insert(q, numbers.len());
940                numbers.push((q, left, right));
941            }
942        }
943        let start = games.len();
944        let mut compact = Vec::new();
945        for (i, &(q, ref left, ref right)) in numbers.iter().enumerate() {
946            for n in 0..8 {
947                games.push((
948                    left.iter()
949                        .map(|j| start + j * 8 + n)
950                        .chain((0..n).map(|m| start + i * 8 + m))
951                        .collect(),
952                    right
953                        .iter()
954                        .map(|j| start + j * 8 + n)
955                        .chain((0..n).map(|m| start + i * 8 + m))
956                        .collect(),
957                ));
958                compact.push((games.len() - 1, (Dyadic::new(q, 1), n as u64)));
959            }
960        }
961        let symmetric = games.len();
962        for (i, &(q, _, _)) in numbers.iter().enumerate() {
963            for mask in 1..16 {
964                let left: Vec<_> = (0..4)
965                    .filter(|n| mask >> n & 1 != 0)
966                    .flat_map(|n| [start + i * 8 + n; 2])
967                    .collect();
968                games.push((left.clone(), left.into_iter().rev().collect()));
969                let mex = (0..).find(|n| mask >> n & 1 == 0).unwrap();
970                compact.push((games.len() - 1, (Dyadic::new(q, 1), mex)));
971            }
972        }
973        for i in 0..numbers.len() {
974            for n in 0..8 {
975                for m in 0..8 {
976                    games.push((vec![start + i * 8 + n], vec![start + i * 8 + m]));
977                }
978            }
979        }
980        let up = games.len();
981        games.push((vec![0], vec![3]));
982        let down = games.len();
983        games.push((vec![3], vec![0]));
984        let inner = games.len();
985        games.push((vec![0], vec![2]));
986        let tiny = games.len();
987        games.push((vec![0], vec![inner]));
988        let inner = games.len();
989        games.push((vec![1], vec![0]));
990        let miny = games.len();
991        games.push((vec![inner], vec![0]));
992        let products_start = games.len();
993        let mut components = vec![0, 1, 2, 3, up, down, start + 2];
994        let mut switches = vec![0];
995        // Half-integer switches and their boundary numbers, with both signs of up.
996        for t in 1..=3 {
997            let a = start + indices[&t] * 8;
998            let b = start + indices[&-t] * 8;
999            let switch = games.len();
1000            games.push((vec![a], vec![b]));
1001            components.extend([a, b, switch]);
1002            switches.push(switch);
1003        }
1004        let mut states: Vec<Vec<_>> = sequence_cases(&mut rng, 0..=components.len() - 1, 0..=5)
1005            .map(|s| s.into_iter().map(|i| components[i]).collect())
1006            .collect();
1007        // At either numeric boundary, the up/nimber tail and turn parity decide.
1008        for (a, &first) in switches.iter().enumerate() {
1009            for (b, &second) in switches[..=a].iter().enumerate() {
1010                for q in [a as i64 - b as i64, b as i64 - a as i64] {
1011                    for u in [0, up, down] {
1012                        for n in [0, 3] {
1013                            states.push(vec![start + indices[&q] * 8, u, n, first, second]);
1014                        }
1015                    }
1016                }
1017            }
1018        }
1019        for &component in &components {
1020            let parts = [tiny, component, miny, component, tiny];
1021            states.extend((3..=5).map(|len| parts[..len].to_vec()));
1022        }
1023        // Translate each sampled DAG and Tiny/Miny by every existing half-integer.
1024        states.extend(
1025            (260..start)
1026                .chain([tiny, miny])
1027                .flat_map(|g| (0..numbers.len()).map(move |i| vec![g, start + i * 8])),
1028        );
1029        let switch_products_start = states.len();
1030        states.extend(
1031            (0usize..1 << (switches.len() - 1))
1032                .filter(|mask| mask.count_ones() > 1)
1033                .map(|mask| {
1034                    switches[1..]
1035                        .iter()
1036                        .enumerate()
1037                        .filter_map(|(i, &s)| (mask >> i & 1 != 0).then_some(s))
1038                        .collect()
1039                }),
1040        );
1041        let mut products = Vec::new();
1042        let mut product_cache = HashMap::new();
1043        for parts in states {
1044            let mut ordered = parts.clone();
1045            ordered.sort_unstable();
1046            let root = ordered
1047                .iter()
1048                .fold(0, |a, &b| sum_options(&mut games, a, b, &mut product_cache));
1049            products.push((root, parts));
1050        }
1051        let mut values: Vec<GameValue> = Vec::new();
1052        for (left, right) in &games {
1053            values.push(GameValue::from_options(
1054                left.iter().map(|&i| values[i].clone()),
1055                right.iter().map(|&i| values[i].clone()),
1056            ));
1057        }
1058        for (i, expected) in compact {
1059            assert_eq!(values[i].as_number_nimber(), Some(expected));
1060        }
1061        assert!(values[tiny].as_switch_sum().is_none());
1062        for (root, parts) in &products {
1063            let sum: GameValue = parts.iter().map(|&i| values[i].clone()).sum();
1064            assert_eq!(sum, values[*root]);
1065            if parts.iter().all(|&i| values[i].as_switch_sum().is_some()) {
1066                assert!(sum.as_switch_sum().is_some());
1067                assert!(values[*root].as_switch_sum().is_some(), "{}", values[*root]);
1068            }
1069            let reversed: GameValue = parts.iter().rev().map(|&i| values[i].clone()).sum();
1070            assert_eq!(sum, reversed);
1071            let folded = parts
1072                .iter()
1073                .fold(GameValue::from(0), |s, &i| s + values[i].clone());
1074            assert_eq!(sum, folded);
1075            // Check arithmetic results with the same raw oracle and canonical-form checks.
1076            values[*root] = sum;
1077        }
1078        // Share identical raw move sets without using canonical game values.
1079        let mut oracle_games = Vec::new();
1080        let mut oracle_ids = Vec::with_capacity(games.len());
1081        let mut oracle_nodes = HashMap::new();
1082        for (left, right) in &games {
1083            let [left, right] = [left, right].map(|moves| {
1084                let mut moves: Vec<_> = moves.iter().map(|&i| oracle_ids[i]).collect();
1085                moves.sort_unstable();
1086                moves.dedup();
1087                moves
1088            });
1089            let id = *oracle_nodes
1090                .entry((left, right))
1091                .or_insert_with_key(|options| {
1092                    let id = oracle_games.len();
1093                    oracle_games.push(options.clone());
1094                    id
1095                });
1096            oracle_ids.push(id);
1097        }
1098        drop(oracle_nodes);
1099        let zero = GameValue::from(0);
1100        let mut differences = HashMap::new();
1101        let mut sums = HashMap::new();
1102        let mut roots: Vec<_> = (0..products_start)
1103            .chain(products.iter().map(|&(i, _)| i))
1104            .collect();
1105        // Sequence-wise arithmetic was checked above; each raw game needs one oracle check.
1106        roots.sort_unstable();
1107        roots.dedup();
1108        for i in roots {
1109            let g = &values[i];
1110            let (left, right) = &games[i];
1111            let candidate = GameValue(GameValueNode::Options(
1112                0.into(),
1113                Rc::new((
1114                    left.iter().map(|&j| values[j].clone()).collect(),
1115                    right.iter().map(|&j| values[j].clone()).collect(),
1116                )),
1117            ));
1118            let winning = [false, true].map(|right| {
1119                wins(
1120                    &oracle_games,
1121                    oracle_ids[i],
1122                    oracle_ids[0],
1123                    right,
1124                    false,
1125                    &mut sums,
1126                )
1127            });
1128            for (player, win) in [Player::Left, Player::Right].into_iter().zip(winning) {
1129                assert_eq!(g.wins(player), win);
1130                let options: Vec<_> = g.options(player).collect();
1131                for (j, option) in options.iter().enumerate() {
1132                    if let Some((q, k, n, ts)) = option.as_switch_sum() {
1133                        let rebuilt: GameValue =
1134                            std::iter::once(GameValue::number_up_nimber(q, k, n))
1135                                .chain(
1136                                    ts.iter().map(|&t| {
1137                                        GameValue::from_options([t.into()], [(-t).into()])
1138                                    }),
1139                                )
1140                                .sum();
1141                        let memo = HashMap::from([(option.0.clone(), ())]);
1142                        assert!(memo.contains_key(&rebuilt.0));
1143                    }
1144                    for other in options.iter().skip(j + 1) {
1145                        assert_eq!(option.partial_cmp(other), None, "dominated options in {g}");
1146                    }
1147                    for reply in option.options(player.opponent()) {
1148                        let order = reply.partial_cmp(g);
1149                        assert_ne!(order, Some(Ordering::Equal), "reversible option in {g}");
1150                        assert_ne!(
1151                            order,
1152                            Some(if player == Player::Left {
1153                                Ordering::Less
1154                            } else {
1155                                Ordering::Greater
1156                            }),
1157                            "reversible option in {g}"
1158                        );
1159                    }
1160                }
1161            }
1162            let outcome = match winning {
1163                [true, true] => PartisanOutcome::Next,
1164                [false, false] => PartisanOutcome::Previous,
1165                [true, false] => PartisanOutcome::Left,
1166                [false, true] => PartisanOutcome::Right,
1167            };
1168            assert_eq!(g.outcome(), outcome);
1169            assert_eq!(g.clone() - g.clone(), zero, "G-G for {i}: {g}");
1170            assert_eq!(g.clone() + -g.clone(), zero);
1171            assert_eq!(-(-g.clone()), *g);
1172            let mut partners: Vec<_> = if i < 260 {
1173                (0..260).collect()
1174            } else if i >= products_start {
1175                components
1176                    .iter()
1177                    .copied()
1178                    .chain([tiny])
1179                    .chain(products[switch_products_start..].iter().map(|&(i, _)| i))
1180                    .collect()
1181            } else if i >= start {
1182                (start..symmetric).collect()
1183            } else {
1184                (0..32).map(|_| rng.random(0..products_start)).collect()
1185            };
1186            if (260..start).contains(&i) {
1187                partners.push(i);
1188            }
1189            for j in partners {
1190                let expected = match (
1191                    wins(
1192                        &oracle_games,
1193                        oracle_ids[i],
1194                        oracle_ids[j],
1195                        false,
1196                        true,
1197                        &mut differences,
1198                    ),
1199                    wins(
1200                        &oracle_games,
1201                        oracle_ids[i],
1202                        oracle_ids[j],
1203                        true,
1204                        true,
1205                        &mut differences,
1206                    ),
1207                ) {
1208                    (false, false) => Some(Ordering::Equal),
1209                    (true, false) => Some(Ordering::Greater),
1210                    (false, true) => Some(Ordering::Less),
1211                    (true, true) => None,
1212                };
1213                assert_eq!(
1214                    g.partial_cmp(&values[j]),
1215                    expected,
1216                    "{i} vs {j}: {g}, {}",
1217                    values[j]
1218                );
1219                assert_eq!(
1220                    candidate.partial_cmp(&values[j]),
1221                    expected,
1222                    "raw options {i} vs {j}"
1223                );
1224                assert_eq!(g == &values[j], expected == Some(Ordering::Equal));
1225                if (i < 260 && j >= 4) || (symmetric..products_start).contains(&i) {
1226                    continue;
1227                }
1228                assert_eq!((g.clone() - values[j].clone()).partial_cmp(&zero), expected);
1229                let sum = g.clone() + values[j].clone();
1230                assert_eq!(sum, values[j].clone() + g.clone());
1231                let collected: GameValue = [g.clone(), values[j].clone()].into_iter().sum();
1232                assert_eq!(sum, collected);
1233                for player in [Player::Left, Player::Right] {
1234                    assert_eq!(
1235                        sum.wins(player),
1236                        wins(
1237                            &oracle_games,
1238                            oracle_ids[i],
1239                            oracle_ids[j],
1240                            player == Player::Right,
1241                            false,
1242                            &mut sums
1243                        ),
1244                        "{i} + {j}, {player:?}"
1245                    );
1246                }
1247            }
1248        }
1249    }
1250
1251    // Build raw disjunctive sums without simplifying options or using game values.
1252    fn sum_options(
1253        games: &mut Vec<(Vec<usize>, Vec<usize>)>,
1254        a: usize,
1255        b: usize,
1256        cache: &mut HashMap<(usize, usize), usize>,
1257    ) -> usize {
1258        if a == 0 {
1259            return b;
1260        }
1261        if b == 0 {
1262            return a;
1263        }
1264        let key = (a.min(b), a.max(b));
1265        if let Some(&i) = cache.get(&key) {
1266            return i;
1267        }
1268        let (al, ar) = games[a].clone();
1269        let (bl, br) = games[b].clone();
1270        let mut options = [Vec::new(), Vec::new()];
1271        for (moves, (left, right)) in options.iter_mut().zip([(al, bl), (ar, br)]) {
1272            moves.extend(left.into_iter().map(|c| sum_options(games, c, b, cache)));
1273            moves.extend(right.into_iter().map(|c| sum_options(games, a, c, cache)));
1274        }
1275        let i = games.len();
1276        let [left, right] = options;
1277        games.push((left, right));
1278        cache.insert(key, i);
1279        i
1280    }
1281
1282    // Direct minimax on raw trees, independently of canonicalization and XOR.
1283    fn wins(
1284        games: &[(Vec<usize>, Vec<usize>)],
1285        a: usize,
1286        b: usize,
1287        right: bool,
1288        subtract: bool,
1289        cache: &mut HashMap<(usize, usize, bool), bool>,
1290    ) -> bool {
1291        // Swapping the terms negates a difference, which also swaps the players.
1292        let key = (a.min(b), a.max(b), right ^ (subtract && a > b));
1293        if let Some(&result) = cache.get(&key) {
1294            return result;
1295        }
1296        let first = if right { &games[a].1 } else { &games[a].0 };
1297        let second = if right ^ subtract {
1298            &games[b].1
1299        } else {
1300            &games[b].0
1301        };
1302        let result = first
1303            .iter()
1304            .any(|&c| !wins(games, c, b, !right, subtract, cache))
1305            || second
1306                .iter()
1307                .any(|&c| !wins(games, a, c, !right, subtract, cache));
1308        cache.insert(key, result);
1309        result
1310    }
1311}