Skip to main content

GameAnalysis

Struct GameAnalysis 

Source
pub struct GameAnalysis<'a, G, S, V, const MISERE: bool = false, F = fn(&mut G, &S) -> V>
where S: ?Sized,
{ game: &'a mut G, evaluate: F, marker: PhantomData<fn(&S) -> V>, }

Fields§

§game: &'a mut G§evaluate: F§marker: PhantomData<fn(&S) -> V>

Implementations§

Source§

impl<'g, S, L, G, V, const MISERE: bool, E> GameAnalysis<'_, ImpartialGraph<'g, G>, S, V, MISERE, E>
where S: Copy, G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>, E: FnMut(&mut ImpartialGraph<'g, G>, &S) -> V,

Source

pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, V>>

Values describe the destination position; outcomes are for the next player.

Source§

impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, S, u64>
where S: Copy, G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,

Source

pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>

Source§

impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, S, GameValue>
where S: Copy, G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,

Source

pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>

Source§

impl<S, L, G, const MISERE: bool> GameAnalysis<'_, ImpartialGraph<'_, G>, S, GameOutcome, MISERE>
where S: Copy, G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,

Source

pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>

Moves to losing positions. To avoid cycling among winning positions, repeatedly use best_move, which follows the retrograde proof.

Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/graph.rs (line 282)
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    }
Source

pub fn best_move(&mut self, state: &S) -> Option<Neighbor<S, L>>

A move that guarantees termination when winning, or preserves a draw. Returns None at terminal or losing positions.

Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/graph.rs (line 343)
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    }
Source§

impl<'g, S, L, G, V, const MISERE: bool, E> GameAnalysis<'_, ImpartialGraph<'g, G>, [S], V, MISERE, E>
where S: Copy, G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>, E: FnMut(&mut ImpartialGraph<'g, G>, &[S]) -> V,

Source

pub fn moves( &mut self, state: &[S], ) -> Vec<EvaluatedMove<Vec<S>, (usize, L), V>>

Source§

impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], u64>
where S: Copy, G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,

Source

pub fn winning_moves( &mut self, state: &[S], ) -> Vec<Neighbor<Vec<S>, (usize, L)>>

Source§

impl<S, L, G, const MISERE: bool> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], GameOutcome, MISERE>
where S: Copy, G: Graph<Vertex = S, Label = L> + VertexMap<Option<usize>>,

Source

pub fn winning_moves( &mut self, state: &[S], ) -> Vec<Neighbor<Vec<S>, (usize, L)>>

Moves to losing positions; use best_move to avoid cycling.

Source

pub fn best_move(&mut self, state: &[S]) -> Option<Neighbor<Vec<S>, (usize, L)>>

A move that guarantees termination when winning, or preserves a draw. Returns None at terminal or losing positions.

Source§

impl<S, L, F, I, V, const MISERE: bool, E> GameAnalysis<'_, Impartial<S, F>, S, V, MISERE, E>
where F: FnMut(&S) -> I, I: IntoIterator<Item = Neighbor<S, L>>, E: FnMut(&mut Impartial<S, F>, &S) -> V,

Source

pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, V>>

Source§

impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, u64>
where S: Clone + Eq + Hash, F: FnMut(&S) -> I, I: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>

Source§

impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, GameValue>
where S: Clone + Eq + Hash, F: FnMut(&S) -> I, I: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>

Source§

impl<S, L, F, I, const MISERE: bool> GameAnalysis<'_, Impartial<S, F>, S, GameOutcome, MISERE>
where S: Clone + Eq + Hash, F: FnMut(&S) -> I, I: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>

Source§

impl<S, L, F, I, V, const MISERE: bool, E> GameAnalysis<'_, Impartial<S, F>, [S], V, MISERE, E>
where S: Clone, F: FnMut(&S) -> I, I: IntoIterator<Item = Neighbor<S, L>>, E: FnMut(&mut Impartial<S, F>, &[S]) -> V,

Source

pub fn moves( &mut self, state: &[S], ) -> Vec<EvaluatedMove<Vec<S>, (usize, L), V>>

Source§

impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, [S], u64>
where S: Clone + Eq + Hash, F: FnMut(&S) -> I, I: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn winning_moves( &mut self, state: &[S], ) -> Vec<Neighbor<Vec<S>, (usize, L)>>

Source§

impl<S, L, F, I, const MISERE: bool> GameAnalysis<'_, Impartial<S, F>, [S], GameOutcome, MISERE>
where S: Clone + Eq + Hash, F: FnMut(&S) -> I, I: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn winning_moves( &mut self, state: &[S], ) -> Vec<Neighbor<Vec<S>, (usize, L)>>

Source§

impl<S, L, FL, FR, IL, IR, V, const MISERE: bool, E> GameAnalysis<'_, Partisan<S, FL, FR>, S, V, MISERE, E>
where FL: FnMut(&S) -> IL, FR: FnMut(&S) -> IR, IL: IntoIterator<Item = Neighbor<S, L>>, IR: IntoIterator<Item = Neighbor<S, L>>, E: FnMut(&mut Partisan<S, FL, FR>, &S) -> V,

Source

pub fn moves( &mut self, state: &S, player: Player, ) -> Vec<EvaluatedMove<S, L, V>>

Values describe the destination game for both choices of starting player.

Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 235)
234    pub fn undominated_moves(&mut self, state: &S, player: Player) -> Vec<Neighbor<S, L>> {
235        let moves = self.moves(state, player);
236        let preferred = if player == Player::Left {
237            Ordering::Greater
238        } else {
239            Ordering::Less
240        };
241        let best = moves.iter().try_fold(None, |best, m| {
242            let (q, k, n) = m.value.as_number_up_nimber()?;
243            let pair = (q, k);
244            Some(Some(match best {
245                Some((previous, nimber)) => match pair.cmp(&previous) {
246                    Ordering::Equal => (previous, if nimber == Some(n) { nimber } else { None }),
247                    order if order == preferred => (pair, Some(n)),
248                    _ => (previous, nimber),
249                },
250                None => (pair, Some(n)),
251            }))
252        });
253        if let Some(best) = best {
254            return moves
255                .into_iter()
256                .filter(|m| {
257                    let (q, k, n) = m.value.as_number_up_nimber().unwrap();
258                    best.is_some_and(|((b, l), nimber)| {
259                        q == b && (k == l || k.abs_diff(l) == 1 && nimber == Some(n ^ 1))
260                    })
261                })
262                .map(|m| m.next)
263                .collect();
264        }
265        let mut dominated = vec![false; moves.len()];
266        for i in 0..moves.len() {
267            if dominated[i] {
268                continue;
269            }
270            for j in i + 1..moves.len() {
271                if dominated[j] {
272                    continue;
273                }
274                let order = moves[i].value.partial_cmp(&moves[j].value);
275                if order == Some(preferred) {
276                    dominated[j] = true;
277                } else if order == Some(preferred.reverse()) {
278                    dominated[i] = true;
279                    break;
280                }
281            }
282        }
283        moves
284            .into_iter()
285            .zip(dominated)
286            .filter(|(_, d)| !d)
287            .map(|(m, _)| m.next)
288            .collect()
289    }
Source§

impl<S, L, FL, FR, IL, IR> GameAnalysis<'_, Partisan<S, FL, FR>, S, GameValue>
where FL: FnMut(&S) -> IL, FR: FnMut(&S) -> IR, IL: IntoIterator<Item = Neighbor<S, L>>, IR: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn compare(&mut self, left: &S, right: &S) -> Option<Ordering>

Source

pub fn undominated_moves( &mut self, state: &S, player: Player, ) -> Vec<Neighbor<S, L>>

Undominated moves for the selected player; incomparable alternatives remain.

Source§

impl<S, L, FL, FR, IL, IR, const MISERE: bool> GameAnalysis<'_, Partisan<S, FL, FR>, S, PartisanOutcome, MISERE>
where S: Clone + Eq + Hash, FL: FnMut(&S) -> IL, FR: FnMut(&S) -> IR, IL: IntoIterator<Item = Neighbor<S, L>>, IR: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn wins(&mut self, state: &S, player: Player) -> bool

Whether the selected starting player can force a win.

Source

pub fn winning_moves( &mut self, state: &S, player: Player, ) -> Vec<Neighbor<S, L>>

Source§

impl<S, L, FL, FR, IL, IR, V, const MISERE: bool, E> GameAnalysis<'_, Partisan<S, FL, FR>, [S], V, MISERE, E>
where S: Clone, FL: FnMut(&S) -> IL, FR: FnMut(&S) -> IR, IL: IntoIterator<Item = Neighbor<S, L>>, IR: IntoIterator<Item = Neighbor<S, L>>, E: FnMut(&mut Partisan<S, FL, FR>, &[S]) -> V,

Source

pub fn moves( &mut self, state: &[S], player: Player, ) -> Vec<EvaluatedMove<Vec<S>, (usize, L), V>>

Source§

impl<S, L, FL, FR, IL, IR, const MISERE: bool> GameAnalysis<'_, Partisan<S, FL, FR>, [S], PartisanOutcome, MISERE>
where S: Clone + Eq + Hash, FL: FnMut(&S) -> IL, FR: FnMut(&S) -> IR, IL: IntoIterator<Item = Neighbor<S, L>>, IR: IntoIterator<Item = Neighbor<S, L>>,

Source

pub fn wins(&mut self, state: &[S], player: Player) -> bool

Whether the selected starting player can force a win.

Source

pub fn winning_moves( &mut self, state: &[S], player: Player, ) -> Vec<Neighbor<Vec<S>, (usize, L)>>

Source§

impl<S, L, F, T, I> GameAnalysis<'_, Scoring<S, F, T>, S, i64>
where F: FnMut(&S) -> I, I: IntoIterator<Item = ScoreMove<S, L>>, T: FnMut(&S) -> Option<i64>,

Source

pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, i64>>

Values include the reward and are measured for the current player.

Source

pub fn best_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>

Source

fn evaluated_moves( &mut self, state: &S, ) -> impl Iterator<Item = EvaluatedMove<S, L, i64>>

Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/scoring.rs (line 93)
92    pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, i64>> {
93        self.evaluated_moves(state).collect()
94    }
95
96    pub fn best_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>> {
97        let mut result = Vec::new();
98        let mut best = None;
99        for m in self.evaluated_moves(state) {
100            if best.is_none_or(|best| m.value > best) {
101                result.clear();
102                best = Some(m.value);
103            }
104            if best == Some(m.value) {
105                result.push(m.next);
106            }
107        }
108        result
109    }
Source§

impl<'a, G, S, V, const MISERE: bool, F> GameAnalysis<'a, G, S, V, MISERE, F>
where S: ?Sized, F: FnMut(&mut G, &S) -> V,

Source

pub fn eval(&mut self, state: &S) -> V

Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/impartial.rs (line 163)
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)] // Sum states and labels both carry component information.
202impl<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)] // Sum states and labels both carry component information.
226impl<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)] // Sum states and labels both carry component information.
237impl<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    }
More examples
Hide additional examples
crates/competitive/src/algorithm/game_analyzer.rs (line 124)
117    pub fn table<T>(&mut self, states: impl IntoIterator<Item = T>) -> Vec<(T, V)>
118    where
119        T: Borrow<S>,
120    {
121        states
122            .into_iter()
123            .map(|state| {
124                let value = self.eval(state.borrow());
125                (state, value)
126            })
127            .collect()
128    }
129
130    /// Returns the first mismatch in input order, or `None` if the range agrees.
131    pub fn verify<T>(
132        &mut self,
133        states: impl IntoIterator<Item = T>,
134        mut expected: impl FnMut(&S) -> V,
135    ) -> Option<GameMismatch<T, V>>
136    where
137        T: Borrow<S>,
138        V: PartialEq,
139    {
140        for state in states {
141            let expected = expected(state.borrow());
142            let actual = self.eval(state.borrow());
143            if actual != expected {
144                return Some(GameMismatch {
145                    state,
146                    expected,
147                    actual,
148                });
149            }
150        }
151        None
152    }
crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 215)
212    pub fn moves(&mut self, state: &S, player: Player) -> Vec<EvaluatedMove<S, L, V>> {
213        partisan_moves(&mut self.game.left, &mut self.game.right, state, player)
214            .map(|next| {
215                let value = self.eval(&next.to);
216                EvaluatedMove { next, value }
217            })
218            .collect()
219    }
220}
221
222impl<S, L, FL, FR, IL, IR> GameAnalysis<'_, Partisan<S, FL, FR>, S, GameValue>
223where
224    FL: FnMut(&S) -> IL,
225    FR: FnMut(&S) -> IR,
226    IL: IntoIterator<Item = Neighbor<S, L>>,
227    IR: IntoIterator<Item = Neighbor<S, L>>,
228{
229    pub fn compare(&mut self, left: &S, right: &S) -> Option<Ordering> {
230        self.eval(left).partial_cmp(&self.eval(right))
231    }
232
233    /// Undominated moves for the selected player; incomparable alternatives remain.
234    pub fn undominated_moves(&mut self, state: &S, player: Player) -> Vec<Neighbor<S, L>> {
235        let moves = self.moves(state, player);
236        let preferred = if player == Player::Left {
237            Ordering::Greater
238        } else {
239            Ordering::Less
240        };
241        let best = moves.iter().try_fold(None, |best, m| {
242            let (q, k, n) = m.value.as_number_up_nimber()?;
243            let pair = (q, k);
244            Some(Some(match best {
245                Some((previous, nimber)) => match pair.cmp(&previous) {
246                    Ordering::Equal => (previous, if nimber == Some(n) { nimber } else { None }),
247                    order if order == preferred => (pair, Some(n)),
248                    _ => (previous, nimber),
249                },
250                None => (pair, Some(n)),
251            }))
252        });
253        if let Some(best) = best {
254            return moves
255                .into_iter()
256                .filter(|m| {
257                    let (q, k, n) = m.value.as_number_up_nimber().unwrap();
258                    best.is_some_and(|((b, l), nimber)| {
259                        q == b && (k == l || k.abs_diff(l) == 1 && nimber == Some(n ^ 1))
260                    })
261                })
262                .map(|m| m.next)
263                .collect();
264        }
265        let mut dominated = vec![false; moves.len()];
266        for i in 0..moves.len() {
267            if dominated[i] {
268                continue;
269            }
270            for j in i + 1..moves.len() {
271                if dominated[j] {
272                    continue;
273                }
274                let order = moves[i].value.partial_cmp(&moves[j].value);
275                if order == Some(preferred) {
276                    dominated[j] = true;
277                } else if order == Some(preferred.reverse()) {
278                    dominated[i] = true;
279                    break;
280                }
281            }
282        }
283        moves
284            .into_iter()
285            .zip(dominated)
286            .filter(|(_, d)| !d)
287            .map(|(m, _)| m.next)
288            .collect()
289    }
290}
291impl<S, L, FL, FR, IL, IR, const MISERE: bool>
292    GameAnalysis<'_, Partisan<S, FL, FR>, S, PartisanOutcome, MISERE>
293where
294    S: Clone + Eq + Hash,
295    FL: FnMut(&S) -> IL,
296    FR: FnMut(&S) -> IR,
297    IL: IntoIterator<Item = Neighbor<S, L>>,
298    IR: IntoIterator<Item = Neighbor<S, L>>,
299{
300    /// Whether the selected starting player can force a win.
301    pub fn wins(&mut self, state: &S, player: Player) -> bool {
302        self.game.wins(state, player, MISERE)
303    }
304
305    pub fn winning_moves(&mut self, state: &S, player: Player) -> Vec<Neighbor<S, L>> {
306        partisan_moves(&mut self.game.left, &mut self.game.right, state, player)
307            .filter(|next| !self.game.wins(&next.to, player.opponent(), MISERE))
308            .collect()
309    }
310}
311
312#[allow(clippy::type_complexity)] // Sum states and labels both carry component information.
313impl<S, L, FL, FR, IL, IR, V, const MISERE: bool, E>
314    GameAnalysis<'_, Partisan<S, FL, FR>, [S], V, MISERE, E>
315where
316    S: Clone,
317    FL: FnMut(&S) -> IL,
318    FR: FnMut(&S) -> IR,
319    IL: IntoIterator<Item = Neighbor<S, L>>,
320    IR: IntoIterator<Item = Neighbor<S, L>>,
321    E: FnMut(&mut Partisan<S, FL, FR>, &[S]) -> V,
322{
323    pub fn moves(
324        &mut self,
325        state: &[S],
326        player: Player,
327    ) -> Vec<EvaluatedMove<Vec<S>, (usize, L), V>> {
328        let mut result = Vec::new();
329        for (i, component) in state.iter().enumerate() {
330            for next in partisan_moves(&mut self.game.left, &mut self.game.right, component, player)
331            {
332                let mut to = state.to_vec();
333                to[i] = next.to;
334                let value = self.eval(&to);
335                result.push(EvaluatedMove {
336                    next: Neighbor::new(to, (i, next.label)),
337                    value,
338                });
339            }
340        }
341        result
342    }
crates/competitive/src/algorithm/game_analyzer/graph.rs (line 139)
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    }
crates/competitive/src/algorithm/game_analyzer/scoring.rs (line 123)
111    fn evaluated_moves(&mut self, state: &S) -> impl Iterator<Item = EvaluatedMove<S, L, i64>> {
112        let terminal = (self.game.terminal)(state).is_some();
113        let mut moves = (!terminal)
114            .then(|| (self.game.moves)(state).into_iter())
115            .into_iter()
116            .flatten()
117            .peekable();
118        assert!(
119            terminal || moves.peek().is_some(),
120            "nonterminal scoring state has no moves"
121        );
122        moves.map(|m| {
123            let child = self.eval(&m.next.to);
124            let value = match m.turn {
125                Turn::Same => m.reward + child,
126                Turn::Other => m.reward - child,
127            };
128            EvaluatedMove {
129                next: m.next,
130                value,
131            }
132        })
133    }
Source

pub fn table<T>(&mut self, states: impl IntoIterator<Item = T>) -> Vec<(T, V)>
where T: Borrow<S>,

Keeps the input items, their order and duplicates; dependency states are omitted.

Source

pub fn verify<T>( &mut self, states: impl IntoIterator<Item = T>, expected: impl FnMut(&S) -> V, ) -> Option<GameMismatch<T, V>>
where T: Borrow<S>, V: PartialEq,

Returns the first mismatch in input order, or None if the range agrees.

Source

fn new(game: &'a mut G, evaluate: F) -> Self

Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/impartial.rs (line 75)
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    /// `combine` receives each move's label and the value of its destination.
87    #[allow(clippy::type_complexity)] // The query owns the fold and its cache.
88    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    }
More examples
Hide additional examples
crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 97)
96    pub fn value(&mut self) -> GameAnalysis<'_, Partisan<S, FL, FR>, S, GameValue> {
97        GameAnalysis::new(&mut self.game, Partisan::value)
98    }
99
100    pub fn outcome(&mut self) -> GameAnalysis<'_, Partisan<S, FL, FR>, S, PartisanOutcome> {
101        GameAnalysis::new(&mut self.game, |g, s| g.outcome(s, false))
102    }
103
104    /// `combine` receives all Left options followed by all Right options.
105    #[allow(clippy::type_complexity)] // The query owns the fold and its cache.
106    pub fn fold<V, C>(
107        &mut self,
108        mut combine: C,
109    ) -> GameAnalysis<
110        '_,
111        Partisan<S, FL, FR>,
112        S,
113        V,
114        false,
115        impl FnMut(&mut Partisan<S, FL, FR>, &S) -> V,
116    >
117    where
118        V: Clone,
119        C: FnMut(&S, &[(Player, L, V)]) -> V,
120    {
121        let mut cache = HashMap::new();
122        GameAnalysis::new(
123            &mut self.game,
124            move |game: &mut Partisan<S, FL, FR>, state: &S| {
125                memoized_game(
126                    state,
127                    &mut cache,
128                    &mut HashSet::new(),
129                    &mut |s| {
130                        let moves = (game.left)(s)
131                            .into_iter()
132                            .map(|n| (n.to, (Player::Left, n.label)))
133                            .chain(
134                                (game.right)(s)
135                                    .into_iter()
136                                    .map(|n| (n.to, (Player::Right, n.label))),
137                            );
138                        ControlFlow::Continue((moves, Vec::new()))
139                    },
140                    &mut |_, moves| moves.next(),
141                    &mut |values, (player, label), value| {
142                        values.push((player, label, value));
143                        ControlFlow::Continue(())
144                    },
145                    &mut |s, values| combine(s, &values),
146                )
147            },
148        )
149    }
150}
151impl<'a, S, L, FL, FR, IL, IR> Misere<'a, Partisan<S, FL, FR>>
152where
153    S: Clone + Eq + Hash,
154    FL: FnMut(&S) -> IL,
155    FR: FnMut(&S) -> IR,
156    IL: IntoIterator<Item = Neighbor<S, L>>,
157    IR: IntoIterator<Item = Neighbor<S, L>>,
158{
159    pub fn outcome(self) -> GameAnalysis<'a, Partisan<S, FL, FR>, S, PartisanOutcome, true> {
160        GameAnalysis::new(self.game, |g, s| g.outcome(s, true))
161    }
162}
163impl<'a, S, L, FL, FR, IL, IR> GameSum<'a, Partisan<S, FL, FR>>
164where
165    S: Clone + Eq + Hash,
166    FL: FnMut(&S) -> IL,
167    FR: FnMut(&S) -> IR,
168    IL: IntoIterator<Item = Neighbor<S, L>>,
169    IR: IntoIterator<Item = Neighbor<S, L>>,
170{
171    pub fn value(self) -> GameAnalysis<'a, Partisan<S, FL, FR>, [S], GameValue> {
172        GameAnalysis::new(self.game, |g, state: &[S]| {
173            state.iter().map(|s| g.value(s)).sum()
174        })
175    }
176}
177impl<'a, S, L, FL, FR, IL, IR, const MISERE: bool> GameSum<'a, Partisan<S, FL, FR>, MISERE>
178where
179    S: Clone + Eq + Hash,
180    FL: FnMut(&S) -> IL,
181    FR: FnMut(&S) -> IR,
182    IL: IntoIterator<Item = Neighbor<S, L>>,
183    IR: IntoIterator<Item = Neighbor<S, L>>,
184{
185    /// Explores reachable sum positions, including the player to move.
186    /// Reuses cached sums of numbers, ups, nimbers and switches in normal play.
187    pub fn outcome(self) -> GameAnalysis<'a, Partisan<S, FL, FR>, [S], PartisanOutcome, MISERE> {
188        GameAnalysis::new(self.game, |g, s| {
189            match (
190                g.sum_wins(s, Player::Left, MISERE),
191                g.sum_wins(s, Player::Right, MISERE),
192            ) {
193                (true, true) => PartisanOutcome::Next,
194                (false, false) => PartisanOutcome::Previous,
195                (true, false) => PartisanOutcome::Left,
196                (false, true) => PartisanOutcome::Right,
197            }
198        })
199    }
crates/competitive/src/algorithm/game_analyzer/graph.rs (line 65)
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    }
crates/competitive/src/algorithm/game_analyzer/scoring.rs (lines 57-82)
56    pub fn score(&mut self) -> GameAnalysis<'_, Scoring<S, F, T>, S, i64> {
57        GameAnalysis::new(&mut self.game, |g, state| {
58            memoized_game(
59                state,
60                &mut g.scores,
61                &mut HashSet::new(),
62                &mut |s| {
63                    if let Some(value) = (g.terminal)(s) {
64                        return ControlFlow::Break(value);
65                    }
66                    let moves = (g.moves)(s)
67                        .into_iter()
68                        .map(|m| (m.next.to, (m.reward, m.turn)));
69                    ControlFlow::Continue((moves, None))
70                },
71                &mut |_, moves| moves.next(),
72                &mut |best: &mut Option<i64>, (reward, turn), child| {
73                    let value = match turn {
74                        Turn::Same => reward + child,
75                        Turn::Other => reward - child,
76                    };
77                    *best = Some(best.map_or(value, |b| b.max(value)));
78                    ControlFlow::Continue(())
79                },
80                &mut |_, best| best.expect("nonterminal scoring state has no moves"),
81            )
82        })
83    }

Auto Trait Implementations§

§

impl<'a, G, S, V, const MISERE: bool = false, F = fn(&mut G, &S) -> V> !UnwindSafe for GameAnalysis<'a, G, S, V, MISERE, F>

§

impl<'a, G, S, V, const MISERE: bool, F> Forget for GameAnalysis<'a, G, S, V, MISERE, F>
where S: ?Sized,

§

impl<'a, G, S, V, const MISERE: bool, F> Freeze for GameAnalysis<'a, G, S, V, MISERE, F>
where &'a mut G: Freeze, F: Freeze, PhantomData<fn(&S) -> V>: Freeze, S: ?Sized,

§

impl<'a, G, S, V, const MISERE: bool, F> Move for GameAnalysis<'a, G, S, V, MISERE, F>
where S: ?Sized,

§

impl<'a, G, S, V, const MISERE: bool, F> RefUnwindSafe for GameAnalysis<'a, G, S, V, MISERE, F>

§

impl<'a, G, S, V, const MISERE: bool, F> Send for GameAnalysis<'a, G, S, V, MISERE, F>
where &'a mut G: Send, F: Send, PhantomData<fn(&S) -> V>: Send, S: ?Sized,

§

impl<'a, G, S, V, const MISERE: bool, F> Sync for GameAnalysis<'a, G, S, V, MISERE, F>
where &'a mut G: Sync, F: Sync, PhantomData<fn(&S) -> V>: Sync, S: ?Sized,

§

impl<'a, G, S, V, const MISERE: bool, F> Unpin for GameAnalysis<'a, G, S, V, MISERE, F>
where &'a mut G: Unpin, F: Unpin, PhantomData<fn(&S) -> V>: Unpin, S: ?Sized,

§

impl<'a, G, S, V, const MISERE: bool, F> UnsafeUnpin for GameAnalysis<'a, G, S, V, MISERE, F>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToArrayVecScalar for T

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.