GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 0.0% 0 / 0 / 79
Functions: 0.0% 0 / 0 / 11
Branches: 0.0% 0 / 40 / 128

combo_games/cold_games.hpp
Line Branch Exec Source
1 #pragma once
2
3 #include <ostream>
4 #include <vector>
5 #include <optional>
6 #include <array>
7 #include <cassert>
8
9 namespace wala {
10
11 // TODO: Make this generic over numerator type, e.g. bignum
12 struct dyadic {
13 int n = 0, d = 0;
14 ✗ friend std::ostream& operator << (std::ostream& o, dyadic v) {
15 ✗ return o << v.n << "/2^" << v.d;
16 }
17 ✗ friend auto operator == (dyadic a, dyadic b) {
18 ✗ return a.n == b.n && a.d == b.d;
19 }
20 ✗ friend auto operator <=> (dyadic a, dyadic b) {
21 ✗ return a.n << std::max(b.d - a.d, 0) <=> b.n << std::max(a.d - b.d, 0);
22 }
23 ✗ dyadic operator - () const {
24 ✗ return {-n, d};
25 }
26 ✗ friend dyadic operator + (dyadic a, dyadic b) {
27 ✗ int nn = (a.n << std::max(b.d - a.d, 0)) + (b.n << std::max(a.d - b.d, 0));
28 ✗ int nd = std::max(a.d, b.d);
29 ✗ if (!nn) nd = 0;
30 else {
31 ✗ int x = std::min(__builtin_ctz(nn), nd);
32 ✗ nn >>= x;
33 ✗ nd -= x;
34 }
35 ✗ return {nn, nd};
36 }
37 };
38
39 // TODO: Make this generic over dyadic type
40 struct cold_ish {
41 dyadic v;
42 ✗ bool star = false;
43 ✗ friend std::ostream& operator << (std::ostream& o, cold_ish d) {
44 ✗ return o << d.v << (d.star ? "*" : "");
45 }
46
47 ✗ friend bool operator == (cold_ish a, cold_ish b) = default;
48
49 ✗ cold_ish operator - () const {
50 ✗ return {-v, star};
51 }
52 ✗ friend cold_ish operator + (cold_ish a, cold_ish b) {
53 ✗ return {a.v + b.v, bool(a.star ^ b.star)};
54 }
55 };
56
57 // If L_options <| a <| R_options and no (direct) ancestors of a
58 // satisfy this (by breaking it on the correct side), then G = a.
59 //
60 // Proof:
61 // L can move to L_options - a <| 0 which means R wins as the next player,
62 // or G - a.R so R moves to some R_options - a.R <= 0 which means R wins as the 2nd player
63 // (this requires that a.R breaks it by being >= some R_options and not <= some L_options,
64 // which holds if everything here is cold-ish).
65
66 // 0 are moves for l
67 // TODO: Could optimize
68 ✗ inline std::optional<cold_ish> cold_ish_game(std::array<std::vector<cold_ish>, 2> moves) {
69 ✗ std::array<std::optional<cold_ish>, 2> best;
70 ✗ for (int z = 0; z < 2; z++) {
71 ✗ std::optional<cold_ish> v;
72 ✗ for (auto m : moves[z]) {
73 ✗ if (z) m = -m;
74 // no star is "bigger" in this sense
75 ✗ if (!v || m.v > v->v || (m.v == v->v && m.star < v->star)) {
76 ✗ v = m;
77 }
78 }
79 ✗ if (z && v) v = -*v;
80 ✗ best[z] = v;
81 }
82 ✗ auto fuzzy_less = [&](std::optional<cold_ish> a, std::optional<cold_ish> b) -> bool {
83 ✗ if (!a) return true;
84 ✗ if (!b) return true;
85 ✗ return (a->v < b->v) || (a->v == b->v && a->star != b->star);
86 };
87 ✗ if (best[0] && best[1] && fuzzy_less(*best[1], *best[0])) {
88 // invalid
89 ✗ return std::nullopt;
90 }
91
92 // best[0] <= best[1], so we should be able to find someone here
93
94 // the only time we return star is if the lower bound == the upper bound
95 ✗ if (best[0] && best[1] && *best[0] == *best[1]) {
96 ✗ auto cnd = *best[0];
97 ✗ cnd.star ^= true;
98 ✗ assert(fuzzy_less(best[0], cnd) && fuzzy_less(cnd, best[1]));
99 ✗ return cnd;
100 }
101 ✗ assert(!best[0] || !best[1] || best[0]->v < best[1]->v);
102
103 ✗ cold_ish zero{{0}, false};
104 ✗ bool le_zero = fuzzy_less(best[0], zero);
105 ✗ bool ge_zero = fuzzy_less(zero, best[1]);
106 ✗ assert(le_zero || ge_zero);
107 ✗ if (le_zero && ge_zero) {
108 ✗ return zero;
109 }
110 ✗ bool flip = le_zero;
111 ✗ if (flip) {
112 ✗ std::swap(le_zero, ge_zero);
113 ✗ std::swap(best[0], best[1]);
114 ✗ if (best[0]) best[0] = -*best[0];
115 ✗ if (best[1]) best[1] = -*best[1];
116 }
117 ✗ assert(ge_zero);
118 ✗ assert(!le_zero);
119 // check if there's an integer that's good
120 ✗ assert(best[0]);
121
122 // best[0] >= 0
123 ✗ int int_cnd = (best[0]->star ? best[0]->v.n - 1 : best[0]->v.n);
124 ✗ assert(int_cnd >= 0);
125 ✗ int_cnd >>= best[0]->v.d;
126 ✗ int_cnd++;
127 ✗ cold_ish cnd = {dyadic(int_cnd)};
128 ✗ assert(fuzzy_less(best[0], cnd));
129 ✗ if (fuzzy_less(cnd, best[1])) {
130 ✗ return flip ? -cnd : cnd;
131 }
132
133 // otherwise, cnd is too big, and we're just doing the dyadic rational thing
134 ✗ assert(best[1]);
135 // extra 1 to allow the middle
136 ✗ int d = std::max(best[0]->v.d, best[1]->v.d)+1;
137 // exclusive bounds
138 ✗ int v0 = (best[0]->v.n << (d - best[0]->v.d)) - best[0]->star;
139 ✗ int v1 = (best[1]->v.n << (d - best[1]->v.d)) + best[1]->star;
140 ✗ assert(v1 - v0 >= 2);
141 ✗ int b = 31 - __builtin_clz((v1-1) ^ v0);
142 ✗ cnd = cold_ish{dyadic((v1-1) >> b, d-b), false};
143 ✗ assert(cnd.v.n & 1);
144 ✗ assert(fuzzy_less(best[0], cnd) && fuzzy_less(cnd, best[1]));
145 ✗ return flip ? -cnd : cnd;
146 }
147
148 } // namespace wala
149