pub struct GameValue(GameValueNode);Expand description
A canonical finite normal-play game. Numbers use Dyadic<i64>.
PartialOrd is the game order: None means fuzzy, not a numeric approximation.
use competitive::algorithm::GameValue;
let zero = GameValue::from(0);
let star = GameValue::nimber(1);
let up = GameValue::from_options([zero.clone()], [star.clone()]);
assert!(up > zero);
assert_eq!(star.partial_cmp(&zero), None);
assert_eq!(star.clone() + star, zero);Tuple Fields§
§0: GameValueNodeImplementations§
Source§impl GameValue
impl GameValue
Sourcepub fn nimber(value: u64) -> Self
pub fn nimber(value: u64) -> Self
Examples found in repository?
More examples
Sourcepub fn number_up_nimber(number: Dyadic<i64>, up: i64, nimber: u64) -> Self
pub fn number_up_nimber(number: Dyadic<i64>, up: i64, nimber: u64) -> Self
Constructs number + up * ↑ + *nimber without expanding options.
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 86)
85 pub fn nimber(value: u64) -> Self {
86 Self::number_up_nimber(0.into(), 0, value)
87 }
88
89 /// Constructs `number + up * ↑ + *nimber` without expanding options.
90 pub fn number_up_nimber(number: Dyadic<i64>, up: i64, nimber: u64) -> Self {
91 Self(GameValueNode::NumberUpNimber(number, up, nimber))
92 }
93
94 pub fn as_number(&self) -> Option<Dyadic<i64>> {
95 match self.0 {
96 GameValueNode::NumberUpNimber(n, 0, 0) => Some(n),
97 _ => None,
98 }
99 }
100
101 pub fn as_nimber(&self) -> Option<u64> {
102 match self.0 {
103 GameValueNode::NumberUpNimber(q, 0, n) if q.numerator() == 0 => Some(n),
104 _ => None,
105 }
106 }
107
108 /// Returns `(q, n)` when the value is `q + *n`.
109 pub fn as_number_nimber(&self) -> Option<(Dyadic<i64>, u64)> {
110 match self.0 {
111 GameValueNode::NumberUpNimber(q, 0, n) => Some((q, n)),
112 _ => None,
113 }
114 }
115
116 /// Returns `(q, k, n)` when the value is `q + k↑ + *n`.
117 pub fn as_number_up_nimber(&self) -> Option<(Dyadic<i64>, i64, u64)> {
118 match self.0 {
119 GameValueNode::NumberUpNimber(q, k, n) => Some((q, k, n)),
120 _ => None,
121 }
122 }
123
124 /// Returns `(q, k, n, temperatures)` for `q + k↑ + *n + Σ{t|-t}`.
125 /// Temperatures are positive and strictly decreasing; the slice may be empty.
126 #[allow(clippy::type_complexity)] // Borrows the components without a separate public type.
127 pub fn as_switch_sum(&self) -> Option<(Dyadic<i64>, i64, u64, &[Dyadic<i64>])> {
128 match &self.0 {
129 GameValueNode::NumberUpNimber(q, k, n) => Some((*q, *k, *n, &[])),
130 GameValueNode::Switches(value) => Some((
131 value.number,
132 value.up,
133 value.nimber,
134 &value.temperatures[value.start..],
135 )),
136 _ => None,
137 }
138 }
139
140 pub fn outcome(&self) -> PartisanOutcome {
141 match self.partial_cmp(&Self::from(0)) {
142 Some(Ordering::Greater) => PartisanOutcome::Left,
143 Some(Ordering::Less) => PartisanOutcome::Right,
144 Some(Ordering::Equal) => PartisanOutcome::Previous,
145 None => PartisanOutcome::Next,
146 }
147 }
148
149 /// Whether the selected starting player can force a win.
150 pub fn wins(&self, player: Player) -> bool {
151 match player {
152 Player::Left => !self.leq(&Self::from(0)),
153 Player::Right => !Self::from(0).leq(self),
154 }
155 }
156
157 /// Removes dominated and reversible options.
158 pub fn from_options(
159 left: impl IntoIterator<Item = Self>,
160 right: impl IntoIterator<Item = Self>,
161 ) -> Self {
162 Self::from_options_memoized(
163 left.into_iter().collect(),
164 right.into_iter().collect(),
165 &mut HashMap::new(),
166 )
167 }
168
169 pub fn options(&self, player: Player) -> impl Iterator<Item = Self> + '_ {
170 let mut offset = Dyadic::from(0);
171 let (single, nimbers, general) = match &self.0 {
172 GameValueNode::NumberUpNimber(n, 0, 0) => {
173 let option = if n.exponent() == 0 {
174 match player {
175 Player::Left if n.numerator() > 0 => Some(Dyadic::from(n.numerator() - 1)),
176 Player::Right if n.numerator() < 0 => Some(Dyadic::from(n.numerator() + 1)),
177 _ => None,
178 }
179 } else {
180 let numerator = (n.numerator() >> 1) + i64::from(player == Player::Right);
181 Some(Dyadic::new(numerator, n.exponent() - 1))
182 };
183 (option.map(Self::from), None, &[][..])
184 }
185 GameValueNode::NumberUpNimber(q, 0, n) => (None, Some((*q, *n)), &[][..]),
186 GameValueNode::NumberUpNimber(q, k, 1)
187 if matches!((*k, player), (1, Player::Left) | (-1, Player::Right)) =>
188 {
189 (None, Some((*q, 2)), &[][..])
190 }
191 GameValueNode::NumberUpNimber(q, k, n) => {
192 let sign = if *k > 0 { 1 } else { -1 };
193 let moves_to_number = (*k > 0) == (player == Player::Left);
194 let option = if moves_to_number {
195 Self::from(*q)
196 } else {
197 Self::number_up_nimber(*q, k - sign, n ^ 1)
198 };
199 (Some(option), None, &[][..])
200 }
201 GameValueNode::Switches(value) => {
202 let temperatures = &value.temperatures[value.start..];
203 let t = if player == Player::Left {
204 temperatures[0]
205 } else {
206 -temperatures[0]
207 };
208 let option = if temperatures.len() == 1 {
209 Self::number_up_nimber(value.number + t, value.up, value.nimber)
210 } else {
211 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
212 number: value.number + t,
213 start: value.start + 1,
214 fingerprint: value.fingerprint ^ Self::temperature_hash(temperatures[0]),
215 ..(**value).clone()
216 })))
217 };
218 (Some(option), None, &[][..])
219 }
220 GameValueNode::Options(q, options) => {
221 offset = *q;
222 let (left, right) = &**options;
223 let moves = if player == Player::Left { left } else { right };
224 (None, None, moves.as_slice())
225 }
226 };
227 single
228 .into_iter()
229 .chain(
230 nimbers
231 .into_iter()
232 .flat_map(|(q, n)| (0..n).map(move |m| Self::number_up_nimber(q, 0, m))),
233 )
234 .chain(general.iter().map(move |g| g.translated(offset)))
235 }
236
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }Sourcepub fn as_number(&self) -> Option<Dyadic<i64>>
pub fn as_number(&self) -> Option<Dyadic<i64>>
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 243)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }Sourcepub fn as_nimber(&self) -> Option<u64>
pub fn as_nimber(&self) -> Option<u64>
Examples found in repository?
More examples
crates/competitive/src/algorithm/game_analyzer/value.rs (line 713)
682 fn sum<I>(iter: I) -> Self
683 where
684 I: Iterator<Item = Self>,
685 {
686 let (mut number, mut up, mut nimber) = (Dyadic::from(0), 0, 0);
687 let mut temperatures = Vec::new();
688 let mut fingerprint = 0;
689 let mut general = Self::from(0);
690 for value in iter {
691 if let Some((q, k, n, ts)) = value.as_switch_sum() {
692 number = number + q;
693 up += k;
694 nimber ^= n;
695 temperatures.extend_from_slice(ts);
696 fingerprint ^= value.switch_hash();
697 } else {
698 general = general + value;
699 }
700 }
701 temperatures.sort_unstable_by(|a, b| b.cmp(a));
702 let mut retained = 0;
703 for i in 0..temperatures.len() {
704 if retained > 0 && temperatures[retained - 1] == temperatures[i] {
705 retained -= 1;
706 } else {
707 temperatures[retained] = temperatures[i];
708 retained += 1;
709 }
710 }
711 temperatures.truncate(retained);
712 let value = Self::from_switches(number, up, nimber, temperatures, fingerprint);
713 if general.as_nimber() == Some(0) {
714 value
715 } else {
716 value + general
717 }
718 }crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 453)
443 fn sum_wins(&mut self, state: &[S], player: Player, misere: bool) -> bool {
444 match state {
445 [] => return misere,
446 [single] => return self.wins(single, player, misere),
447 _ => {}
448 }
449 let mut normalize = |mut state: Vec<S>| {
450 if !misere {
451 state.retain(|s| {
452 if let Some(value) = self.values.get(s) {
453 return value.as_nimber() != Some(0);
454 }
455 let mut key = (s.clone(), Player::Left);
456 if self.outcomes[0].get(&key) != Some(&false) {
457 return true;
458 }
459 key.1 = Player::Right;
460 self.outcomes[0].get(&key) != Some(&false)
461 });
462 }
463 canonical_sum(state, &mut self.sum_ids)
464 };
465 let state = normalize(state.to_vec());
466 match state.as_slice() {
467 [] => return misere,
468 [single] => return self.wins(single, player, misere),
469 _ => {}
470 }
471 let key = (state, player);
472 memoized_wins(
473 &key,
474 &mut self.sums[usize::from(misere)],
475 |(s, p)| {
476 if !misere
477 && s.iter().all(|s| {
478 self.values
479 .get(s)
480 .is_some_and(|g| g.as_switch_sum().is_some())
481 })
482 {
483 let value: GameValue = s.iter().map(|s| self.values[s].clone()).sum();
484 return ControlFlow::Break(value.wins(*p));
485 }
486 // Losing as starter in every component implies losing in the sum.
487 // One known win is enough if the opponent loses in every other component.
488 if !misere && (!self.values.is_empty() || !self.outcomes[0].is_empty()) {
489 let mut all_lose = true;
490 let mut all_opponent_lose = true;
491 let mut can_win = false;
492 for s in s {
493 let (mut win, mut opponent_win) = (None, None);
494 if !self.outcomes[0].is_empty() {
495 let mut key = (s.clone(), *p);
496 win = self.outcomes[0].get(&key).copied();
497 key.1 = p.opponent();
498 opponent_win = self.outcomes[0].get(&key).copied();
499 }
500 if let Some(value) = self.values.get(s) {
501 win.get_or_insert_with(|| value.wins(*p));
502 opponent_win.get_or_insert_with(|| value.wins(p.opponent()));
503 }
504 can_win = can_win && opponent_win == Some(false)
505 || all_opponent_lose && win == Some(true);
506 all_lose &= win == Some(false);
507 all_opponent_lose &= opponent_win == Some(false);
508 if !all_lose && !all_opponent_lose && !can_win {
509 break;
510 }
511 }
512 if all_lose || can_win {
513 return ControlFlow::Break(can_win);
514 }
515 }
516 ControlFlow::Continue((0, None))
517 },
518 |(s, p), (i, moves)| {
519 while *i < s.len() {
520 if *i != 0 && s[*i - 1] == s[*i] {
521 *i += 1;
522 continue;
523 }
524 let iterator = moves.get_or_insert_with(|| {
525 partisan_moves(&mut self.left, &mut self.right, &s[*i], *p)
526 });
527 if let Some(next) = iterator.next() {
528 let mut to = s.clone();
529 to[*i] = next.to;
530 return Some((normalize(to), p.opponent()));
531 }
532 *i += 1;
533 *moves = None;
534 }
535 None
536 },
537 misere,
538 )
539 }Sourcepub fn as_number_nimber(&self) -> Option<(Dyadic<i64>, u64)>
pub fn as_number_nimber(&self) -> Option<(Dyadic<i64>, u64)>
Returns (q, n) when the value is q + *n.
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 267)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }Sourcepub fn as_number_up_nimber(&self) -> Option<(Dyadic<i64>, i64, u64)>
pub fn as_number_up_nimber(&self) -> Option<(Dyadic<i64>, i64, u64)>
Returns (q, k, n) when the value is q + k↑ + *n.
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 242)
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 }More examples
crates/competitive/src/algorithm/game_analyzer/value.rs (line 284)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }Sourcepub fn as_switch_sum(&self) -> Option<(Dyadic<i64>, i64, u64, &[Dyadic<i64>])>
pub fn as_switch_sum(&self) -> Option<(Dyadic<i64>, i64, u64, &[Dyadic<i64>])>
Returns (q, k, n, temperatures) for q + k↑ + *n + Σ{t|-t}.
Temperatures are positive and strictly decreasing; the slice may be empty.
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 480)
443 fn sum_wins(&mut self, state: &[S], player: Player, misere: bool) -> bool {
444 match state {
445 [] => return misere,
446 [single] => return self.wins(single, player, misere),
447 _ => {}
448 }
449 let mut normalize = |mut state: Vec<S>| {
450 if !misere {
451 state.retain(|s| {
452 if let Some(value) = self.values.get(s) {
453 return value.as_nimber() != Some(0);
454 }
455 let mut key = (s.clone(), Player::Left);
456 if self.outcomes[0].get(&key) != Some(&false) {
457 return true;
458 }
459 key.1 = Player::Right;
460 self.outcomes[0].get(&key) != Some(&false)
461 });
462 }
463 canonical_sum(state, &mut self.sum_ids)
464 };
465 let state = normalize(state.to_vec());
466 match state.as_slice() {
467 [] => return misere,
468 [single] => return self.wins(single, player, misere),
469 _ => {}
470 }
471 let key = (state, player);
472 memoized_wins(
473 &key,
474 &mut self.sums[usize::from(misere)],
475 |(s, p)| {
476 if !misere
477 && s.iter().all(|s| {
478 self.values
479 .get(s)
480 .is_some_and(|g| g.as_switch_sum().is_some())
481 })
482 {
483 let value: GameValue = s.iter().map(|s| self.values[s].clone()).sum();
484 return ControlFlow::Break(value.wins(*p));
485 }
486 // Losing as starter in every component implies losing in the sum.
487 // One known win is enough if the opponent loses in every other component.
488 if !misere && (!self.values.is_empty() || !self.outcomes[0].is_empty()) {
489 let mut all_lose = true;
490 let mut all_opponent_lose = true;
491 let mut can_win = false;
492 for s in s {
493 let (mut win, mut opponent_win) = (None, None);
494 if !self.outcomes[0].is_empty() {
495 let mut key = (s.clone(), *p);
496 win = self.outcomes[0].get(&key).copied();
497 key.1 = p.opponent();
498 opponent_win = self.outcomes[0].get(&key).copied();
499 }
500 if let Some(value) = self.values.get(s) {
501 win.get_or_insert_with(|| value.wins(*p));
502 opponent_win.get_or_insert_with(|| value.wins(p.opponent()));
503 }
504 can_win = can_win && opponent_win == Some(false)
505 || all_opponent_lose && win == Some(true);
506 all_lose &= win == Some(false);
507 all_opponent_lose &= opponent_win == Some(false);
508 if !all_lose && !all_opponent_lose && !can_win {
509 break;
510 }
511 }
512 if all_lose || can_win {
513 return ControlFlow::Break(can_win);
514 }
515 }
516 ControlFlow::Continue((0, None))
517 },
518 |(s, p), (i, moves)| {
519 while *i < s.len() {
520 if *i != 0 && s[*i - 1] == s[*i] {
521 *i += 1;
522 continue;
523 }
524 let iterator = moves.get_or_insert_with(|| {
525 partisan_moves(&mut self.left, &mut self.right, &s[*i], *p)
526 });
527 if let Some(next) = iterator.next() {
528 let mut to = s.clone();
529 to[*i] = next.to;
530 return Some((normalize(to), p.opponent()));
531 }
532 *i += 1;
533 *moves = None;
534 }
535 None
536 },
537 misere,
538 )
539 }More examples
crates/competitive/src/algorithm/game_analyzer/value.rs (line 305)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }
658}
659impl Neg for GameValue {
660 type Output = Self;
661 fn neg(self) -> Self {
662 self.negated(&mut HashMap::new())
663 }
664}
665impl Add for GameValue {
666 type Output = Self;
667 fn add(self, rhs: Self) -> Self {
668 self.sum(&rhs, &mut HashMap::new(), &mut HashMap::new())
669 }
670}
671impl Sub for GameValue {
672 type Output = Self;
673 fn sub(self, rhs: Self) -> Self {
674 if self.0 == rhs.0 {
675 Self::from(0)
676 } else {
677 self + -rhs
678 }
679 }
680}
681impl Sum for GameValue {
682 fn sum<I>(iter: I) -> Self
683 where
684 I: Iterator<Item = Self>,
685 {
686 let (mut number, mut up, mut nimber) = (Dyadic::from(0), 0, 0);
687 let mut temperatures = Vec::new();
688 let mut fingerprint = 0;
689 let mut general = Self::from(0);
690 for value in iter {
691 if let Some((q, k, n, ts)) = value.as_switch_sum() {
692 number = number + q;
693 up += k;
694 nimber ^= n;
695 temperatures.extend_from_slice(ts);
696 fingerprint ^= value.switch_hash();
697 } else {
698 general = general + value;
699 }
700 }
701 temperatures.sort_unstable_by(|a, b| b.cmp(a));
702 let mut retained = 0;
703 for i in 0..temperatures.len() {
704 if retained > 0 && temperatures[retained - 1] == temperatures[i] {
705 retained -= 1;
706 } else {
707 temperatures[retained] = temperatures[i];
708 retained += 1;
709 }
710 }
711 temperatures.truncate(retained);
712 let value = Self::from_switches(number, up, nimber, temperatures, fingerprint);
713 if general.as_nimber() == Some(0) {
714 value
715 } else {
716 value + general
717 }
718 }
719}
720impl PartialEq for GameValue {
721 fn eq(&self, rhs: &Self) -> bool {
722 match (&self.0, &rhs.0) {
723 (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) => {
724 return a == b && k == l && n == m;
725 }
726 (GameValueNode::Switches(_), GameValueNode::Switches(_)) => {
727 return self.0 == rhs.0;
728 }
729 _ => {}
730 }
731 let mut cache = HashMap::new();
732 self.leq_memoized(rhs, &mut cache) && rhs.leq_memoized(self, &mut cache)
733 }
734}
735impl Eq for GameValue {}
736impl PartialOrd for GameValue {
737 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
738 let mut cache = HashMap::new();
739 match (
740 self.leq_memoized(rhs, &mut cache),
741 rhs.leq_memoized(self, &mut cache),
742 ) {
743 (true, true) => Some(Ordering::Equal),
744 (true, false) => Some(Ordering::Less),
745 (false, true) => Some(Ordering::Greater),
746 (false, false) => None,
747 }
748 }
749}
750impl fmt::Display for GameValue {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 if let Some((q, k, n, temperatures)) = self.as_switch_sum() {
753 let mut separator = "";
754 if q.numerator() != 0 || k == 0 && n == 0 && temperatures.is_empty() {
755 write!(f, "{q}")?;
756 separator = "+";
757 }
758 if k != 0 {
759 f.write_str(separator)?;
760 if k.unsigned_abs() != 1 {
761 write!(f, "{}", k.unsigned_abs())?;
762 }
763 f.write_str(if k > 0 { "↑" } else { "↓" })?;
764 separator = "+";
765 }
766 if n != 0 {
767 write!(f, "{separator}*")?;
768 if n != 1 {
769 write!(f, "{n}")?;
770 }
771 separator = "+";
772 }
773 for t in temperatures {
774 write!(f, "{separator}{{{t}|{}}}", -*t)?;
775 separator = "+";
776 }
777 return Ok(());
778 }
779 f.write_str("{")?;
780 for (separator, player) in [("", Player::Left), ("|", Player::Right)] {
781 f.write_str(separator)?;
782 for (i, g) in self.options(player).enumerate() {
783 if i > 0 {
784 f.write_str(",")?;
785 }
786 write!(f, "{g}")?;
787 }
788 }
789 f.write_str("}")
790 }pub fn outcome(&self) -> PartisanOutcome
Sourcepub fn wins(&self, player: Player) -> bool
pub fn wins(&self, player: Player) -> bool
Whether the selected starting player can force a win.
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 431)
424 fn wins(&mut self, state: &S, player: Player, misere: bool) -> bool {
425 let key = (state.clone(), player);
426 memoized_wins(
427 &key,
428 &mut self.outcomes[usize::from(misere)],
429 |(s, p)| {
430 if !misere && let Some(value) = self.values.get(s) {
431 return ControlFlow::Break(value.wins(*p));
432 }
433 let next_player = p.opponent();
434 let moves = partisan_moves(&mut self.left, &mut self.right, s, *p)
435 .map(move |n| (n.to, next_player));
436 ControlFlow::Continue(moves)
437 },
438 |_, moves| moves.next(),
439 misere,
440 )
441 }
442
443 fn sum_wins(&mut self, state: &[S], player: Player, misere: bool) -> bool {
444 match state {
445 [] => return misere,
446 [single] => return self.wins(single, player, misere),
447 _ => {}
448 }
449 let mut normalize = |mut state: Vec<S>| {
450 if !misere {
451 state.retain(|s| {
452 if let Some(value) = self.values.get(s) {
453 return value.as_nimber() != Some(0);
454 }
455 let mut key = (s.clone(), Player::Left);
456 if self.outcomes[0].get(&key) != Some(&false) {
457 return true;
458 }
459 key.1 = Player::Right;
460 self.outcomes[0].get(&key) != Some(&false)
461 });
462 }
463 canonical_sum(state, &mut self.sum_ids)
464 };
465 let state = normalize(state.to_vec());
466 match state.as_slice() {
467 [] => return misere,
468 [single] => return self.wins(single, player, misere),
469 _ => {}
470 }
471 let key = (state, player);
472 memoized_wins(
473 &key,
474 &mut self.sums[usize::from(misere)],
475 |(s, p)| {
476 if !misere
477 && s.iter().all(|s| {
478 self.values
479 .get(s)
480 .is_some_and(|g| g.as_switch_sum().is_some())
481 })
482 {
483 let value: GameValue = s.iter().map(|s| self.values[s].clone()).sum();
484 return ControlFlow::Break(value.wins(*p));
485 }
486 // Losing as starter in every component implies losing in the sum.
487 // One known win is enough if the opponent loses in every other component.
488 if !misere && (!self.values.is_empty() || !self.outcomes[0].is_empty()) {
489 let mut all_lose = true;
490 let mut all_opponent_lose = true;
491 let mut can_win = false;
492 for s in s {
493 let (mut win, mut opponent_win) = (None, None);
494 if !self.outcomes[0].is_empty() {
495 let mut key = (s.clone(), *p);
496 win = self.outcomes[0].get(&key).copied();
497 key.1 = p.opponent();
498 opponent_win = self.outcomes[0].get(&key).copied();
499 }
500 if let Some(value) = self.values.get(s) {
501 win.get_or_insert_with(|| value.wins(*p));
502 opponent_win.get_or_insert_with(|| value.wins(p.opponent()));
503 }
504 can_win = can_win && opponent_win == Some(false)
505 || all_opponent_lose && win == Some(true);
506 all_lose &= win == Some(false);
507 all_opponent_lose &= opponent_win == Some(false);
508 if !all_lose && !all_opponent_lose && !can_win {
509 break;
510 }
511 }
512 if all_lose || can_win {
513 return ControlFlow::Break(can_win);
514 }
515 }
516 ControlFlow::Continue((0, None))
517 },
518 |(s, p), (i, moves)| {
519 while *i < s.len() {
520 if *i != 0 && s[*i - 1] == s[*i] {
521 *i += 1;
522 continue;
523 }
524 let iterator = moves.get_or_insert_with(|| {
525 partisan_moves(&mut self.left, &mut self.right, &s[*i], *p)
526 });
527 if let Some(next) = iterator.next() {
528 let mut to = s.clone();
529 to[*i] = next.to;
530 return Some((normalize(to), p.opponent()));
531 }
532 *i += 1;
533 *moves = None;
534 }
535 None
536 },
537 misere,
538 )
539 }Sourcepub fn from_options(
left: impl IntoIterator<Item = Self>,
right: impl IntoIterator<Item = Self>,
) -> Self
pub fn from_options( left: impl IntoIterator<Item = Self>, right: impl IntoIterator<Item = Self>, ) -> Self
Removes dominated and reversible options.
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/partisan.rs (line 408)
387 fn value(&mut self, state: &S) -> GameValue {
388 memoized_game(
389 state,
390 &mut self.values,
391 &mut HashSet::new(),
392 &mut |s| {
393 let moves = (self.left)(s)
394 .into_iter()
395 .map(|n| (n.to, Player::Left))
396 .chain((self.right)(s).into_iter().map(|n| (n.to, Player::Right)));
397 ControlFlow::Continue((moves, (Vec::new(), Vec::new())))
398 },
399 &mut |_, moves| moves.next(),
400 &mut |(left, right), player, value| {
401 if player == Player::Left {
402 left.push(value)
403 } else {
404 right.push(value)
405 }
406 ControlFlow::Continue(())
407 },
408 &mut |_, (left, right)| GameValue::from_options(left, right),
409 )
410 }Sourcepub fn options(&self, player: Player) -> impl Iterator<Item = Self> + '_
pub fn options(&self, player: Player) -> impl Iterator<Item = Self> + '_
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 354)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }
658}
659impl Neg for GameValue {
660 type Output = Self;
661 fn neg(self) -> Self {
662 self.negated(&mut HashMap::new())
663 }
664}
665impl Add for GameValue {
666 type Output = Self;
667 fn add(self, rhs: Self) -> Self {
668 self.sum(&rhs, &mut HashMap::new(), &mut HashMap::new())
669 }
670}
671impl Sub for GameValue {
672 type Output = Self;
673 fn sub(self, rhs: Self) -> Self {
674 if self.0 == rhs.0 {
675 Self::from(0)
676 } else {
677 self + -rhs
678 }
679 }
680}
681impl Sum for GameValue {
682 fn sum<I>(iter: I) -> Self
683 where
684 I: Iterator<Item = Self>,
685 {
686 let (mut number, mut up, mut nimber) = (Dyadic::from(0), 0, 0);
687 let mut temperatures = Vec::new();
688 let mut fingerprint = 0;
689 let mut general = Self::from(0);
690 for value in iter {
691 if let Some((q, k, n, ts)) = value.as_switch_sum() {
692 number = number + q;
693 up += k;
694 nimber ^= n;
695 temperatures.extend_from_slice(ts);
696 fingerprint ^= value.switch_hash();
697 } else {
698 general = general + value;
699 }
700 }
701 temperatures.sort_unstable_by(|a, b| b.cmp(a));
702 let mut retained = 0;
703 for i in 0..temperatures.len() {
704 if retained > 0 && temperatures[retained - 1] == temperatures[i] {
705 retained -= 1;
706 } else {
707 temperatures[retained] = temperatures[i];
708 retained += 1;
709 }
710 }
711 temperatures.truncate(retained);
712 let value = Self::from_switches(number, up, nimber, temperatures, fingerprint);
713 if general.as_nimber() == Some(0) {
714 value
715 } else {
716 value + general
717 }
718 }
719}
720impl PartialEq for GameValue {
721 fn eq(&self, rhs: &Self) -> bool {
722 match (&self.0, &rhs.0) {
723 (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) => {
724 return a == b && k == l && n == m;
725 }
726 (GameValueNode::Switches(_), GameValueNode::Switches(_)) => {
727 return self.0 == rhs.0;
728 }
729 _ => {}
730 }
731 let mut cache = HashMap::new();
732 self.leq_memoized(rhs, &mut cache) && rhs.leq_memoized(self, &mut cache)
733 }
734}
735impl Eq for GameValue {}
736impl PartialOrd for GameValue {
737 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
738 let mut cache = HashMap::new();
739 match (
740 self.leq_memoized(rhs, &mut cache),
741 rhs.leq_memoized(self, &mut cache),
742 ) {
743 (true, true) => Some(Ordering::Equal),
744 (true, false) => Some(Ordering::Less),
745 (false, true) => Some(Ordering::Greater),
746 (false, false) => None,
747 }
748 }
749}
750impl fmt::Display for GameValue {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 if let Some((q, k, n, temperatures)) = self.as_switch_sum() {
753 let mut separator = "";
754 if q.numerator() != 0 || k == 0 && n == 0 && temperatures.is_empty() {
755 write!(f, "{q}")?;
756 separator = "+";
757 }
758 if k != 0 {
759 f.write_str(separator)?;
760 if k.unsigned_abs() != 1 {
761 write!(f, "{}", k.unsigned_abs())?;
762 }
763 f.write_str(if k > 0 { "↑" } else { "↓" })?;
764 separator = "+";
765 }
766 if n != 0 {
767 write!(f, "{separator}*")?;
768 if n != 1 {
769 write!(f, "{n}")?;
770 }
771 separator = "+";
772 }
773 for t in temperatures {
774 write!(f, "{separator}{{{t}|{}}}", -*t)?;
775 separator = "+";
776 }
777 return Ok(());
778 }
779 f.write_str("{")?;
780 for (separator, player) in [("", Player::Left), ("|", Player::Right)] {
781 f.write_str(separator)?;
782 for (i, g) in self.options(player).enumerate() {
783 if i > 0 {
784 f.write_str(",")?;
785 }
786 write!(f, "{g}")?;
787 }
788 }
789 f.write_str("}")
790 }Sourcefn from_options_memoized(
left: Vec<Self>,
right: Vec<Self>,
cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
) -> Self
fn from_options_memoized( left: Vec<Self>, right: Vec<Self>, cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>, ) -> Self
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (lines 162-166)
158 pub fn from_options(
159 left: impl IntoIterator<Item = Self>,
160 right: impl IntoIterator<Item = Self>,
161 ) -> Self {
162 Self::from_options_memoized(
163 left.into_iter().collect(),
164 right.into_iter().collect(),
165 &mut HashMap::new(),
166 )
167 }
168
169 pub fn options(&self, player: Player) -> impl Iterator<Item = Self> + '_ {
170 let mut offset = Dyadic::from(0);
171 let (single, nimbers, general) = match &self.0 {
172 GameValueNode::NumberUpNimber(n, 0, 0) => {
173 let option = if n.exponent() == 0 {
174 match player {
175 Player::Left if n.numerator() > 0 => Some(Dyadic::from(n.numerator() - 1)),
176 Player::Right if n.numerator() < 0 => Some(Dyadic::from(n.numerator() + 1)),
177 _ => None,
178 }
179 } else {
180 let numerator = (n.numerator() >> 1) + i64::from(player == Player::Right);
181 Some(Dyadic::new(numerator, n.exponent() - 1))
182 };
183 (option.map(Self::from), None, &[][..])
184 }
185 GameValueNode::NumberUpNimber(q, 0, n) => (None, Some((*q, *n)), &[][..]),
186 GameValueNode::NumberUpNimber(q, k, 1)
187 if matches!((*k, player), (1, Player::Left) | (-1, Player::Right)) =>
188 {
189 (None, Some((*q, 2)), &[][..])
190 }
191 GameValueNode::NumberUpNimber(q, k, n) => {
192 let sign = if *k > 0 { 1 } else { -1 };
193 let moves_to_number = (*k > 0) == (player == Player::Left);
194 let option = if moves_to_number {
195 Self::from(*q)
196 } else {
197 Self::number_up_nimber(*q, k - sign, n ^ 1)
198 };
199 (Some(option), None, &[][..])
200 }
201 GameValueNode::Switches(value) => {
202 let temperatures = &value.temperatures[value.start..];
203 let t = if player == Player::Left {
204 temperatures[0]
205 } else {
206 -temperatures[0]
207 };
208 let option = if temperatures.len() == 1 {
209 Self::number_up_nimber(value.number + t, value.up, value.nimber)
210 } else {
211 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
212 number: value.number + t,
213 start: value.start + 1,
214 fingerprint: value.fingerprint ^ Self::temperature_hash(temperatures[0]),
215 ..(**value).clone()
216 })))
217 };
218 (Some(option), None, &[][..])
219 }
220 GameValueNode::Options(q, options) => {
221 offset = *q;
222 let (left, right) = &**options;
223 let moves = if player == Player::Left { left } else { right };
224 (None, None, moves.as_slice())
225 }
226 };
227 single
228 .into_iter()
229 .chain(
230 nimbers
231 .into_iter()
232 .flat_map(|(q, n)| (0..n).map(move |m| Self::number_up_nimber(q, 0, m))),
233 )
234 .chain(general.iter().map(move |g| g.translated(offset)))
235 }
236
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }Sourcefn leq_memoized(
&self,
rhs: &Self,
cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
) -> bool
fn leq_memoized( &self, rhs: &Self, cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>, ) -> bool
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 350)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }
658}
659impl Neg for GameValue {
660 type Output = Self;
661 fn neg(self) -> Self {
662 self.negated(&mut HashMap::new())
663 }
664}
665impl Add for GameValue {
666 type Output = Self;
667 fn add(self, rhs: Self) -> Self {
668 self.sum(&rhs, &mut HashMap::new(), &mut HashMap::new())
669 }
670}
671impl Sub for GameValue {
672 type Output = Self;
673 fn sub(self, rhs: Self) -> Self {
674 if self.0 == rhs.0 {
675 Self::from(0)
676 } else {
677 self + -rhs
678 }
679 }
680}
681impl Sum for GameValue {
682 fn sum<I>(iter: I) -> Self
683 where
684 I: Iterator<Item = Self>,
685 {
686 let (mut number, mut up, mut nimber) = (Dyadic::from(0), 0, 0);
687 let mut temperatures = Vec::new();
688 let mut fingerprint = 0;
689 let mut general = Self::from(0);
690 for value in iter {
691 if let Some((q, k, n, ts)) = value.as_switch_sum() {
692 number = number + q;
693 up += k;
694 nimber ^= n;
695 temperatures.extend_from_slice(ts);
696 fingerprint ^= value.switch_hash();
697 } else {
698 general = general + value;
699 }
700 }
701 temperatures.sort_unstable_by(|a, b| b.cmp(a));
702 let mut retained = 0;
703 for i in 0..temperatures.len() {
704 if retained > 0 && temperatures[retained - 1] == temperatures[i] {
705 retained -= 1;
706 } else {
707 temperatures[retained] = temperatures[i];
708 retained += 1;
709 }
710 }
711 temperatures.truncate(retained);
712 let value = Self::from_switches(number, up, nimber, temperatures, fingerprint);
713 if general.as_nimber() == Some(0) {
714 value
715 } else {
716 value + general
717 }
718 }
719}
720impl PartialEq for GameValue {
721 fn eq(&self, rhs: &Self) -> bool {
722 match (&self.0, &rhs.0) {
723 (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) => {
724 return a == b && k == l && n == m;
725 }
726 (GameValueNode::Switches(_), GameValueNode::Switches(_)) => {
727 return self.0 == rhs.0;
728 }
729 _ => {}
730 }
731 let mut cache = HashMap::new();
732 self.leq_memoized(rhs, &mut cache) && rhs.leq_memoized(self, &mut cache)
733 }
734}
735impl Eq for GameValue {}
736impl PartialOrd for GameValue {
737 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
738 let mut cache = HashMap::new();
739 match (
740 self.leq_memoized(rhs, &mut cache),
741 rhs.leq_memoized(self, &mut cache),
742 ) {
743 (true, true) => Some(Ordering::Equal),
744 (true, false) => Some(Ordering::Less),
745 (false, true) => Some(Ordering::Greater),
746 (false, false) => None,
747 }
748 }Sourcefn negated(&self, cache: &mut HashMap<usize, Self>) -> Self
fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 470)
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }
658}
659impl Neg for GameValue {
660 type Output = Self;
661 fn neg(self) -> Self {
662 self.negated(&mut HashMap::new())
663 }Sourcefn sum(
&self,
rhs: &Self,
cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
) -> Self
fn sum( &self, rhs: &Self, cache: &mut HashMap<(GameValueNode, GameValueNode), Self>, comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>, ) -> Self
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 513)
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }
658}
659impl Neg for GameValue {
660 type Output = Self;
661 fn neg(self) -> Self {
662 self.negated(&mut HashMap::new())
663 }
664}
665impl Add for GameValue {
666 type Output = Self;
667 fn add(self, rhs: Self) -> Self {
668 self.sum(&rhs, &mut HashMap::new(), &mut HashMap::new())
669 }Sourcefn number_offset(&self) -> Dyadic<i64>
fn number_offset(&self) -> Dyadic<i64>
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 420)
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }Sourcefn translated(&self, number: Dyadic<i64>) -> Self
fn translated(&self, number: Dyadic<i64>) -> Self
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 234)
169 pub fn options(&self, player: Player) -> impl Iterator<Item = Self> + '_ {
170 let mut offset = Dyadic::from(0);
171 let (single, nimbers, general) = match &self.0 {
172 GameValueNode::NumberUpNimber(n, 0, 0) => {
173 let option = if n.exponent() == 0 {
174 match player {
175 Player::Left if n.numerator() > 0 => Some(Dyadic::from(n.numerator() - 1)),
176 Player::Right if n.numerator() < 0 => Some(Dyadic::from(n.numerator() + 1)),
177 _ => None,
178 }
179 } else {
180 let numerator = (n.numerator() >> 1) + i64::from(player == Player::Right);
181 Some(Dyadic::new(numerator, n.exponent() - 1))
182 };
183 (option.map(Self::from), None, &[][..])
184 }
185 GameValueNode::NumberUpNimber(q, 0, n) => (None, Some((*q, *n)), &[][..]),
186 GameValueNode::NumberUpNimber(q, k, 1)
187 if matches!((*k, player), (1, Player::Left) | (-1, Player::Right)) =>
188 {
189 (None, Some((*q, 2)), &[][..])
190 }
191 GameValueNode::NumberUpNimber(q, k, n) => {
192 let sign = if *k > 0 { 1 } else { -1 };
193 let moves_to_number = (*k > 0) == (player == Player::Left);
194 let option = if moves_to_number {
195 Self::from(*q)
196 } else {
197 Self::number_up_nimber(*q, k - sign, n ^ 1)
198 };
199 (Some(option), None, &[][..])
200 }
201 GameValueNode::Switches(value) => {
202 let temperatures = &value.temperatures[value.start..];
203 let t = if player == Player::Left {
204 temperatures[0]
205 } else {
206 -temperatures[0]
207 };
208 let option = if temperatures.len() == 1 {
209 Self::number_up_nimber(value.number + t, value.up, value.nimber)
210 } else {
211 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
212 number: value.number + t,
213 start: value.start + 1,
214 fingerprint: value.fingerprint ^ Self::temperature_hash(temperatures[0]),
215 ..(**value).clone()
216 })))
217 };
218 (Some(option), None, &[][..])
219 }
220 GameValueNode::Options(q, options) => {
221 offset = *q;
222 let (left, right) = &**options;
223 let moves = if player == Player::Left { left } else { right };
224 (None, None, moves.as_slice())
225 }
226 };
227 single
228 .into_iter()
229 .chain(
230 nimbers
231 .into_iter()
232 .flat_map(|(q, n)| (0..n).map(move |m| Self::number_up_nimber(q, 0, m))),
233 )
234 .chain(general.iter().map(move |g| g.translated(offset)))
235 }
236
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }Sourcefn temperature_hash(t: Dyadic<i64>) -> u64
fn temperature_hash(t: Dyadic<i64>) -> u64
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 214)
169 pub fn options(&self, player: Player) -> impl Iterator<Item = Self> + '_ {
170 let mut offset = Dyadic::from(0);
171 let (single, nimbers, general) = match &self.0 {
172 GameValueNode::NumberUpNimber(n, 0, 0) => {
173 let option = if n.exponent() == 0 {
174 match player {
175 Player::Left if n.numerator() > 0 => Some(Dyadic::from(n.numerator() - 1)),
176 Player::Right if n.numerator() < 0 => Some(Dyadic::from(n.numerator() + 1)),
177 _ => None,
178 }
179 } else {
180 let numerator = (n.numerator() >> 1) + i64::from(player == Player::Right);
181 Some(Dyadic::new(numerator, n.exponent() - 1))
182 };
183 (option.map(Self::from), None, &[][..])
184 }
185 GameValueNode::NumberUpNimber(q, 0, n) => (None, Some((*q, *n)), &[][..]),
186 GameValueNode::NumberUpNimber(q, k, 1)
187 if matches!((*k, player), (1, Player::Left) | (-1, Player::Right)) =>
188 {
189 (None, Some((*q, 2)), &[][..])
190 }
191 GameValueNode::NumberUpNimber(q, k, n) => {
192 let sign = if *k > 0 { 1 } else { -1 };
193 let moves_to_number = (*k > 0) == (player == Player::Left);
194 let option = if moves_to_number {
195 Self::from(*q)
196 } else {
197 Self::number_up_nimber(*q, k - sign, n ^ 1)
198 };
199 (Some(option), None, &[][..])
200 }
201 GameValueNode::Switches(value) => {
202 let temperatures = &value.temperatures[value.start..];
203 let t = if player == Player::Left {
204 temperatures[0]
205 } else {
206 -temperatures[0]
207 };
208 let option = if temperatures.len() == 1 {
209 Self::number_up_nimber(value.number + t, value.up, value.nimber)
210 } else {
211 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
212 number: value.number + t,
213 start: value.start + 1,
214 fingerprint: value.fingerprint ^ Self::temperature_hash(temperatures[0]),
215 ..(**value).clone()
216 })))
217 };
218 (Some(option), None, &[][..])
219 }
220 GameValueNode::Options(q, options) => {
221 offset = *q;
222 let (left, right) = &**options;
223 let moves = if player == Player::Left { left } else { right };
224 (None, None, moves.as_slice())
225 }
226 };
227 single
228 .into_iter()
229 .chain(
230 nimbers
231 .into_iter()
232 .flat_map(|(q, n)| (0..n).map(move |m| Self::number_up_nimber(q, 0, m))),
233 )
234 .chain(general.iter().map(move |g| g.translated(offset)))
235 }
236
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }Sourcefn switch_hash(&self) -> u64
fn switch_hash(&self) -> u64
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 321)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }
658}
659impl Neg for GameValue {
660 type Output = Self;
661 fn neg(self) -> Self {
662 self.negated(&mut HashMap::new())
663 }
664}
665impl Add for GameValue {
666 type Output = Self;
667 fn add(self, rhs: Self) -> Self {
668 self.sum(&rhs, &mut HashMap::new(), &mut HashMap::new())
669 }
670}
671impl Sub for GameValue {
672 type Output = Self;
673 fn sub(self, rhs: Self) -> Self {
674 if self.0 == rhs.0 {
675 Self::from(0)
676 } else {
677 self + -rhs
678 }
679 }
680}
681impl Sum for GameValue {
682 fn sum<I>(iter: I) -> Self
683 where
684 I: Iterator<Item = Self>,
685 {
686 let (mut number, mut up, mut nimber) = (Dyadic::from(0), 0, 0);
687 let mut temperatures = Vec::new();
688 let mut fingerprint = 0;
689 let mut general = Self::from(0);
690 for value in iter {
691 if let Some((q, k, n, ts)) = value.as_switch_sum() {
692 number = number + q;
693 up += k;
694 nimber ^= n;
695 temperatures.extend_from_slice(ts);
696 fingerprint ^= value.switch_hash();
697 } else {
698 general = general + value;
699 }
700 }
701 temperatures.sort_unstable_by(|a, b| b.cmp(a));
702 let mut retained = 0;
703 for i in 0..temperatures.len() {
704 if retained > 0 && temperatures[retained - 1] == temperatures[i] {
705 retained -= 1;
706 } else {
707 temperatures[retained] = temperatures[i];
708 retained += 1;
709 }
710 }
711 temperatures.truncate(retained);
712 let value = Self::from_switches(number, up, nimber, temperatures, fingerprint);
713 if general.as_nimber() == Some(0) {
714 value
715 } else {
716 value + general
717 }
718 }Sourcefn from_switches(
q: Dyadic<i64>,
k: i64,
n: u64,
temperatures: Vec<Dyadic<i64>>,
fingerprint: u64,
) -> Self
fn from_switches( q: Dyadic<i64>, k: i64, n: u64, temperatures: Vec<Dyadic<i64>>, fingerprint: u64, ) -> Self
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (lines 255-261)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }
378
379 fn leq(&self, rhs: &Self) -> bool {
380 self.leq_memoized(rhs, &mut HashMap::new())
381 }
382
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }
545
546 fn number_offset(&self) -> Dyadic<i64> {
547 match &self.0 {
548 GameValueNode::NumberUpNimber(q, ..) | GameValueNode::Options(q, _) => *q,
549 GameValueNode::Switches(v) => v.number,
550 }
551 }
552
553 fn translated(&self, number: Dyadic<i64>) -> Self {
554 if number.numerator() == 0 {
555 return self.clone();
556 }
557 match &self.0 {
558 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(*q + number, *k, *n),
559 GameValueNode::Switches(v) => {
560 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
561 number: v.number + number,
562 ..(**v).clone()
563 })))
564 }
565 GameValueNode::Options(q, options) => {
566 Self(GameValueNode::Options(*q + number, options.clone()))
567 }
568 }
569 }
570
571 fn temperature_hash(t: Dyadic<i64>) -> u64 {
572 let mut hasher = DefaultHasher::new();
573 t.hash(&mut hasher);
574 hasher.finish()
575 }
576
577 fn switch_hash(&self) -> u64 {
578 match &self.0 {
579 GameValueNode::NumberUpNimber(..) => 0,
580 GameValueNode::Switches(v) => v.fingerprint,
581 GameValueNode::Options(..) => unreachable!(),
582 }
583 }
584
585 fn from_switches(
586 q: Dyadic<i64>,
587 k: i64,
588 n: u64,
589 temperatures: Vec<Dyadic<i64>>,
590 fingerprint: u64,
591 ) -> Self {
592 if temperatures.is_empty() {
593 Self::number_up_nimber(q, k, n)
594 } else {
595 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
596 number: q,
597 up: k,
598 nimber: n,
599 fingerprint,
600 temperatures: Rc::new(temperatures),
601 start: 0,
602 })))
603 }
604 }
605
606 fn switch_difference<'a>(
607 mut left: &'a [Dyadic<i64>],
608 mut right: &'a [Dyadic<i64>],
609 ) -> impl Iterator<Item = Dyadic<i64>> + 'a {
610 std::iter::from_fn(move || {
611 while let (Some(a), Some(b)) = (left.first(), right.first()) {
612 if a != b {
613 break;
614 }
615 left = &left[1..];
616 right = &right[1..];
617 }
618 if left.first() < right.first() {
619 std::mem::swap(&mut left, &mut right);
620 }
621 let (&value, tail) = left.split_first()?;
622 left = tail;
623 Some(value)
624 })
625 }
626
627 fn remove_dominated(
628 options: &mut Vec<Self>,
629 player: Player,
630 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
631 ) {
632 let mut retained = vec![true; options.len()];
633 for i in 0..options.len() {
634 let dominated = (0..options.len()).any(|j| {
635 i != j
636 && retained[j]
637 && match player {
638 Player::Left => options[i].leq_memoized(&options[j], cache),
639 Player::Right => options[j].leq_memoized(&options[i], cache),
640 }
641 });
642 retained[i] = !dominated;
643 }
644 let mut retained = retained.into_iter();
645 options.retain(|_| retained.next().unwrap());
646 }
647}
648
649impl From<i64> for GameValue {
650 fn from(value: i64) -> Self {
651 Dyadic::from(value).into()
652 }
653}
654impl From<Dyadic<i64>> for GameValue {
655 fn from(value: Dyadic<i64>) -> Self {
656 Self::number_up_nimber(value, 0, 0)
657 }
658}
659impl Neg for GameValue {
660 type Output = Self;
661 fn neg(self) -> Self {
662 self.negated(&mut HashMap::new())
663 }
664}
665impl Add for GameValue {
666 type Output = Self;
667 fn add(self, rhs: Self) -> Self {
668 self.sum(&rhs, &mut HashMap::new(), &mut HashMap::new())
669 }
670}
671impl Sub for GameValue {
672 type Output = Self;
673 fn sub(self, rhs: Self) -> Self {
674 if self.0 == rhs.0 {
675 Self::from(0)
676 } else {
677 self + -rhs
678 }
679 }
680}
681impl Sum for GameValue {
682 fn sum<I>(iter: I) -> Self
683 where
684 I: Iterator<Item = Self>,
685 {
686 let (mut number, mut up, mut nimber) = (Dyadic::from(0), 0, 0);
687 let mut temperatures = Vec::new();
688 let mut fingerprint = 0;
689 let mut general = Self::from(0);
690 for value in iter {
691 if let Some((q, k, n, ts)) = value.as_switch_sum() {
692 number = number + q;
693 up += k;
694 nimber ^= n;
695 temperatures.extend_from_slice(ts);
696 fingerprint ^= value.switch_hash();
697 } else {
698 general = general + value;
699 }
700 }
701 temperatures.sort_unstable_by(|a, b| b.cmp(a));
702 let mut retained = 0;
703 for i in 0..temperatures.len() {
704 if retained > 0 && temperatures[retained - 1] == temperatures[i] {
705 retained -= 1;
706 } else {
707 temperatures[retained] = temperatures[i];
708 retained += 1;
709 }
710 }
711 temperatures.truncate(retained);
712 let value = Self::from_switches(number, up, nimber, temperatures, fingerprint);
713 if general.as_nimber() == Some(0) {
714 value
715 } else {
716 value + general
717 }
718 }Sourcefn switch_difference<'a>(
left: &'a [Dyadic<i64>],
right: &'a [Dyadic<i64>],
) -> impl Iterator<Item = Dyadic<i64>> + 'a
fn switch_difference<'a>( left: &'a [Dyadic<i64>], right: &'a [Dyadic<i64>], ) -> impl Iterator<Item = Dyadic<i64>> + 'a
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 395)
383 fn leq_memoized(
384 &self,
385 rhs: &Self,
386 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
387 ) -> bool {
388 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
389 (self.as_switch_sum(), rhs.as_switch_sum())
390 {
391 // Both players take the hottest remaining switch. At zero, the tail
392 // decides the winner with the turn reversed once per switch.
393 let mut number = a;
394 let mut odd = false;
395 for t in Self::switch_difference(ts, us) {
396 // The unplayed alternating tail has the sign of the next term.
397 if !odd && number >= b || odd && number <= b {
398 return odd;
399 }
400 number = if odd { number - t } else { number + t };
401 odd = !odd;
402 }
403 return match number.cmp(&b) {
404 Ordering::Less => true,
405 Ordering::Greater => false,
406 Ordering::Equal => {
407 let up = if odd { l - k } else { k - l };
408 let nimber = n ^ m;
409 let wins = up > 0 || up == 0 && nimber != 0 || up == -1 && nimber == 1;
410 wins == odd
411 }
412 };
413 }
414 if let (GameValueNode::Options(a, g), GameValueNode::Options(b, h)) = (&self.0, &rhs.0)
415 && Rc::ptr_eq(g, h)
416 {
417 return a <= b;
418 }
419 // Normalize only memo keys: from_options also compares unreduced candidates.
420 let offset = -self.number_offset();
421 let left = self.translated(offset).0;
422 let right = rhs.translated(offset).0;
423 if let Some(&value) = cache.get(&left).and_then(|row| row.get(&right)) {
424 return value;
425 }
426 // Try explicit options before expanding a compact value.
427 let mut sides = [(self, rhs, Player::Left), (rhs, self, Player::Right)];
428 if self.as_switch_sum().is_some() {
429 sides.swap(0, 1);
430 }
431 let value = sides.into_iter().all(|(game, other, player)| {
432 let mut preferred = |a: &Self, b: &Self| match player {
433 Player::Left => b.leq_memoized(a, cache),
434 Player::Right => a.leq_memoized(b, cache),
435 };
436 if let GameValueNode::NumberUpNimber(q, 0, n) = game.0
437 && n != 0
438 {
439 let number = Self::from(q);
440 !preferred(&number, other)
441 && (preferred(other, &number)
442 || !game.options(player).skip(1).any(|g| preferred(&g, other)))
443 } else {
444 !game.options(player).any(|g| preferred(&g, other))
445 }
446 });
447 cache.entry(left).or_default().insert(right, value);
448 value
449 }
450
451 fn negated(&self, cache: &mut HashMap<usize, Self>) -> Self {
452 match &self.0 {
453 GameValueNode::NumberUpNimber(q, k, n) => Self::number_up_nimber(-*q, -*k, *n),
454 GameValueNode::Switches(value) => {
455 Self(GameValueNode::Switches(Rc::new(GameValueSwitches {
456 number: -value.number,
457 up: -value.up,
458 ..(**value).clone()
459 })))
460 }
461 GameValueNode::Options(q, options) => {
462 let key = Rc::as_ptr(options) as usize;
463 if let Some(value) = cache.get(&key) {
464 return value.translated(-*q);
465 }
466 let (left, right) = &**options;
467 let value = Self(GameValueNode::Options(
468 0.into(),
469 Rc::new((
470 right.iter().map(|g| g.negated(cache)).collect(),
471 left.iter().map(|g| g.negated(cache)).collect(),
472 )),
473 ));
474 cache.insert(key, value.clone());
475 value.translated(-*q)
476 }
477 }
478 }
479
480 fn sum(
481 &self,
482 rhs: &Self,
483 cache: &mut HashMap<(GameValueNode, GameValueNode), Self>,
484 comparisons: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
485 ) -> Self {
486 if let (GameValueNode::NumberUpNimber(a, k, n), GameValueNode::NumberUpNimber(b, l, m)) =
487 (&self.0, &rhs.0)
488 {
489 return Self::number_up_nimber(*a + *b, k + l, n ^ m);
490 }
491 if let Some(q) = self.as_number() {
492 return rhs.translated(q);
493 }
494 if let Some(q) = rhs.as_number() {
495 return self.translated(q);
496 }
497 if let (Some((a, k, n, ts)), Some((b, l, m, us))) =
498 (self.as_switch_sum(), rhs.as_switch_sum())
499 {
500 return Self::from_switches(
501 a + b,
502 k + l,
503 n ^ m,
504 Self::switch_difference(ts, us).collect(),
505 self.switch_hash() ^ rhs.switch_hash(),
506 );
507 }
508 // Cache the sum of the untranslated parts, sharing it across numeric offsets.
509 let (a, b) = (self.number_offset(), rhs.number_offset());
510 if a.numerator() != 0 || b.numerator() != 0 {
511 return self
512 .translated(-a)
513 .sum(&rhs.translated(-b), cache, comparisons)
514 .translated(a + b);
515 }
516 let mut key = (self.0.clone(), rhs.0.clone());
517 let swap = match (&key.0, &key.1) {
518 (GameValueNode::Options(_, a), GameValueNode::Options(_, b)) => {
519 Rc::as_ptr(a) > Rc::as_ptr(b)
520 }
521 (GameValueNode::Options(..), _) => true,
522 _ => false,
523 };
524 if swap {
525 std::mem::swap(&mut key.0, &mut key.1);
526 }
527 if let Some(value) = cache.get(&key) {
528 return value.clone();
529 }
530 let mut options = |player| {
531 let mut moves = Vec::new();
532 for g in self.options(player) {
533 moves.push(g.sum(rhs, cache, comparisons));
534 }
535 for g in rhs.options(player) {
536 moves.push(self.sum(&g, cache, comparisons));
537 }
538 moves
539 };
540 let value =
541 Self::from_options_memoized(options(Player::Left), options(Player::Right), comparisons);
542 cache.insert(key, value.clone());
543 value
544 }Sourcefn remove_dominated(
options: &mut Vec<Self>,
player: Player,
cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
)
fn remove_dominated( options: &mut Vec<Self>, player: Player, cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>, )
Examples found in repository?
crates/competitive/src/algorithm/game_analyzer/value.rs (line 326)
237 fn from_options_memoized(
238 mut left: Vec<Self>,
239 mut right: Vec<Self>,
240 cache: &mut HashMap<GameValueNode, HashMap<GameValueNode, bool>>,
241 ) -> Self {
242 loop {
243 if left.iter().chain(&right).all(|g| g.as_number().is_some()) {
244 let l = left.iter().filter_map(Self::as_number).max();
245 let r = right.iter().filter_map(Self::as_number).min();
246 if let Some(n) = Dyadic::simplest_between(l, r) {
247 return n.into();
248 }
249 let (l, r) = (l.unwrap(), r.unwrap());
250 if l == r {
251 return Self::number_up_nimber(l, 0, 1);
252 }
253 let (sum, difference) = (l + r, l - r);
254 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
255 return Self::from_switches(
256 Dyadic::new(sum.numerator(), sum.exponent() + 1),
257 0,
258 0,
259 vec![temperature],
260 Self::temperature_hash(temperature),
261 );
262 }
263 if !right.is_empty()
264 && let Some((number, _)) = left.first().and_then(Self::as_number_nimber)
265 {
266 let nimber = |g: &Self| {
267 g.as_number_nimber()
268 .filter(|&(q, _)| q == number)
269 .map(|(_, n)| n)
270 };
271 let l: Option<Vec<_>> = left.iter().map(nimber).collect();
272 let r: Option<Vec<_>> = right.iter().map(nimber).collect();
273 if let (Some(l), Some(r)) = (l, r) {
274 let value = game_mex(l);
275 if value == game_mex(r) {
276 return Self::number_up_nimber(number, 0, value);
277 }
278 }
279 }
280 for (moves, replies, sign) in [(&left, &right, 1), (&right, &left, -1)] {
281 if replies.len() != 1 {
282 continue;
283 }
284 if let Some((q, k, n)) = replies[0].as_number_up_nimber() {
285 if moves.len() == 1
286 && moves[0].as_number() == Some(q)
287 && (k == 0 || k.signum() == sign)
288 {
289 return Self::number_up_nimber(q, k + sign, n ^ 1);
290 }
291 if k == 0 && n == 0 && moves.len() == 2 {
292 let a = moves[0].as_number_nimber();
293 let b = moves[1].as_number_nimber();
294 if (a == Some((q, 0)) && b == Some((q, 1)))
295 || (a == Some((q, 1)) && b == Some((q, 0)))
296 {
297 return Self::number_up_nimber(q, sign, 1);
298 }
299 }
300 }
301 }
302 if left.len() == 1
303 && right.len() == 1
304 && let (Some((a, k, n, ts)), Some((b, l, m, us))) =
305 (left[0].as_switch_sum(), right[0].as_switch_sum())
306 && k == l
307 && n == m
308 && ts == us
309 && a > b
310 {
311 let (sum, difference) = (a + b, a - b);
312 let temperature = Dyadic::new(difference.numerator(), difference.exponent() + 1);
313 if ts.first().is_none_or(|&t| temperature > t) {
314 return Self::from_switches(
315 Dyadic::new(sum.numerator(), sum.exponent() + 1),
316 k,
317 n,
318 std::iter::once(temperature)
319 .chain(ts.iter().copied())
320 .collect(),
321 Self::temperature_hash(temperature) ^ left[0].switch_hash(),
322 );
323 }
324 }
325 let count = left.len() + right.len();
326 Self::remove_dominated(&mut left, Player::Left, cache);
327 Self::remove_dominated(&mut right, Player::Right, cache);
328 if count != left.len() + right.len() {
329 continue;
330 }
331 let options = Rc::new((left, right));
332 let game = Self(GameValueNode::Options(0.into(), options.clone()));
333 // Bypasses preserve the game, so all checks use the same candidate.
334 let mut reversible = Vec::new();
335 for player in [Player::Left, Player::Right] {
336 let moves = match player {
337 Player::Left => &options.0,
338 Player::Right => &options.1,
339 };
340 for (i, g) in moves.iter().enumerate() {
341 if let Some((q, n)) = g.as_number_nimber()
342 && n > 1
343 {
344 let number = Self::from(q);
345 let (lower, upper) = match player {
346 Player::Left => (&game, &number),
347 Player::Right => (&number, &game),
348 };
349 // A strict inequality against q excludes every reply q + *m.
350 if lower.leq_memoized(upper, cache) && !upper.leq_memoized(lower, cache) {
351 continue;
352 }
353 }
354 if let Some(reply) = g.options(player.opponent()).find(|r| match player {
355 Player::Left => r.leq_memoized(&game, cache),
356 Player::Right => game.leq_memoized(r, cache),
357 }) {
358 reversible.push((player, i, reply));
359 }
360 }
361 }
362 if reversible.is_empty() {
363 return game;
364 }
365 drop(game);
366 (left, right) = Rc::unwrap_or_clone(options);
367 for (player, i, reply) in reversible.into_iter().rev() {
368 let moves = if player == Player::Left {
369 &mut left
370 } else {
371 &mut right
372 };
373 moves.remove(i);
374 moves.extend(reply.options(player));
375 }
376 }
377 }Trait Implementations§
impl Eq for GameValue
Source§impl PartialOrd for GameValue
impl PartialOrd for GameValue
Auto Trait Implementations§
impl !Send for GameValue
impl !Sync for GameValue
impl Forget for GameValue
impl Freeze for GameValue
impl Move for GameValue
impl RefUnwindSafe for GameValue
impl Unpin for GameValue
impl UnsafeUnpin for GameValue
impl UnwindSafe for GameValue
Blanket Implementations§
Source§impl<T> AsTotalOrd for Twhere
T: PartialOrd,
impl<T> AsTotalOrd for Twhere
T: PartialOrd,
fn as_total_ord(&self) -> TotalOrd<&T>
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