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