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>
impl<'g, S, L, G, V, const MISERE: bool, E> GameAnalysis<'_, ImpartialGraph<'g, G>, S, V, MISERE, E>
Sourcepub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, V>>
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>
impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, S, u64>
pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>
Source§impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, S, GameValue>
impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, S, GameValue>
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>
impl<S, L, G, const MISERE: bool> GameAnalysis<'_, ImpartialGraph<'_, G>, S, GameOutcome, MISERE>
Sourcepub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>
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 }Sourcepub fn best_move(&mut self, state: &S) -> Option<Neighbor<S, L>>
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>
impl<'g, S, L, G, V, const MISERE: bool, E> GameAnalysis<'_, ImpartialGraph<'g, G>, [S], V, MISERE, E>
Source§impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], u64>
impl<S, L, G> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], u64>
Source§impl<S, L, G, const MISERE: bool> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], GameOutcome, MISERE>
impl<S, L, G, const MISERE: bool> GameAnalysis<'_, ImpartialGraph<'_, G>, [S], GameOutcome, MISERE>
Source§impl<S, L, F, I, V, const MISERE: bool, E> GameAnalysis<'_, Impartial<S, F>, S, V, MISERE, E>
impl<S, L, F, I, V, const MISERE: bool, E> GameAnalysis<'_, Impartial<S, F>, S, V, MISERE, E>
pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, V>>
Source§impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, u64>
impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, u64>
pub fn winning_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>
Source§impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, GameValue>
impl<S, L, F, I> GameAnalysis<'_, Impartial<S, F>, S, GameValue>
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>
impl<S, L, F, I, const MISERE: bool> GameAnalysis<'_, Impartial<S, F>, S, GameOutcome, MISERE>
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>
impl<S, L, F, I, V, const MISERE: bool, E> GameAnalysis<'_, Impartial<S, F>, [S], V, MISERE, E>
Source§impl<S, L, F, I, const MISERE: bool> GameAnalysis<'_, Impartial<S, F>, [S], GameOutcome, MISERE>
impl<S, L, F, I, const MISERE: bool> GameAnalysis<'_, Impartial<S, F>, [S], GameOutcome, MISERE>
Source§impl<S, L, FL, FR, IL, IR, V, const MISERE: bool, E> GameAnalysis<'_, Partisan<S, FL, FR>, S, V, MISERE, E>
impl<S, L, FL, FR, IL, IR, V, const MISERE: bool, E> GameAnalysis<'_, Partisan<S, FL, FR>, S, V, MISERE, E>
Sourcepub fn moves(
&mut self,
state: &S,
player: Player,
) -> Vec<EvaluatedMove<S, L, V>>
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>>,
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§impl<S, L, FL, FR, IL, IR, const MISERE: bool> GameAnalysis<'_, Partisan<S, FL, FR>, S, PartisanOutcome, MISERE>
impl<S, L, FL, FR, IL, IR, const MISERE: bool> GameAnalysis<'_, Partisan<S, FL, FR>, S, PartisanOutcome, MISERE>
Source§impl<S, L, FL, FR, IL, IR, V, const MISERE: bool, E> GameAnalysis<'_, Partisan<S, FL, FR>, [S], V, MISERE, E>
impl<S, L, FL, FR, IL, IR, V, const MISERE: bool, E> GameAnalysis<'_, Partisan<S, FL, FR>, [S], V, MISERE, E>
Source§impl<S, L, FL, FR, IL, IR, const MISERE: bool> GameAnalysis<'_, Partisan<S, FL, FR>, [S], PartisanOutcome, MISERE>
impl<S, L, FL, FR, IL, IR, const MISERE: bool> GameAnalysis<'_, Partisan<S, FL, FR>, [S], PartisanOutcome, MISERE>
Source§impl<S, L, F, T, I> GameAnalysis<'_, Scoring<S, F, T>, S, i64>
impl<S, L, F, T, I> GameAnalysis<'_, Scoring<S, F, T>, S, i64>
Sourcepub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, i64>>
pub fn moves(&mut self, state: &S) -> Vec<EvaluatedMove<S, L, i64>>
Values include the reward and are measured for the current player.
pub fn best_moves(&mut self, state: &S) -> Vec<Neighbor<S, L>>
Sourcefn evaluated_moves(
&mut self,
state: &S,
) -> impl Iterator<Item = EvaluatedMove<S, L, i64>>
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>
impl<'a, G, S, V, const MISERE: bool, F> GameAnalysis<'a, G, S, V, MISERE, F>
Sourcepub fn eval(&mut self, state: &S) -> V
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
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 }Sourcepub fn table<T>(&mut self, states: impl IntoIterator<Item = T>) -> Vec<(T, V)>where
T: Borrow<S>,
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.
Sourcepub fn verify<T>(
&mut self,
states: impl IntoIterator<Item = T>,
expected: impl FnMut(&S) -> V,
) -> Option<GameMismatch<T, V>>
pub fn verify<T>( &mut self, states: impl IntoIterator<Item = T>, expected: impl FnMut(&S) -> V, ) -> Option<GameMismatch<T, V>>
Returns the first mismatch in input order, or None if the range agrees.
Sourcefn new(game: &'a mut G, evaluate: F) -> Self
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
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>
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>where
&'a mut G: RefUnwindSafe,
F: RefUnwindSafe,
PhantomData<fn(&S) -> V>: RefUnwindSafe,
S: ?Sized,
impl<'a, G, S, V, const MISERE: bool, F> Send for GameAnalysis<'a, G, S, V, MISERE, F>
impl<'a, G, S, V, const MISERE: bool, F> Sync for GameAnalysis<'a, G, S, V, MISERE, F>
impl<'a, G, S, V, const MISERE: bool, F> Unpin for GameAnalysis<'a, G, S, V, MISERE, F>
impl<'a, G, S, V, const MISERE: bool, F> UnsafeUnpin for GameAnalysis<'a, G, S, V, MISERE, F>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more