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 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 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 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 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 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)] impl<'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)] impl<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)] impl<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 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 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 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 xor ^= target.unwrap();
424 infinite = None;
425 }
426 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 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 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 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; }
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 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 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 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 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 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 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 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 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 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 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 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}