1use super::{
2 ControlFlow, EvaluatedMove, GameAnalysis, GameAnalyzer, GameOutcome, GameSum, GameValue, Hash,
3 HashMap, HashSet, Misere, Neighbor, canonical_sum, game_mex, memoized_game, memoized_wins,
4};
5
6pub struct Impartial<S, F> {
7 moves: F,
8 grundy: HashMap<S, u64>,
9 outcomes: [HashMap<S, bool>; 2],
10 sums: HashMap<Vec<S>, bool>,
11 sum_ids: HashMap<S, usize>,
12}
13
14impl GameAnalyzer {
15 #[allow(clippy::type_complexity)] pub fn impartial<S, F, I>(
17 self,
18 mut moves: F,
19 ) -> GameAnalyzer<
20 Impartial<S, impl FnMut(&S) -> std::iter::Map<I::IntoIter, fn(S) -> Neighbor<S, ()>>>,
21 >
22 where
23 F: FnMut(&S) -> I,
24 I: IntoIterator<Item = S>,
25 {
26 let label: fn(S) -> Neighbor<S, ()> = |to| Neighbor::new(to, ());
27 self.impartial_moves(move |s: &S| moves(s).into_iter().map(label))
28 }
29
30 pub fn impartial_moves<S, L, F, I>(self, moves: F) -> GameAnalyzer<Impartial<S, F>>
31 where
32 F: FnMut(&S) -> I,
33 I: IntoIterator<Item = Neighbor<S, L>>,
34 {
35 GameAnalyzer {
36 game: Impartial {
37 moves,
38 grundy: HashMap::new(),
39 outcomes: std::array::from_fn(|_| HashMap::new()),
40 sums: HashMap::new(),
41 sum_ids: HashMap::new(),
42 },
43 }
44 }
45}
46
47impl<S, F> GameAnalyzer<Impartial<S, F>> {
48 pub fn misere(&mut self) -> Misere<'_, Impartial<S, F>> {
49 Misere {
50 game: &mut self.game,
51 }
52 }
53
54 pub fn sum(&mut self) -> GameSum<'_, Impartial<S, F>> {
55 GameSum {
56 game: &mut self.game,
57 }
58 }
59
60 pub fn clear_cache(&mut self) {
61 self.game.grundy.clear();
62 self.game.outcomes.iter_mut().for_each(HashMap::clear);
63 self.game.sums.clear();
64 self.game.sum_ids.clear();
65 }
66}
67
68impl<S, L, F, I> GameAnalyzer<Impartial<S, F>>
69where
70 S: Clone + Eq + Hash,
71 F: FnMut(&S) -> I,
72 I: IntoIterator<Item = Neighbor<S, L>>,
73{
74 pub fn grundy(&mut self) -> GameAnalysis<'_, Impartial<S, F>, S, u64> {
75 GameAnalysis::new(&mut self.game, Impartial::grundy)
76 }
77
78 pub fn value(&mut self) -> GameAnalysis<'_, Impartial<S, F>, S, GameValue> {
79 GameAnalysis::new(&mut self.game, |g, s| GameValue::nimber(g.grundy(s)))
80 }
81
82 pub fn outcome(&mut self) -> GameAnalysis<'_, Impartial<S, F>, S, GameOutcome> {
83 GameAnalysis::new(&mut self.game, |g, s| g.outcome(s, false))
84 }
85
86 #[allow(clippy::type_complexity)] pub fn fold<V, C>(
89 &mut self,
90 mut combine: C,
91 ) -> GameAnalysis<'_, Impartial<S, F>, S, V, false, impl FnMut(&mut Impartial<S, F>, &S) -> V>
92 where
93 V: Clone,
94 C: FnMut(&S, &[(L, V)]) -> V,
95 {
96 let mut cache = HashMap::new();
97 GameAnalysis::new(
98 &mut self.game,
99 move |game: &mut Impartial<S, F>, state: &S| {
100 memoized_game(
101 state,
102 &mut cache,
103 &mut HashSet::new(),
104 &mut |s| {
105 ControlFlow::Continue((
106 (game.moves)(s).into_iter().map(|n| (n.to, n.label)),
107 Vec::new(),
108 ))
109 },
110 &mut |_, moves| moves.next(),
111 &mut |values, label, value| {
112 values.push((label, value));
113 ControlFlow::Continue(())
114 },
115 &mut |s, values| combine(s, &values),
116 )
117 },
118 )
119 }
120}
121
122impl<'a, S, L, F, I> Misere<'a, Impartial<S, F>>
123where
124 S: Clone + Eq + Hash,
125 F: FnMut(&S) -> I,
126 I: IntoIterator<Item = Neighbor<S, L>>,
127{
128 pub fn outcome(self) -> GameAnalysis<'a, Impartial<S, F>, S, GameOutcome, true> {
129 GameAnalysis::new(self.game, |g, s| g.outcome(s, true))
130 }
131}
132impl<'a, S, L, F, I> GameSum<'a, Impartial<S, F>>
133where
134 S: Clone + Eq + Hash,
135 F: FnMut(&S) -> I,
136 I: IntoIterator<Item = Neighbor<S, L>>,
137{
138 pub fn grundy(self) -> GameAnalysis<'a, Impartial<S, F>, [S], u64> {
139 GameAnalysis::new(self.game, Impartial::sum_grundy)
140 }
141}
142impl<'a, S, L, F, I, const MISERE: bool> GameSum<'a, Impartial<S, F>, MISERE>
143where
144 S: Clone + Eq + Hash,
145 F: FnMut(&S) -> I,
146 I: IntoIterator<Item = Neighbor<S, L>>,
147{
148 pub fn outcome(self) -> GameAnalysis<'a, Impartial<S, F>, [S], GameOutcome, MISERE> {
149 GameAnalysis::new(self.game, |g, s| g.sum_outcome(s, MISERE))
150 }
151}
152
153impl<S, L, F, I, V, const MISERE: bool, E> GameAnalysis<'_, Impartial<S, F>, S, V, MISERE, E>
154where
155 F: FnMut(&S) -> I,
156 I: IntoIterator<Item = Neighbor<S, L>>,
157 E: FnMut(&mut Impartial<S, F>, &S) -> V,
158{
159 pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, V>> {
160 (self.game.moves)(state)
161 .into_iter()
162 .map(|next| {
163 let value = self.eval(&next.to);
164 EvaluatedMove { next, value }
165 })
166 .collect()
167 }
168}
169
170impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, u64>
171where
172 S: Clone + Eq + Hash,
173 F: FnMut(&S) -> I,
174 I: IntoIterator<Item = Neighbor<S, L>>,
175{
176 pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>> {
177 self.game.winning_moves(state, false).collect()
178 }
179}
180impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, GameValue>
181where
182 S: Clone + Eq + Hash,
183 F: FnMut(&S) -> I,
184 I: IntoIterator<Item = Neighbor<S, L>>,
185{
186 pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>> {
187 self.game.winning_moves(state, false).collect()
188 }
189}
190impl<S, L, F, I, const MISERE: bool> GameAnalysis<'_, Impartial<S, F>, S, GameOutcome, MISERE>
191where
192 S: Clone + Eq + Hash,
193 F: FnMut(&S) -> I,
194 I: IntoIterator<Item = Neighbor<S, L>>,
195{
196 pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>> {
197 self.game.winning_moves(state, MISERE).collect()
198 }
199}
200
201#[allow(clippy::type_complexity)] impl<S, L, F, I, V, const MISERE: bool, E> GameAnalysis<'_, Impartial<S, F>, [S], V, MISERE, E>
203where
204 S: Clone,
205 F: FnMut(&S) -> I,
206 I: IntoIterator<Item = Neighbor<S, L>>,
207 E: FnMut(&mut Impartial<S, F>, &[S]) -> V,
208{
209 pub fn moves(&mut self, state: &[S]) -> Vec<EvaluatedMove<Vec<S>, (usize, L), V>> {
210 let mut result = Vec::new();
211 for (i, component) in state.iter().enumerate() {
212 for next in (self.game.moves)(component) {
213 let mut to = state.to_vec();
214 to[i] = next.to;
215 let value = self.eval(&to);
216 result.push(EvaluatedMove {
217 next: Neighbor::new(to, (i, next.label)),
218 value,
219 });
220 }
221 }
222 result
223 }
224}
225#[allow(clippy::type_complexity)] impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, [S], u64>
227where
228 S: Clone + Eq + Hash,
229 F: FnMut(&S) -> I,
230 I: IntoIterator<Item = Neighbor<S, L>>,
231{
232 pub fn winning_moves(&mut self, state: &[S]) -> Vec<Neighbor<Vec<S>, (usize, L)>> {
233 self.game.sum_winning_moves(state)
234 }
235}
236#[allow(clippy::type_complexity)] impl<S, L, F, I, const MISERE: bool> GameAnalysis<'_, Impartial<S, F>, [S], GameOutcome, MISERE>
238where
239 S: Clone + Eq + Hash,
240 F: FnMut(&S) -> I,
241 I: IntoIterator<Item = Neighbor<S, L>>,
242{
243 pub fn winning_moves(&mut self, state: &[S]) -> Vec<Neighbor<Vec<S>, (usize, L)>> {
244 if let [single] = state {
245 return self
246 .game
247 .winning_moves(single, MISERE)
248 .map(|next| Neighbor::new(vec![next.to], (0, next.label)))
249 .collect();
250 }
251 if !MISERE {
252 return self.game.sum_winning_moves(state);
253 }
254 let mut result = Vec::new();
255 for (i, component) in state.iter().enumerate() {
256 for next in (self.game.moves)(component) {
257 let mut to = state.to_vec();
258 to[i] = next.to;
259 if self.eval(&to) == GameOutcome::Lose {
260 result.push(Neighbor::new(to, (i, next.label)));
261 }
262 }
263 }
264 result
265 }
266}
267
268impl<S, L, F, I> Impartial<S, F>
269where
270 S: Clone + Eq + Hash,
271 F: FnMut(&S) -> I,
272 I: IntoIterator<Item = Neighbor<S, L>>,
273{
274 fn grundy(&mut self, state: &S) -> u64 {
275 memoized_game(
276 state,
277 &mut self.grundy,
278 &mut HashSet::new(),
279 &mut |s| {
280 if self.outcomes[0].get(s) == Some(&false) {
281 return ControlFlow::Break(0);
282 }
283 let moves = (self.moves)(s).into_iter().map(|n| (n.to, ()));
284 ControlFlow::Continue((moves, Vec::new()))
285 },
286 &mut |_, moves| moves.next(),
287 &mut |values, (), value| {
288 values.push(value);
289 ControlFlow::Continue(())
290 },
291 &mut |_, values| game_mex(values),
292 )
293 }
294
295 fn outcome(&mut self, state: &S, misere: bool) -> GameOutcome {
296 let win = memoized_wins(
297 state,
298 &mut self.outcomes[usize::from(misere)],
299 |s| {
300 if !misere && let Some(&value) = self.grundy.get(s) {
301 return ControlFlow::Break(value != 0);
302 }
303 let moves = (self.moves)(s).into_iter().map(|n| n.to);
304 ControlFlow::Continue(moves)
305 },
306 |_, moves| moves.next(),
307 misere,
308 );
309 GameOutcome::from_win(win)
310 }
311
312 fn winning_moves(&mut self, state: &S, misere: bool) -> impl Iterator<Item = Neighbor<S, L>> {
313 (self.moves)(state).into_iter().filter(move |next| {
314 if !misere && let Some(&value) = self.grundy.get(&next.to) {
315 value == 0
316 } else {
317 self.outcome(&next.to, misere) == GameOutcome::Lose
318 }
319 })
320 }
321
322 fn sum_grundy(&mut self, state: &[S]) -> u64 {
323 match state {
324 [] => return 0,
325 [single] => return self.grundy(single),
326 _ => {}
327 }
328 let (xor, odd) = self.reduce_sum(state);
329 odd.into_iter().fold(xor, |value, s| value ^ self.grundy(s))
330 }
331
332 #[allow(clippy::type_complexity)] fn sum_winning_moves(&mut self, state: &[S]) -> Vec<Neighbor<Vec<S>, (usize, L)>> {
334 let xor = self.sum_grundy(state);
335 let mut result = Vec::new();
336 if xor == 0 {
337 return result;
338 }
339 for (i, component) in state.iter().enumerate() {
340 let target = xor ^ self.grundy(component);
341 for next in (self.moves)(component) {
342 if self.grundy(&next.to) == target {
343 let mut to = state.to_vec();
344 to[i] = next.to;
345 result.push(Neighbor::new(to, (i, next.label)));
346 }
347 }
348 }
349 result
350 }
351
352 fn sum_outcome(&mut self, state: &[S], misere: bool) -> GameOutcome {
353 if state.is_empty() {
354 return GameOutcome::from_win(misere);
355 }
356 if let [single] = state {
357 return self.outcome(single, misere);
358 }
359 let win = if misere {
360 if let Some(&win) = self.sums.get(state) {
361 win
362 } else {
363 memoized_wins(
364 &canonical_sum(state.to_vec(), &mut self.sum_ids),
365 &mut self.sums,
366 |_| ControlFlow::Continue((0, None)),
367 |s, (i, moves)| {
368 while *i < s.len() {
369 if *i != 0 && s[*i - 1] == s[*i] {
370 *i += 1;
371 continue;
372 }
373 let iterator =
374 moves.get_or_insert_with(|| (self.moves)(&s[*i]).into_iter());
375 if let Some(next) = iterator.next() {
376 let mut to = s.clone();
377 to[*i] = next.to;
378 return Some(canonical_sum(to, &mut self.sum_ids));
379 }
380 *i += 1;
381 *moves = None;
382 }
383 None
384 },
385 true,
386 )
387 }
388 } else {
389 let (xor, odd) = self.reduce_sum(state);
390 if xor == 0 && odd.len() == 1 {
391 return self.outcome(odd.into_iter().next().unwrap(), false);
392 }
393 odd.into_iter().fold(xor, |value, s| value ^ self.grundy(s)) != 0
394 };
395 GameOutcome::from_win(win)
396 }
397
398 fn reduce_sum<'a>(&self, state: &'a [S]) -> (u64, HashSet<&'a S>) {
399 let mut xor = 0;
400 let mut odd = HashSet::new();
401 for s in state {
402 if let Some(&value) = self.grundy.get(s) {
403 xor ^= value;
404 } else if self.outcomes[0].get(s) != Some(&false) && !odd.insert(s) {
405 odd.remove(s);
406 }
407 }
408 (xor, odd)
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415 use crate::tools::{Xorshift, testutil::sequence_cases};
416 use std::cell::Cell;
417
418 #[test]
419 fn queries_and_caches() {
420 let mut rng = Xorshift::default();
421 let mut input_rng = Xorshift::default();
422 for takes in sequence_cases(&mut input_rng, 1..=8, 0..=8) {
423 let edges: Vec<Vec<_>> = (0usize..65)
424 .map(|n| {
425 takes
426 .iter()
427 .filter_map(|&take| n.checked_sub(take).map(|to| Neighbor::new(to, take)))
428 .collect()
429 })
430 .collect();
431 let calls = Cell::new(0);
432 let mut game = GameAnalyzer::new().impartial_moves(|&n: &usize| {
433 calls.set(calls.get() + 1);
434 edges[n].iter().copied()
435 });
436 let mut grundy = [0u64; 65];
437 let mut misere = [true; 65];
438 let mut heights = [0usize; 65];
439 for (n, moves) in edges.iter().enumerate() {
440 grundy[n] = (0..)
441 .find(|g| moves.iter().all(|m| grundy[m.to] != *g))
442 .unwrap();
443 misere[n] = moves.is_empty() || moves.iter().any(|m| !misere[m.to]);
444 heights[n] = moves.iter().map(|m| heights[m.to] + 1).max().unwrap_or(0);
445 }
446 let states = [64, 0, 64, rng.random(0..65)];
447 assert_eq!(game.grundy().table(states), states.map(|s| (s, grundy[s])));
448 assert_eq!(game.grundy().verify(0..65, |&s| grundy[s]), None);
449 assert_eq!(calls.get(), 65);
450 let bad = rng.random(1..64);
451 let later = rng.random(bad + 1..65);
452 let mut checked = 0;
453 let mismatch = game
454 .grundy()
455 .verify(0..65, |&s| {
456 checked += 1;
457 grundy[s] ^ u64::from(s == bad || s == later)
458 })
459 .unwrap();
460 assert_eq!(
461 (mismatch.state, mismatch.expected, mismatch.actual),
462 (bad, grundy[bad] ^ 1, grundy[bad])
463 );
464 assert_eq!(calls.get(), 65);
465 assert_eq!(checked, bad + 1);
466 for (n, moves) in edges.iter().enumerate() {
467 let [normal, misere_outcome] = [grundy[n] != 0, misere[n]].map(|win| {
468 if win {
469 GameOutcome::Win
470 } else {
471 GameOutcome::Lose
472 }
473 });
474 assert_eq!(game.outcome().eval(&n), normal);
475 assert_eq!(game.misere().outcome().eval(&n), misere_outcome);
476 assert_eq!(game.outcome().eval(&n), normal);
477 assert_eq!(game.value().eval(&n).as_nimber(), Some(grundy[n]));
478 let winning: Vec<_> = moves
479 .iter()
480 .filter(|m| grundy[m.to] == 0)
481 .copied()
482 .collect();
483 assert_eq!(game.grundy().winning_moves(&n), winning);
484 assert_eq!(game.value().winning_moves(&n), winning);
485 assert_eq!(game.outcome().winning_moves(&n), winning);
486 assert_eq!(
487 game.misere().outcome().winning_moves(&n),
488 moves
489 .iter()
490 .filter(|m| !misere[m.to])
491 .copied()
492 .collect::<Vec<_>>()
493 );
494 }
495 let mut height = game.fold(|_: &usize, children: &[(usize, usize)]| {
496 children.iter().map(|(_, h)| h + 1).max().unwrap_or(0)
497 });
498 assert_eq!(height.verify(0..65, |&s| heights[s]), None);
499 for (n, moves) in edges.iter().enumerate() {
500 let expected: Vec<_> = moves
501 .iter()
502 .map(|&next| EvaluatedMove {
503 next,
504 value: heights[next.to],
505 })
506 .collect();
507 assert_eq!(height.moves(&n), expected);
508 }
509 drop(height);
510 let mut order = game.fold(|_: &usize, children: &[(usize, Vec<usize>)]| {
511 children
512 .iter()
513 .flat_map(|(label, path)| std::iter::once(*label).chain(path.iter().copied()))
514 .collect()
515 });
516 for n in 0usize..=4 {
518 let mut expected = Vec::new();
519 let mut stack = vec![(n, 0)];
520 while let Some((s, label)) = stack.pop() {
521 if label != 0 {
522 expected.push(label);
523 }
524 stack.extend(edges[s].iter().rev().map(|m| (m.to, m.label)));
525 }
526 assert_eq!(order.eval(&n), expected);
527 }
528 drop(order);
529 game.clear_cache();
530 let before = calls.get();
531 assert_eq!(game.grundy().verify(0..65, |&s| grundy[s]), None);
532 assert_eq!(calls.get() - before, 65);
533 game.clear_cache();
534 for n in (0..65).rev() {
536 assert_eq!(game.outcome().eval(&n) == GameOutcome::Win, grundy[n] != 0);
537 assert_eq!(
538 game.misere().outcome().eval(&n) == GameOutcome::Win,
539 misere[n]
540 );
541 assert_eq!(game.grundy().eval(&n), grundy[n]);
542 }
543 }
544 }
545
546 #[test]
547 fn cached_children() {
548 for n in [0usize, 1, 32, 33] {
549 let calls = Cell::new(0);
550 let mut game = GameAnalyzer::new().impartial(|&s: &usize| {
551 calls.set(calls.get() + 1);
552 s.checked_sub(1)
553 });
554 game.grundy().eval(&n);
555 calls.set(0);
556 let normal = if n % 2 == 0 {
557 GameOutcome::Win
558 } else {
559 GameOutcome::Lose
560 };
561 assert_eq!(game.outcome().eval(&(n + 1)), normal);
562 assert_eq!(calls.get(), 1);
563 calls.set(0);
564 let misere = if n % 2 == 0 {
565 GameOutcome::Lose
566 } else {
567 GameOutcome::Win
568 };
569 assert_eq!(game.misere().outcome().eval(&(n + 1)), misere);
570 assert_eq!(calls.get(), n + 2);
571 game.clear_cache();
572 game.outcome().eval(&n);
573 calls.set(0);
574 assert_eq!(game.grundy().eval(&(n + 1)), (n as u64 + 1) % 2);
575 assert_eq!(calls.get(), 1 + n % 2);
576 }
577 }
578
579 #[test]
580 fn shortcuts() {
581 for n in [1usize, 2, 31, 32] {
582 let transitions = Cell::new(0);
583 let mut game = GameAnalyzer::new().impartial_moves(|&s: &usize| {
584 let transitions = &transitions;
585 (0..s).map(move |to| {
586 transitions.set(transitions.get() + 1);
587 Neighbor::new(to, s - to)
588 })
589 });
590 assert_eq!(game.sum().grundy().eval(&[n, n]), 0);
591 assert_eq!(game.sum().outcome().eval(&[n, n]), GameOutcome::Lose);
592 assert_eq!(transitions.get(), 0);
593 for query in 0..3 {
594 let winning = match query {
595 0 => game.grundy().winning_moves(&n),
596 1 => game.value().winning_moves(&n),
597 _ => game.outcome().winning_moves(&n),
598 };
599 assert_eq!(winning, vec![Neighbor::new(0, n)]);
600 assert_eq!(transitions.get(), 2 * n - 1);
601 game.clear_cache();
602 transitions.set(0);
603 }
604 assert_eq!(
605 game.sum().outcome().winning_moves(&[n]),
606 vec![Neighbor::new(vec![0], (0, n))]
607 );
608 assert_eq!(transitions.get(), 2 * n - 1);
609 game.clear_cache();
610 transitions.set(0);
611 assert_eq!(game.sum().outcome().eval(&[n, 1, n]), GameOutcome::Win);
612 assert_eq!(transitions.get(), 1);
613 assert_eq!(game.sum().grundy().eval(&[n, 1, n]), 1);
614 }
615 }
616}