Skip to main content

competitive/algorithm/game_analyzer/
graph.rs

1use super::{
2    EvaluatedMove, GameAnalysis, GameAnalyzer, GameOutcome, GameSum, GameValue, Graph, HashMap,
3    HashSet, Misere, Neighbor, TopologicalSortExt, UsizeGraph, VertexMap, game_mex,
4};
5use std::{cmp::Reverse, collections::VecDeque};
6
7pub struct ImpartialGraph<'g, G>
8where
9    G: Graph + VertexMap<Option<usize>>,
10{
11    graph: &'g G,
12    index: Option<GraphIndex<G>>,
13    grundy: Vec<Option<u64>>,
14    outcomes: [GraphOutcomes; 2],
15    generalized_grundy: Vec<Option<(u64, usize)>>,
16    sums: GraphSums,
17}
18
19struct GraphIndex<G>
20where
21    G: Graph + VertexMap<Option<usize>>,
22{
23    indices: G::Vmap,
24    edges: Vec<Vec<usize>>,
25    reverse: Vec<Vec<usize>>,
26}
27
28#[derive(Default)]
29struct GraphSums {
30    indices: HashMap<Vec<usize>, usize>,
31    outcomes: GraphOutcomes,
32}
33
34#[derive(Default)]
35struct GraphOutcomes {
36    values: Vec<GameOutcome>,
37    winning: Vec<Option<usize>>,
38}
39
40impl GameAnalyzer {
41    /// Uses the graph's shared moves for both players. Query vertices must be
42    /// valid inputs to the graph's `VertexMap` operations.
43    pub fn impartial_graph<G>(self, graph: &G) -> GameAnalyzer<ImpartialGraph<'_, G>>
44    where
45        G: Graph + VertexMap<Option<usize>>,
46    {
47        GameAnalyzer {
48            game: ImpartialGraph {
49                graph,
50                index: None,
51                grundy: Vec::new(),
52                outcomes: std::array::from_fn(|_| GraphOutcomes::default()),
53                generalized_grundy: Vec::new(),
54                sums: GraphSums::default(),
55            },
56        }
57    }
58}
59
60impl<'g, G> GameAnalyzer<ImpartialGraph<'g, G>>
61where
62    G: Graph + VertexMap<Option<usize>>,
63{
64    pub fn grundy(&mut self) -> GameAnalysis<'_, ImpartialGraph<'g, G>, G::Vertex, u64> {
65        GameAnalysis::new(&mut self.game, ImpartialGraph::grundy)
66    }
67
68    pub fn value(&mut self) -> GameAnalysis<'_, ImpartialGraph<'g, G>, G::Vertex, GameValue> {
69        GameAnalysis::new(&mut self.game, |g, s| GameValue::nimber(g.grundy(s)))
70    }
71
72    pub fn outcome(&mut self) -> GameAnalysis<'_, ImpartialGraph<'g, G>, G::Vertex, GameOutcome> {
73        GameAnalysis::new(&mut self.game, |g, s| g.outcome(s, false))
74    }
75
76    pub fn misere(&mut self) -> Misere<'_, ImpartialGraph<'g, G>> {
77        Misere {
78            game: &mut self.game,
79        }
80    }
81
82    pub fn sum(&mut self) -> GameSum<'_, ImpartialGraph<'g, G>> {
83        GameSum {
84            game: &mut self.game,
85        }
86    }
87
88    pub fn clear_cache(&mut self) {
89        self.game.grundy.clear();
90        self.game.outcomes = std::array::from_fn(|_| GraphOutcomes::default());
91        self.game.generalized_grundy.clear();
92        self.game.sums = GraphSums::default();
93    }
94}
95
96impl<'a, 'g, G> Misere<'a, ImpartialGraph<'g, G>>
97where
98    G: Graph + VertexMap<Option<usize>>,
99{
100    pub fn outcome(self) -> GameAnalysis<'a, ImpartialGraph<'g, G>, G::Vertex, GameOutcome, true> {
101        GameAnalysis::new(self.game, |g, s| g.outcome(s, true))
102    }
103}
104impl<'a, 'g, G> GameSum<'a, ImpartialGraph<'g, G>>
105where
106    G: Graph + VertexMap<Option<usize>>,
107{
108    pub fn grundy(self) -> GameAnalysis<'a, ImpartialGraph<'g, G>, [G::Vertex], u64> {
109        GameAnalysis::new(self.game, ImpartialGraph::sum_grundy)
110    }
111}
112impl<'a, 'g, G, const MISERE: bool> GameSum<'a, ImpartialGraph<'g, G>, MISERE>
113where
114    G: Graph + VertexMap<Option<usize>>,
115{
116    /// Normal play combines generalized Grundy values, including cyclic positions.
117    /// Misere explores the sum graph, which can grow exponentially in the component count.
118    pub fn outcome(
119        self,
120    ) -> GameAnalysis<'a, ImpartialGraph<'g, G>, [G::Vertex], GameOutcome, MISERE> {
121        GameAnalysis::new(self.game, |g, s| g.sum_outcome(s, MISERE))
122    }
123}
124
125impl<'g, S, L, G, V, const MISERE: bool, E> GameAnalysis<'_, ImpartialGraph<'g, G>, S, V, MISERE, E>
126where
127    S: Copy,
128    G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,
129    E: FnMut(&mut ImpartialGraph<'g, G>, &S) -> V,
130{
131    /// Values describe the destination position; outcomes are for the next player.
132    pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, V>> {
133        self.game.prepare();
134        self.game.index_of(*state);
135        self.game
136            .graph
137            .neighbors(*state)
138            .map(|next| {
139                let value = self.eval(&next.to);
140                EvaluatedMove { next, value }
141            })
142            .collect()
143    }
144}
145impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, S, u64>
146where
147    S: Copy,
148    G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,
149{
150    pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>> {
151        self.game.prepare();
152        self.game.index_of(*state);
153        self.game
154            .graph
155            .neighbors(*state)
156            .filter(|next| self.eval(&next.to) == 0)
157            .collect()
158    }
159}
160impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, S, GameValue>
161where
162    S: Copy,
163    G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,
164{
165    pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>> {
166        self.game.prepare();
167        self.game.index_of(*state);
168        self.game
169            .graph
170            .neighbors(*state)
171            .filter(|next| self.eval(&next.to).as_nimber() == Some(0))
172            .collect()
173    }
174}
175impl<S, L, G, const MISERE: bool> GameAnalysis<'_, ImpartialGraph<'_, G>, S, GameOutcome, MISERE>
176where
177    S: Copy,
178    G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,
179{
180    /// Moves to losing positions. To avoid cycling among winning positions,
181    /// repeatedly use `best_move`, which follows the retrograde proof.
182    pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>> {
183        self.game.prepare();
184        self.game.index_of(*state);
185        self.game
186            .graph
187            .neighbors(*state)
188            .filter(|next| self.eval(&next.to) == GameOutcome::Lose)
189            .collect()
190    }
191
192    /// A move that guarantees termination when winning, or preserves a draw.
193    /// Returns `None` at terminal or losing positions.
194    pub fn best_move(&mut self, state: &S) -> Option<Neighbor<S, L>> {
195        let outcome = self.eval(state);
196        if outcome == GameOutcome::Lose {
197            return None;
198        }
199        let game = &self.game;
200        let root = game.index_of(*state);
201        let result = &game.outcomes[usize::from(MISERE)];
202        let winner = result.winning[root];
203        for next in game.graph.neighbors(*state) {
204            let id = game.index_of(next.to);
205            if Some(id) == winner
206                || outcome == GameOutcome::Draw && result.values[id] == GameOutcome::Draw
207            {
208                return Some(next);
209            }
210        }
211        None
212    }
213}
214
215#[allow(clippy::type_complexity)] // Sum states and labels both carry component information.
216impl<'g, S, L, G, V, const MISERE: bool, E>
217    GameAnalysis<'_, ImpartialGraph<'g, G>, [S], V, MISERE, E>
218where
219    S: Copy,
220    G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,
221    E: FnMut(&mut ImpartialGraph<'g, G>, &[S]) -> V,
222{
223    pub fn moves(&mut self, state: &[S]) -> Vec<EvaluatedMove<Vec<S>, (usize, L), V>> {
224        self.eval(state);
225        let mut result = Vec::new();
226        for (i, &component) in state.iter().enumerate() {
227            for next in self.game.graph.neighbors(component) {
228                let mut to = state.to_vec();
229                to[i] = next.to;
230                let value = self.eval(&to);
231                result.push(EvaluatedMove {
232                    next: Neighbor::new(to, (i, next.label)),
233                    value,
234                });
235            }
236        }
237        result
238    }
239}
240#[allow(clippy::type_complexity)] // Sum states and labels both carry component information.
241impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], u64>
242where
243    S: Copy,
244    G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,
245{
246    pub fn winning_moves(&mut self, state: &[S]) -> Vec<Neighbor<Vec<S>, (usize, L)>> {
247        let xor = self.eval(state);
248        let mut result = Vec::new();
249        if xor == 0 {
250            return result;
251        }
252        for (i, component) in state.iter().enumerate() {
253            let target = xor ^ self.game.grundy(component);
254            for next in self.game.graph.neighbors(*component) {
255                if self.game.grundy(&next.to) == target {
256                    let mut to = state.to_vec();
257                    to[i] = next.to;
258                    result.push(Neighbor::new(to, (i, next.label)));
259                }
260            }
261        }
262        result
263    }
264}
265#[allow(clippy::type_complexity)] // Sum states and labels both carry component information.
266impl<S, L, G, const MISERE: bool> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], GameOutcome, MISERE>
267where
268    S: Copy,
269    G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,
270{
271    /// Moves to losing positions; use `best_move` to avoid cycling.
272    pub fn winning_moves(&mut self, state: &[S]) -> Vec<Neighbor<Vec<S>, (usize, L)>> {
273        if state.is_empty() {
274            return Vec::new();
275        }
276        if let [single] = state {
277            let mut query: GameAnalysis<'_, _, S, GameOutcome, MISERE> =
278                GameAnalysis::new(self.game, |g: &mut ImpartialGraph<'_, G>, s| {
279                    g.outcome(s, MISERE)
280                });
281            return query
282                .winning_moves(single)
283                .into_iter()
284                .map(|next| Neighbor::new(vec![next.to], (0, next.label)))
285                .collect();
286        }
287        if self.eval(state) != GameOutcome::Win {
288            return Vec::new();
289        }
290        let mut indices = self.game.sum_indices(state);
291        let game = &self.game;
292        let mut xor = 0;
293        let mut infinite = 0;
294        if !MISERE {
295            for &v in &indices {
296                if let Some((value, _)) = game.generalized_grundy[v] {
297                    xor ^= value;
298                } else {
299                    infinite += 1;
300                }
301            }
302        }
303        let mut result = Vec::new();
304        for (i, &component) in state.iter().enumerate() {
305            let source = indices[i];
306            for next in game.graph.neighbors(component) {
307                indices[i] = game.index_of(next.to);
308                let wins = if MISERE {
309                    let key =
310                        GraphSums::canonical(indices.clone(), &game.index.as_ref().unwrap().edges);
311                    game.sums.outcomes.values[game.sums.indices[&key]] == GameOutcome::Lose
312                } else if let Some((value, _)) = game.generalized_grundy[indices[i]] {
313                    match game.generalized_grundy[source] {
314                        Some((g, _)) => infinite == 0 && xor ^ g == value,
315                        None => infinite == 1 && xor == value,
316                    }
317                } else {
318                    false
319                };
320                if wins {
321                    let mut to = state.to_vec();
322                    to[i] = next.to;
323                    result.push(Neighbor::new(to, (i, next.label)));
324                }
325            }
326            indices[i] = source;
327        }
328        result
329    }
330
331    /// A move that guarantees termination when winning, or preserves a draw.
332    /// Returns `None` at terminal or losing positions.
333    pub fn best_move(&mut self, state: &[S]) -> Option<Neighbor<Vec<S>, (usize, L)>> {
334        if state.is_empty() {
335            return None;
336        }
337        if let [single] = state {
338            let mut query: GameAnalysis<'_, _, S, GameOutcome, MISERE> =
339                GameAnalysis::new(self.game, |g: &mut ImpartialGraph<'_, G>, s| {
340                    g.outcome(s, MISERE)
341                });
342            return query
343                .best_move(single)
344                .map(|next| Neighbor::new(vec![next.to], (0, next.label)));
345        }
346        let outcome = self.eval(state);
347        if outcome == GameOutcome::Lose {
348            return None;
349        }
350        let mut indices = self.game.sum_indices(state);
351        let game = &self.game;
352        if MISERE {
353            let key = GraphSums::canonical(indices.clone(), &game.index.as_ref().unwrap().edges);
354            let winner = game.sums.outcomes.winning[game.sums.indices[&key]];
355            for (i, &component) in state.iter().enumerate() {
356                let original = indices[i];
357                for next in game.graph.neighbors(component) {
358                    indices[i] = game.index_of(next.to);
359                    let key =
360                        GraphSums::canonical(indices.clone(), &game.index.as_ref().unwrap().edges);
361                    let id = game.sums.indices[&key];
362                    if Some(id) == winner
363                        || outcome == GameOutcome::Draw
364                            && game.sums.outcomes.values[id] == GameOutcome::Draw
365                    {
366                        let mut to = state.to_vec();
367                        to[i] = next.to;
368                        return Some(Neighbor::new(to, (i, next.label)));
369                    }
370                }
371                indices[i] = original;
372            }
373            return None;
374        }
375        let mut xor = 0;
376        let mut infinite = None;
377        let mut multiple = false;
378        for (i, &v) in indices.iter().enumerate() {
379            if let Some((value, _)) = game.generalized_grundy[v] {
380                xor ^= value;
381            } else if infinite.replace(i).is_some() {
382                multiple = true;
383                break;
384            }
385        }
386        if outcome == GameOutcome::Draw {
387            let i = infinite.unwrap();
388            let v = indices[i];
389            let edges = &game.index.as_ref().unwrap().edges;
390            let target = (!multiple).then(|| {
391                let values = edges[v]
392                    .iter()
393                    .filter_map(|&u| game.generalized_grundy[u].map(|(g, _)| g))
394                    .collect();
395                game_mex(values)
396            });
397            // Preserve two infinite components, or keep the finite XOR outside
398            // the remaining infinite component's finite follower values.
399            if target.is_none_or(|g| g == xor) {
400                let to = edges[v]
401                    .iter()
402                    .copied()
403                    .find(|&u| {
404                        game.generalized_grundy[u].is_none()
405                            && target.is_none_or(|g| {
406                                !edges[u].iter().any(|&w| {
407                                    game.generalized_grundy[w].is_some_and(|(value, _)| value == g)
408                                })
409                            })
410                    })
411                    .unwrap();
412                let next = game
413                    .graph
414                    .neighbors(state[i])
415                    .find(|next| game.index_of(next.to) == to)
416                    .unwrap();
417                let mut to = state.to_vec();
418                to[i] = next.to;
419                return Some(Neighbor::new(to, (i, next.label)));
420            }
421            // The current finite XOR exceeds this mex, so a finite component
422            // can decrease it to the mex without moving the infinite component.
423            xor ^= target.unwrap();
424            infinite = None;
425        }
426        // Reduce the largest (Grundy, counter) pair, then minimize the
427        // destination counter so repeated winning moves terminate.
428        let (i, target) = if let Some(i) = infinite {
429            (i, xor)
430        } else {
431            let (i, (g, _)) = indices
432                .iter()
433                .enumerate()
434                .filter_map(|(i, &v)| game.generalized_grundy[v].map(|g| (i, g)))
435                .filter(|(_, (g, _))| g ^ xor < *g)
436                .max_by_key(|&(i, g)| (g, Reverse(i)))?;
437            (i, g ^ xor)
438        };
439        let next = game
440            .graph
441            .neighbors(state[i])
442            .filter_map(|next| {
443                let (g, counter) = game.generalized_grundy[game.index_of(next.to)]?;
444                (g == target).then_some((next, counter))
445            })
446            .min_by_key(|&(_, counter)| counter)?
447            .0;
448        let mut to = state.to_vec();
449        to[i] = next.to;
450        Some(Neighbor::new(to, (i, next.label)))
451    }
452}
453
454impl<G> ImpartialGraph<'_, G>
455where
456    G: Graph + VertexMap<Option<usize>>,
457{
458    fn grundy(&mut self, state: &G::Vertex) -> u64 {
459        self.prepare();
460        let root = self.index_of(*state);
461        let index = self.index.as_ref().unwrap();
462        self.grundy.resize(index.edges.len(), None);
463        if let Some(value) = self.grundy[root] {
464            return value;
465        }
466        let mut active = HashSet::from([root]);
467        let mut stack = vec![(root, index.edges[root].iter())];
468        while let Some((v, moves)) = stack.last_mut() {
469            if let Some(&u) = moves.next() {
470                if self.grundy[u].is_none() {
471                    assert!(active.insert(u), "cycle in a finite game");
472                    stack.push((u, index.edges[u].iter()));
473                }
474            } else {
475                let values = index.edges[*v]
476                    .iter()
477                    .map(|&u| self.grundy[u].unwrap())
478                    .collect();
479                self.grundy[*v] = Some(game_mex(values));
480                active.remove(v);
481                stack.pop();
482            }
483        }
484        self.grundy[root].unwrap()
485    }
486
487    fn outcome(&mut self, state: &G::Vertex, misere: bool) -> GameOutcome {
488        self.prepare();
489        let index = self.index.as_ref().unwrap();
490        let outcome = &mut self.outcomes[usize::from(misere)];
491        if outcome.values.is_empty() {
492            outcome.extend(&index.edges, &index.reverse, misere);
493        }
494        self.outcomes[usize::from(misere)].values[self.index_of(*state)]
495    }
496
497    fn sum_grundy(&mut self, state: &[G::Vertex]) -> u64 {
498        state.iter().fold(0, |value, s| value ^ self.grundy(s))
499    }
500
501    fn sum_outcome(&mut self, state: &[G::Vertex], misere: bool) -> GameOutcome {
502        match state {
503            [] => return GameOutcome::from_win(misere),
504            [single] => return self.outcome(single, misere),
505            _ => {}
506        }
507        let indices = self.sum_indices(state);
508        if !misere {
509            self.solve_generalized_grundy();
510            return self.sum_normal_outcome(&indices);
511        }
512        let edges = &self.index.as_ref().unwrap().edges;
513        let root = self.sums.prepare(indices, edges);
514        self.sums.outcomes.values[root]
515    }
516
517    fn sum_normal_outcome(&self, state: &[usize]) -> GameOutcome {
518        let mut xor = 0;
519        let mut infinite = None;
520        for &v in state {
521            if let Some((value, _)) = self.generalized_grundy[v] {
522                xor ^= value;
523            } else if infinite.replace(v).is_some() {
524                return GameOutcome::Draw;
525            }
526        }
527        if let Some(v) = infinite {
528            let can_win = self.index.as_ref().unwrap().edges[v]
529                .iter()
530                .any(|&u| self.generalized_grundy[u].is_some_and(|(value, _)| value == xor));
531            if can_win {
532                GameOutcome::Win
533            } else {
534                GameOutcome::Draw
535            }
536        } else if xor == 0 {
537            GameOutcome::Lose
538        } else {
539            GameOutcome::Win
540        }
541    }
542
543    fn sum_indices(&mut self, state: &[G::Vertex]) -> Vec<usize> {
544        self.prepare();
545        state.iter().map(|&s| self.index_of(s)).collect()
546    }
547
548    fn prepare(&mut self) {
549        if self.index.is_some() {
550            return;
551        }
552        let vertices: Vec<_> = self.graph.vertices().collect();
553        let mut indices = self.graph.construct_vmap(|| None);
554        for (i, &v) in vertices.iter().enumerate() {
555            self.graph.vmap_set(&mut indices, v, Some(i));
556        }
557        let mut edges = vec![Vec::new(); vertices.len()];
558        let mut reverse = vec![Vec::new(); vertices.len()];
559        let mut seen = vec![None; vertices.len()];
560        for (i, &v) in vertices.iter().enumerate() {
561            for next in self.graph.neighbors(v) {
562                let j = self
563                    .graph
564                    .vmap_get(&indices, next.to)
565                    .expect("vertex is not enumerated by the graph");
566                if seen[j] != Some(i) {
567                    seen[j] = Some(i);
568                    edges[i].push(j);
569                    reverse[j].push(i);
570                }
571            }
572        }
573        self.index = Some(GraphIndex {
574            indices,
575            edges,
576            reverse,
577        });
578    }
579
580    fn index_of(&self, v: G::Vertex) -> usize {
581        self.graph
582            .vmap_get(&self.index.as_ref().unwrap().indices, v)
583            .expect("vertex is not enumerated by the graph")
584    }
585
586    fn solve_generalized_grundy(&mut self) {
587        let index = self.index.as_ref().unwrap();
588        let n = index.edges.len();
589        if self.generalized_grundy.len() == n {
590            return;
591        }
592        let mut values: Vec<Option<(u64, usize)>> = vec![None; n];
593        // Visit acyclic followers first, so DAGs need only one pass.
594        let reverse = UsizeGraph::new(n, |v: usize| index.reverse[v].iter().map(|&u| (u, ())));
595        let order = reverse.topological_sort();
596        if order.len() == n {
597            for (counter, v) in order.into_iter().enumerate() {
598                let mex = self.grundy.get(v).copied().flatten().unwrap_or_else(|| {
599                    let options = index.edges[v]
600                        .iter()
601                        .map(|&u| values[u].unwrap().0)
602                        .collect();
603                    game_mex(options)
604                });
605                values[v] = Some((mex, counter));
606            }
607            self.generalized_grundy = values;
608            return;
609        }
610        let mut queue: VecDeque<_> = order.into();
611        let mut queued = vec![false; n];
612        for &v in &queue {
613            queued[v] = true;
614        }
615        for (v, pending) in queued.iter_mut().enumerate() {
616            if !*pending {
617                *pending = true;
618                queue.push_back(v);
619            }
620        }
621        let mut counter = 0;
622        let mut options = vec![HashSet::new(); n];
623        let mut mexes = vec![0; n];
624        while let Some(v) = queue.pop_front() {
625            queued[v] = false;
626            let mex = mexes[v];
627            // Every unresolved option must be able to return to this mex.
628            let ready = index.edges[v]
629                .iter()
630                .all(|&u| values[u].is_some() || options[u].contains(&mex));
631            if !ready {
632                continue;
633            }
634            values[v] = Some((mex, counter));
635            counter += 1;
636            for &u in &index.reverse[v] {
637                if values[u].is_some() {
638                    continue;
639                }
640                let inserted = options[u].insert(mex);
641                if inserted {
642                    while options[u].contains(&mexes[u]) {
643                        mexes[u] += 1;
644                    }
645                }
646                // A larger value already had a follower at this vertex's mex.
647                if mex < mexes[u] && !queued[u] {
648                    queued[u] = true;
649                    queue.push_back(u);
650                }
651                if inserted {
652                    for &w in &index.reverse[u] {
653                        if queue.len() == n - counter {
654                            break; // All unresolved vertices are already queued.
655                        }
656                        if values[w].is_none() && !queued[w] && mexes[w] == mex {
657                            queued[w] = true;
658                            queue.push_back(w);
659                        }
660                    }
661                }
662            }
663        }
664        self.generalized_grundy = values;
665    }
666}
667
668impl GraphSums {
669    fn prepare(&mut self, state: Vec<usize>, components: &[Vec<usize>]) -> usize {
670        let state = Self::canonical(state, components);
671        if let Some(&root) = self.indices.get(&state) {
672            return root;
673        }
674        let root = self.outcomes.values.len();
675        self.indices.insert(state.clone(), root);
676        let mut edges = vec![Vec::new()];
677        let mut reverse = vec![Vec::new()];
678        let mut pending = VecDeque::from([(root, state)]);
679        while let Some((v, state)) = pending.pop_front() {
680            for (i, &component) in state.iter().enumerate() {
681                if i != 0 && state[i - 1] == component {
682                    continue;
683                }
684                for &next in &components[component] {
685                    let mut to = state.clone();
686                    to[i] = next;
687                    let to = Self::canonical(to, components);
688                    let u = *self.indices.entry(to).or_insert_with_key(|to| {
689                        let u = root + edges.len();
690                        pending.push_back((u, to.clone()));
691                        edges.push(Vec::new());
692                        reverse.push(Vec::new());
693                        u
694                    });
695                    edges[v - root].push(u);
696                }
697            }
698            edges[v - root].sort_unstable();
699            edges[v - root].dedup();
700            for &u in &edges[v - root] {
701                if u >= root {
702                    reverse[u - root].push(v);
703                }
704            }
705        }
706        self.outcomes.extend(&edges, &reverse, true);
707        root
708    }
709
710    fn canonical(mut state: Vec<usize>, components: &[Vec<usize>]) -> Vec<usize> {
711        state.retain(|&v| !components[v].is_empty());
712        state.sort_unstable();
713        state
714    }
715}
716
717impl GraphOutcomes {
718    fn extend(&mut self, edges: &[Vec<usize>], reverse: &[Vec<usize>], misere: bool) {
719        let start = self.values.len();
720        self.values.resize(start + edges.len(), GameOutcome::Draw);
721        self.winning.resize(start + edges.len(), None);
722        let mut remaining: Vec<_> = edges.iter().map(Vec::len).collect();
723        let mut queue = VecDeque::new();
724        for (i, next) in edges.iter().enumerate() {
725            let v = start + i;
726            if next.is_empty() {
727                self.values[v] = GameOutcome::from_win(misere);
728            } else if start != 0 {
729                // The old prefix is closed under moves; only the appended states need solving.
730                for &u in next {
731                    if u < start {
732                        match self.values[u] {
733                            GameOutcome::Lose => {
734                                self.values[v] = GameOutcome::Win;
735                                self.winning[v] = Some(u);
736                                break;
737                            }
738                            GameOutcome::Win => remaining[v - start] -= 1,
739                            GameOutcome::Draw => {}
740                        }
741                    }
742                }
743                if self.values[v] == GameOutcome::Draw && remaining[v - start] == 0 {
744                    self.values[v] = GameOutcome::Lose;
745                }
746            }
747            if self.values[v] != GameOutcome::Draw {
748                queue.push_back(v);
749            }
750        }
751        while let Some(v) = queue.pop_front() {
752            for &u in &reverse[v - start] {
753                if self.values[u] != GameOutcome::Draw {
754                    continue;
755                }
756                if self.values[v] == GameOutcome::Lose {
757                    self.values[u] = GameOutcome::Win;
758                    self.winning[u] = Some(v);
759                    queue.push_back(u);
760                } else {
761                    remaining[u - start] -= 1;
762                    if remaining[u - start] == 0 {
763                        self.values[u] = GameOutcome::Lose;
764                        queue.push_back(u);
765                    }
766                }
767            }
768        }
769    }
770}
771
772#[cfg(test)]
773mod tests {
774    use super::*;
775    use crate::graph::AdjacencyListGraph;
776    use crate::tools::testutil::exhaustive_sequences;
777
778    #[test]
779    fn sums_against_play() {
780        let mut graphs = Vec::new();
781        // Every graph on up to three vertices, including duplicate labeled edges.
782        for n in 0..=3 {
783            for mask in 0..1usize << (n * n) {
784                let mut graph = AdjacencyListGraph::new(n);
785                for u in 0..n {
786                    for v in 0..n {
787                        if mask >> (u * n + v) & 1 != 0 {
788                            graph.add_edge(u, v);
789                            if (u + v) % 2 == 0 {
790                                graph.add_edge(u, v);
791                            }
792                        }
793                    }
794                }
795                graphs.push((graph, 3));
796            }
797        }
798        // Resolving another option below the mex must wake its predecessor,
799        // even when that option's value was already present.
800        let mut graph = AdjacencyListGraph::new(4);
801        for (u, v) in [(0, 3), (0, 1), (1, 2), (2, 1), (2, 3)] {
802            graph.add_edge(u, v);
803        }
804        graphs.push((graph, 3));
805        // In [3, 2], preserving a draw requires moving the finite component.
806        let mut graph = AdjacencyListGraph::new(5);
807        for (u, v) in [(1, 0), (2, 0), (2, 1), (3, 4), (4, 4), (4, 2)] {
808            graph.add_edge(u, v);
809        }
810        graphs.push((graph, 3));
811        // In [3, 0], skip repeated unsafe infinite successors before choosing 1.
812        let mut graph = AdjacencyListGraph::new(4);
813        for (u, v) in [(1, 1), (2, 2), (2, 0), (3, 2), (3, 2), (3, 1)] {
814            graph.add_edge(u, v);
815        }
816        graphs.push((graph, 3));
817        for copies in [1, 2, 31] {
818            let mut graph = AdjacencyListGraph::new(6);
819            for (u, v) in [(0, 0), (3, 4), (4, 3), (4, 5)] {
820                graph.add_edge(u, v);
821            }
822            for _ in 0..copies {
823                graph.add_edge(1, 2);
824                graph.add_edge(2, 3);
825            }
826            graphs.push((graph, 3));
827            let mut graph = AdjacencyListGraph::new(3);
828            for _ in 0..copies {
829                graph.add_edge(0, 2);
830                graph.add_edge(1, 1);
831            }
832            graph.add_edge(1, 2);
833            graphs.push((graph, 3));
834        }
835        // Normal-equivalent positions need not be interchangeable under misere.
836        let mut graph = AdjacencyListGraph::new(4);
837        for (u, v) in [(1, 0), (2, 0), (2, 1), (3, 2)] {
838            graph.add_edge(u, v);
839        }
840        graphs.push((graph, 3));
841        // Keep a terminal in larger samples so finite values also occur.
842        let mut rng = crate::tools::Xorshift::default();
843        for n in 4..=8 {
844            for degree in 0..=n {
845                for dag in [false, true] {
846                    let mut graph = AdjacencyListGraph::new(n);
847                    for v in 1..n {
848                        for _ in 0..degree {
849                            let bound = if dag { v } else { n };
850                            graph.add_edge(v, rng.rand(bound as u64) as usize);
851                        }
852                    }
853                    graphs.push((graph, 3));
854                }
855            }
856        }
857        let mut nim = AdjacencyListGraph::new(5);
858        for s in 0..5 {
859            for to in 0..s {
860                nim.add_edge(s, to);
861            }
862        }
863        graphs.push((nim, 4));
864        for (graph, max_len) in &graphs {
865            let n = graph.vsize;
866            let mut game = GameAnalyzer::new().impartial_graph(graph);
867            let mut acyclic = (0..n)
868                .all(|s| graph.neighbors(s).all(|m| m.to < s))
869                .then(|| GameAnalyzer::new().impartial_moves(|&s: &usize| graph.neighbors(s)));
870            let mut grundy = vec![0u64; n];
871            if acyclic.is_some() {
872                for s in 0..n {
873                    grundy[s] = (0..)
874                        .find(|g| graph.neighbors(s).all(|m| grundy[m.to] != *g))
875                        .unwrap();
876                }
877            }
878            // A singleton sum also checks the single-position outcome and strategy.
879            for len in 0..=*max_len {
880                let states: Vec<_> = exhaustive_sequences(0..n, len..=len).collect();
881                let encode = |s: &[usize]| s.iter().rev().fold(0, |id, v| id * n + v);
882                let edges: Vec<Vec<_>> = states
883                    .iter()
884                    .map(|state| {
885                        let mut edges = Vec::new();
886                        for (i, &v) in state.iter().enumerate() {
887                            for next in graph.neighbors(v) {
888                                let mut to = state.clone();
889                                to[i] = next.to;
890                                edges.push(Neighbor::new(to, (i, next.label)));
891                            }
892                        }
893                        edges
894                    })
895                    .collect();
896                let indexed: Vec<Vec<_>> = edges
897                    .iter()
898                    .map(|next| next.iter().map(|e| encode(&e.to)).collect())
899                    .collect();
900                for misere in [false, true] {
901                    let expected = outcomes(&indexed, misere);
902                    let mut policy = vec![Vec::new(); 2 * states.len()];
903                    // Descending queries append reachable states to an existing sum cache.
904                    for (id, state) in states.iter().enumerate().rev() {
905                        let (actual, moves, winning, best) = if misere {
906                            let mut query = game.sum().misere().outcome();
907                            (
908                                query.eval(state),
909                                query.moves(state),
910                                query.winning_moves(state),
911                                query.best_move(state),
912                            )
913                        } else {
914                            let mut query = game.sum().outcome();
915                            (
916                                query.eval(state),
917                                query.moves(state),
918                                query.winning_moves(state),
919                                query.best_move(state),
920                            )
921                        };
922                        assert_eq!(
923                            actual, expected[id],
924                            "{:?}, {state:?}, {misere}",
925                            graph.graph
926                        );
927                        let evaluated: Vec<_> = edges[id]
928                            .iter()
929                            .map(|next| EvaluatedMove {
930                                next: next.clone(),
931                                value: expected[encode(&next.to)],
932                            })
933                            .collect();
934                        assert_eq!(moves, evaluated);
935                        let expected_winning: Vec<_> = evaluated
936                            .iter()
937                            .filter(|m| m.value == GameOutcome::Lose)
938                            .map(|m| m.next.clone())
939                            .collect();
940                        assert_eq!(winning, expected_winning);
941                        if let Some(closure) = &mut acyclic {
942                            let (value, moves, winning) = if misere {
943                                let mut query = closure.misere().sum().outcome();
944                                (
945                                    query.eval(state),
946                                    query.moves(state),
947                                    query.winning_moves(state),
948                                )
949                            } else {
950                                let mut query = closure.sum().outcome();
951                                (
952                                    query.eval(state),
953                                    query.moves(state),
954                                    query.winning_moves(state),
955                                )
956                            };
957                            assert_eq!(value, expected[id]);
958                            assert_eq!(moves, evaluated);
959                            assert_eq!(winning, expected_winning);
960                            if !misere {
961                                let xor = state.iter().fold(0, |g, &s| g ^ grundy[s]);
962                                assert_eq!(closure.sum().grundy().eval(state), xor);
963                                assert_eq!(game.sum().grundy().eval(state), xor);
964                                assert_eq!(
965                                    closure.sum().grundy().winning_moves(state),
966                                    expected_winning
967                                );
968                                assert_eq!(
969                                    game.sum().grundy().winning_moves(state),
970                                    expected_winning
971                                );
972                            }
973                        }
974                        if let Some(next) = &best {
975                            assert!(edges[id].contains(next));
976                        } else {
977                            assert!(edges[id].is_empty() || actual == GameOutcome::Lose);
978                        }
979                        if let Some(next) = best.as_ref().or_else(|| edges[id].first()) {
980                            policy[2 * id].push(2 * encode(&next.to) + 1);
981                        }
982                        policy[2 * id + 1] = indexed[id].iter().map(|u| 2 * u).collect();
983                    }
984                    // Fix our strategy to best_move, leaving every opponent reply available.
985                    let actual = outcomes(&policy, misere);
986                    for (id, &expected) in expected.iter().enumerate() {
987                        if expected != GameOutcome::Lose {
988                            assert_eq!(actual[2 * id], expected);
989                        }
990                    }
991                }
992            }
993            if n != 0 {
994                let before = game.sum().misere().outcome().eval(&[0, 0]);
995                game.clear_cache();
996                assert_eq!(game.misere().sum().outcome().eval(&[0, 0]), before);
997            }
998        }
999    }
1000
1001    // Independent fixed point on the full ordered product, without reverse edges or a queue.
1002    fn outcomes(edges: &[Vec<usize>], misere: bool) -> Vec<GameOutcome> {
1003        let mut values: Vec<_> = edges
1004            .iter()
1005            .map(|e| e.is_empty().then_some(misere))
1006            .collect();
1007        loop {
1008            let next: Vec<_> = edges
1009                .iter()
1010                .zip(&values)
1011                .map(|(edges, &known)| {
1012                    known.or_else(|| {
1013                        if edges.iter().any(|&v| values[v] == Some(false)) {
1014                            Some(true)
1015                        } else if edges.iter().all(|&v| values[v] == Some(true)) {
1016                            Some(false)
1017                        } else {
1018                            None
1019                        }
1020                    })
1021                })
1022                .collect();
1023            if next == values {
1024                break;
1025            }
1026            values = next;
1027        }
1028        values
1029            .into_iter()
1030            .map(|v| match v {
1031                Some(true) => GameOutcome::Win,
1032                Some(false) => GameOutcome::Lose,
1033                None => GameOutcome::Draw,
1034            })
1035            .collect()
1036    }
1037
1038    #[test]
1039    fn reachable_dag_and_parallel_edges() {
1040        let mut graph = AdjacencyListGraph::new(6);
1041        for (u, v) in [
1042            (1, 0),
1043            (1, 0),
1044            (2, 0),
1045            (2, 1),
1046            (3, 4),
1047            (4, 3),
1048            (5, 3),
1049            (5, 2),
1050        ] {
1051            graph.add_edge(u, v);
1052        }
1053        let mut game = GameAnalyzer::new().impartial_graph(&graph);
1054        assert_eq!(game.grundy().table(0..3), vec![(0, 0), (1, 1), (2, 2)]);
1055        assert_eq!(game.value().eval(&2).as_nimber(), Some(2));
1056        assert_eq!(
1057            game.value()
1058                .winning_moves(&2)
1059                .iter()
1060                .map(|n| n.to)
1061                .collect::<Vec<_>>(),
1062            vec![0]
1063        );
1064        assert_eq!(game.grundy().eval(&2), 2);
1065        assert_eq!(game.outcome().eval(&5), GameOutcome::Draw);
1066        assert_eq!(game.outcome().winning_moves(&1).len(), 2);
1067        assert_eq!(
1068            game.grundy()
1069                .moves(&1)
1070                .iter()
1071                .map(|m| m.next.label)
1072                .collect::<Vec<_>>(),
1073            vec![0, 1]
1074        );
1075        game.clear_cache();
1076        assert_eq!(game.misere().outcome().eval(&1), GameOutcome::Lose);
1077        assert_eq!(game.outcome().eval(&1), GameOutcome::Win);
1078    }
1079
1080    #[test]
1081    fn sparse_vertices() {
1082        use crate::graph::ClosureGraph;
1083        let graph = ClosureGraph::new(
1084            || [10, 20].into_iter(),
1085            |v| (v == 20).then_some((10, ())).into_iter(),
1086        );
1087        let mut game = GameAnalyzer::new().impartial_graph(&graph);
1088        assert_eq!(game.grundy().eval(&20), 1);
1089        assert_eq!(game.outcome().eval(&20), GameOutcome::Win);
1090    }
1091
1092    #[test]
1093    fn vertex_map_without_hash() {
1094        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1095        struct Vertex(usize);
1096        struct Label(usize);
1097        struct Path;
1098        impl Graph for Path {
1099            type Vertex = Vertex;
1100            type Label = Label;
1101            type Vertices<'g> = std::iter::Map<std::ops::Range<usize>, fn(usize) -> Vertex>;
1102            type Neighbors<'g> = std::option::IntoIter<Neighbor<Vertex, Label>>;
1103            fn vsize(&self) -> usize {
1104                3
1105            }
1106            fn vertices(&self) -> Self::Vertices<'_> {
1107                (0..3).map(Vertex)
1108            }
1109            fn neighbors(&self, v: Vertex) -> Self::Neighbors<'_> {
1110                v.0.checked_sub(1)
1111                    .map(|u| Neighbor::new(Vertex(u), Label(v.0)))
1112                    .into_iter()
1113            }
1114        }
1115        impl<T> VertexMap<T> for Path {
1116            type Vmap = Vec<T>;
1117            fn construct_vmap<F>(&self, mut f: F) -> Vec<T>
1118            where
1119                F: FnMut() -> T,
1120            {
1121                (0..3).map(|_| f()).collect()
1122            }
1123            fn vmap_get<'a>(&self, map: &'a Vec<T>, v: Vertex) -> &'a T {
1124                &map[v.0]
1125            }
1126            fn vmap_get_mut<'a>(&self, map: &'a mut Vec<T>, v: Vertex) -> &'a mut T {
1127                &mut map[v.0]
1128            }
1129        }
1130        let mut game = GameAnalyzer::new().impartial_graph(&Path);
1131        assert_eq!(game.grundy().eval(&Vertex(2)), 0);
1132        assert_eq!(game.outcome().eval(&Vertex(1)), GameOutcome::Win);
1133        assert_eq!(game.outcome().best_move(&Vertex(1)).unwrap().label.0, 1);
1134    }
1135}