diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2021-08-21 17:18:02 -0400 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2021-08-21 17:18:02 -0400 |
commit | ba04ac934dea549497e2e89fa7a9e5bc8723f5f8 (patch) | |
tree | f852810377bfe663dfd73bedfaaf4dca8c657f37 | |
parent | c60df0e75e63f488d94fd744ad70df8124dc7724 (diff) | |
download | witness-tutorializer-ba04ac934dea549497e2e89fa7a9e5bc8723f5f8.tar.gz witness-tutorializer-ba04ac934dea549497e2e89fa7a9e5bc8723f5f8.tar.bz2 witness-tutorializer-ba04ac934dea549497e2e89fa7a9e5bc8723f5f8.zip |
Cleaned up a bunch of stuff I'm not using
-rw-r--r-- | App/Main.cpp | 4 | ||||
-rw-r--r-- | Source/ChallengeRandomizer.cpp | 108 | ||||
-rw-r--r-- | Source/ChallengeRandomizer.h | 13 | ||||
-rw-r--r-- | Source/Randomizer.cpp | 251 | ||||
-rw-r--r-- | Source/Randomizer.h | 31 | ||||
-rw-r--r-- | Source/Solver.cpp | 76 | ||||
-rw-r--r-- | Source/Solver.h | 13 | ||||
-rw-r--r-- | Source/Source.vcxproj | 6 | ||||
-rw-r--r-- | Source/Validator.cpp | 97 | ||||
-rw-r--r-- | Source/Validator.h | 27 |
10 files changed, 8 insertions, 618 deletions
diff --git a/App/Main.cpp b/App/Main.cpp index ad3d127..f0f770a 100644 --- a/App/Main.cpp +++ b/App/Main.cpp | |||
@@ -13,7 +13,6 @@ | |||
13 | #define RANDOMIZE_DONE 0x404 | 13 | #define RANDOMIZE_DONE 0x404 |
14 | 14 | ||
15 | /* ------- Temp ------- */ | 15 | /* ------- Temp ------- */ |
16 | #include "Solver.h" | ||
17 | #include "PuzzleSerializer.h" | 16 | #include "PuzzleSerializer.h" |
18 | #include <sstream> | 17 | #include <sstream> |
19 | #include <iomanip> | 18 | #include <iomanip> |
@@ -150,12 +149,9 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance | |||
150 | 149 | ||
151 | CreateMultiLabel(10, 10, 460, 86, L"This mod replaces most puzzles in the game with Tutorial Straight (the first puzzle in the tunnel where you start the game). Certain special panels are unaffected. Additionally, some panels (e.g. the tutorial gate, and every puzzle in Bunker) behave a little strangely now, and can be solved by simply double clicking in the middle of the panel."); | 150 | CreateMultiLabel(10, 10, 460, 86, L"This mod replaces most puzzles in the game with Tutorial Straight (the first puzzle in the tunnel where you start the game). Certain special panels are unaffected. Additionally, some panels (e.g. the tutorial gate, and every puzzle in Bunker) behave a little strangely now, and can be solved by simply double clicking in the middle of the panel."); |
152 | CreateLabel(390, 110, 90, L"Version: " VERSION_STR); | 151 | CreateLabel(390, 110, 90, L"Version: " VERSION_STR); |
153 | //g_seed = CreateText(10, 10, 100); | ||
154 | //PostMessage(g_seed, EM_SETEVENTMASK, 0, ENM_KEYEVENTS); | ||
155 | g_randomizerStatus = CreateButton(120, 105, 180, L"Tutorialise", RANDOMIZING); | 152 | g_randomizerStatus = CreateButton(120, 105, 180, L"Tutorialise", RANDOMIZING); |
156 | EnableWindow(g_randomizerStatus, FALSE); | 153 | EnableWindow(g_randomizerStatus, FALSE); |
157 | 154 | ||
158 | |||
159 | g_witnessProc->StartHeartbeat(g_hwnd, HEARTBEAT); | 155 | g_witnessProc->StartHeartbeat(g_hwnd, HEARTBEAT); |
160 | 156 | ||
161 | ShowWindow(g_hwnd, nCmdShow); | 157 | ShowWindow(g_hwnd, nCmdShow); |
diff --git a/Source/ChallengeRandomizer.cpp b/Source/ChallengeRandomizer.cpp deleted file mode 100644 index 976374e..0000000 --- a/Source/ChallengeRandomizer.cpp +++ /dev/null | |||
@@ -1,108 +0,0 @@ | |||
1 | #include "pch.h" | ||
2 | #include "ChallengeRandomizer.h" | ||
3 | |||
4 | // Modify an opcode to use RNG2 instead of main RNG | ||
5 | void ChallengeRandomizer::AdjustRng(int offset) { | ||
6 | int currentRng = _memory->ReadData<int>({offset}, 0x1)[0]; | ||
7 | _memory->WriteData<int>({offset}, {currentRng + 0x20}); | ||
8 | } | ||
9 | |||
10 | // Overwrite the pointer for the lightmap_generator (which is unused, afaict) to point to a secondary RNG. | ||
11 | // Then, adjust all the RNG functions in challenge/doors to use this RNG. | ||
12 | ChallengeRandomizer::ChallengeRandomizer(const std::shared_ptr<Memory>& memory, int seed) : _memory(memory) | ||
13 | { | ||
14 | RNG_ADDR = _memory->ReadData<int>({GLOBALS + 0x10}, 1)[0]; | ||
15 | RNG2_ADDR = _memory->ReadData<int>({GLOBALS + 0x30}, 1)[0]; | ||
16 | bool alreadyInjected = (RNG2_ADDR == RNG_ADDR + 4); | ||
17 | |||
18 | if (!alreadyInjected) _memory->WriteData<int>({GLOBALS + 0x30}, {RNG_ADDR + 4}); | ||
19 | _memory->WriteData<int>({GLOBALS + 0x30, 0}, {seed}); | ||
20 | |||
21 | // do_success_side_effects | ||
22 | _memory->AddSigScan({0xFF, 0xC8, 0x99, 0x2B, 0xC2, 0xD1, 0xF8, 0x8B, 0xD0}, [&](int index) { | ||
23 | if (GLOBALS == 0x5B28C0) { // Version differences. | ||
24 | index += 0x3E; | ||
25 | } else if (GLOBALS == 0x62D0A0) { | ||
26 | index += 0x42; | ||
27 | } | ||
28 | // Overwritten bytes start just after the movsxd rax, dword ptr ds:[rdi + 0x230] | ||
29 | // aka test eax, eax; jle 2C; imul rcx, rax, 34 | ||
30 | _memory->WriteData<byte>({index}, { | ||
31 | 0x8B, 0x0D, 0x00, 0x00, 0x00, 0x00, // mov ecx, [0x00000000] ;This is going to be the address of the custom RNG | ||
32 | 0x67, 0xC7, 0x01, 0x00, 0x00, 0x00, 0x00, // mov dword ptr ds:[ecx], 0x00000000 ;This is going to be the seed value | ||
33 | 0x48, 0x83, 0xF8, 0x02, // cmp rax, 0x2 ;This is the short solve on the record player (which turns it off) | ||
34 | 0x90, 0x90, 0x90 // nop nop nop | ||
35 | }); | ||
36 | int target = (GLOBALS + 0x30) - (index + 0x6); // +6 is for the length of the line | ||
37 | _memory->WriteData<int>({index + 0x2}, {target}); | ||
38 | _memory->WriteData<int>({index + 0x9}, {seed}); // Because we're resetting seed every challenge, we need to run this injection every time. | ||
39 | }); | ||
40 | |||
41 | if (!alreadyInjected) { | ||
42 | // shuffle_integers | ||
43 | _memory->AddSigScan({0x48, 0x89, 0x5C, 0x24, 0x10, 0x56, 0x48, 0x83, 0xEC, 0x20, 0x48, 0x63, 0xDA, 0x48, 0x8B, 0xF1, 0x83, 0xFB, 0x01}, [&](int index) { | ||
44 | AdjustRng(index + 0x23); | ||
45 | }); | ||
46 | // shuffle<int> | ||
47 | _memory->AddSigScan({0x33, 0xF6, 0x48, 0x8B, 0xD9, 0x39, 0x31, 0x7E, 0x51}, [&](int index) { | ||
48 | AdjustRng(index - 0x4); | ||
49 | }); | ||
50 | // cut_random_edges | ||
51 | _memory->AddSigScan({0x89, 0x44, 0x24, 0x3C, 0x33, 0xC0, 0x85, 0xC0, 0x75, 0xFA}, [&](int index) { | ||
52 | AdjustRng(index + 0x3B); | ||
53 | }); | ||
54 | // get_empty_decoration_slot | ||
55 | _memory->AddSigScan({0x42, 0x83, 0x3C, 0x80, 0x00, 0x75, 0xDF}, [&](int index) { | ||
56 | AdjustRng(index - 0x17); | ||
57 | }); | ||
58 | // get_empty_dot_spot | ||
59 | _memory->AddSigScan({0xF7, 0xF3, 0x85, 0xD2, 0x74, 0xEC}, [&](int index) { | ||
60 | AdjustRng(index - 0xB); | ||
61 | }); | ||
62 | // add_exactly_this_many_bisection_dots | ||
63 | _memory->AddSigScan({0x48, 0x8B, 0xB4, 0x24, 0xB8, 0x00, 0x00, 0x00, 0x48, 0x8B, 0xBC, 0x24, 0xB0, 0x00, 0x00, 0x00}, [&](int index) { | ||
64 | AdjustRng(index - 0x4); | ||
65 | }); | ||
66 | // make_a_shaper | ||
67 | _memory->AddSigScan({0xF7, 0xE3, 0xD1, 0xEA, 0x8D, 0x0C, 0x52}, [&](int index) { | ||
68 | AdjustRng(index - 0x10); | ||
69 | AdjustRng(index + 0x1C); | ||
70 | AdjustRng(index + 0x49); | ||
71 | }); | ||
72 | // Entity_Machine_Panel::init_pattern_data_lotus | ||
73 | _memory->AddSigScan({0x40, 0x55, 0x56, 0x48, 0x8D, 0x6C, 0x24, 0xB1}, [&](int index) { | ||
74 | AdjustRng(index + 0x433); | ||
75 | AdjustRng(index + 0x45B); | ||
76 | AdjustRng(index + 0x5A7); | ||
77 | AdjustRng(index + 0x5D6); | ||
78 | AdjustRng(index + 0x6F6); | ||
79 | AdjustRng(index + 0xD17); | ||
80 | AdjustRng(index + 0xFDA); | ||
81 | }); | ||
82 | // Entity_Record_Player::reroll_lotus_eater_stuff | ||
83 | _memory->AddSigScan({0xB8, 0xAB, 0xAA, 0xAA, 0xAA, 0x41, 0xC1, 0xE8}, [&](int index) { | ||
84 | AdjustRng(index - 0x13); | ||
85 | AdjustRng(index + 0x34); | ||
86 | }); | ||
87 | |||
88 | // These disable the random locations on timer panels, which would otherwise increment the RNG. | ||
89 | // I'm writing 31 C0 (xor eax, eax), then 3 NOPs, which pretends the RNG returns 0. | ||
90 | // do_lotus_minutes | ||
91 | _memory->AddSigScan({0x0F, 0xBE, 0x6C, 0x08, 0xFF, 0x45}, [&](int index) { | ||
92 | _memory->WriteData<byte>({index + 0x410}, {0x31, 0xC0, 0x90, 0x90, 0x90}); | ||
93 | }); | ||
94 | // do_lotus_tenths | ||
95 | _memory->AddSigScan({0x00, 0x04, 0x00, 0x00, 0x41, 0x8D, 0x50, 0x09}, [&](int index) { | ||
96 | _memory->WriteData<byte>({index + 0xA2}, {0x31, 0xC0, 0x90, 0x90, 0x90}); | ||
97 | }); | ||
98 | // do_lotus_eighths | ||
99 | _memory->AddSigScan({0x75, 0xF5, 0x0F, 0xBE, 0x44, 0x08, 0xFF}, [&](int index) { | ||
100 | _memory->WriteData<byte>({index + 0x1AE}, {0x31, 0xC0, 0x90, 0x90, 0x90}); | ||
101 | }); | ||
102 | } | ||
103 | |||
104 | int failed = _memory->ExecuteSigScans(); | ||
105 | if (failed != 0) { | ||
106 | std::cout << "Failed " << failed << " sigscans"; | ||
107 | } | ||
108 | } | ||
diff --git a/Source/ChallengeRandomizer.h b/Source/ChallengeRandomizer.h deleted file mode 100644 index b06be81..0000000 --- a/Source/ChallengeRandomizer.h +++ /dev/null | |||
@@ -1,13 +0,0 @@ | |||
1 | #pragma once | ||
2 | |||
3 | class ChallengeRandomizer { | ||
4 | public: | ||
5 | ChallengeRandomizer(const std::shared_ptr<Memory>& memory, int seed); | ||
6 | |||
7 | private: | ||
8 | void AdjustRng(int offset); | ||
9 | std::shared_ptr<Memory> _memory; | ||
10 | |||
11 | int RNG_ADDR; | ||
12 | int RNG2_ADDR; | ||
13 | }; | ||
diff --git a/Source/Randomizer.cpp b/Source/Randomizer.cpp index 74166af..5239cd6 100644 --- a/Source/Randomizer.cpp +++ b/Source/Randomizer.cpp | |||
@@ -93,7 +93,6 @@ Things to do for V2: | |||
93 | */ | 93 | */ |
94 | #include "pch.h" | 94 | #include "pch.h" |
95 | #include "Randomizer.h" | 95 | #include "Randomizer.h" |
96 | #include "ChallengeRandomizer.h" | ||
97 | #include "Panels.h" | 96 | #include "Panels.h" |
98 | #include "Random.h" | 97 | #include "Random.h" |
99 | 98 | ||
@@ -124,14 +123,6 @@ void Randomizer::Randomize() { | |||
124 | } | 123 | } |
125 | _memory->WriteData<byte>({index}, {0xEB}); // jz -> jmp | 124 | _memory->WriteData<byte>({index}, {0xEB}); // jz -> jmp |
126 | }); | 125 | }); |
127 | // Sig scans will be run during challenge randomization. | ||
128 | |||
129 | // Seed challenge first for future-proofing | ||
130 | MEMORY_CATCH(RandomizeChallenge()); | ||
131 | |||
132 | // Content swaps -- must happen before squarePanels | ||
133 | //MEMORY_CATCH(Randomize(upDownPanels, SWAP::LINES | SWAP::COLORS)); | ||
134 | //MEMORY_CATCH(Randomize(leftForwardRightPanels, SWAP::LINES | SWAP::COLORS)); | ||
135 | 126 | ||
136 | // Tutorial Bend | 127 | // Tutorial Bend |
137 | for (int panel : utmPerspective) { | 128 | for (int panel : utmPerspective) { |
@@ -141,220 +132,23 @@ void Randomizer::Randomize() { | |||
141 | for (int panel : squarePanels) { | 132 | for (int panel : squarePanels) { |
142 | Tutorialise(panel, 0x00064); | 133 | Tutorialise(panel, 0x00064); |
143 | } | 134 | } |
144 | //Randomize(squarePanels, SWAP::LINES | SWAP::COLORS); | ||
145 | |||
146 | // Individual area modifications | ||
147 | MEMORY_CATCH(RandomizeTutorial()); | ||
148 | MEMORY_CATCH(RandomizeDesert()); | ||
149 | MEMORY_CATCH(RandomizeQuarry()); | ||
150 | MEMORY_CATCH(RandomizeTreehouse()); | ||
151 | MEMORY_CATCH(RandomizeKeep()); | ||
152 | MEMORY_CATCH(RandomizeShadows()); | ||
153 | MEMORY_CATCH(RandomizeMonastery()); | ||
154 | MEMORY_CATCH(RandomizeBunker()); | ||
155 | MEMORY_CATCH(RandomizeJungle()); | ||
156 | MEMORY_CATCH(RandomizeSwamp()); | ||
157 | MEMORY_CATCH(RandomizeMountain()); | ||
158 | MEMORY_CATCH(RandomizeTown()); | ||
159 | MEMORY_CATCH(RandomizeSymmetry()); | ||
160 | // RandomizeAudioLogs(); | ||
161 | } | ||
162 | |||
163 | void Randomizer::AdjustSpeed() { | ||
164 | // Desert Surface Final Control | ||
165 | _memory->WriteEntityData<float>(0x09F95, OPEN_RATE, {0.04f}); // 4x | ||
166 | // Swamp Sliding Bridge | ||
167 | _memory->WriteEntityData<float>(0x0061A, OPEN_RATE, {0.1f}); // 4x | ||
168 | // Mountain 2 Elevator | ||
169 | _memory->WriteEntityData<float>(0x09EEC, OPEN_RATE, {0.075f}); // 3x | ||
170 | } | ||
171 | |||
172 | void Randomizer::RandomizeLasers() { | ||
173 | Randomize(lasers, SWAP::TARGETS); | ||
174 | // Read the target of keep front laser, and write it to keep back laser. | ||
175 | std::vector<int> keepFrontLaserTarget = _memory->ReadEntityData<int>(0x0360E, TARGET, 1); | ||
176 | _memory->WriteEntityData<int>(0x03317, TARGET, keepFrontLaserTarget); | ||
177 | } | ||
178 | 135 | ||
179 | void Randomizer::PreventSnipes() | ||
180 | { | ||
181 | // Distance-gate swamp snipe 1 to prevent RNG swamp snipe | ||
182 | //_memory->WriteEntityData<float>(0x17C05, MAX_BROADCAST_DISTANCE, {15.0}); | ||
183 | // Distance-gate shadows laser to prevent sniping through the bars | ||
184 | //_memory->WriteEntityData<float>(0x19650, MAX_BROADCAST_DISTANCE, {2.5}); | ||
185 | } | ||
186 | |||
187 | // Private methods | ||
188 | void Randomizer::RandomizeTutorial() { | ||
189 | // Disable tutorial cursor speed modifications (not working?) | 136 | // Disable tutorial cursor speed modifications (not working?) |
190 | _memory->WriteEntityData<float>(0x00295, CURSOR_SPEED_SCALE, {1.0}); | 137 | _memory->WriteEntityData<float>(0x00295, CURSOR_SPEED_SCALE, { 1.0 }); |
191 | _memory->WriteEntityData<float>(0x0C373, CURSOR_SPEED_SCALE, {1.0}); | 138 | _memory->WriteEntityData<float>(0x0C373, CURSOR_SPEED_SCALE, { 1.0 }); |
192 | _memory->WriteEntityData<float>(0x00293, CURSOR_SPEED_SCALE, {1.0}); | 139 | _memory->WriteEntityData<float>(0x00293, CURSOR_SPEED_SCALE, { 1.0 }); |
193 | _memory->WriteEntityData<float>(0x002C2, CURSOR_SPEED_SCALE, {1.0}); | 140 | _memory->WriteEntityData<float>(0x002C2, CURSOR_SPEED_SCALE, { 1.0 }); |
194 | } | ||
195 | |||
196 | void Randomizer::RandomizeSymmetry() { | ||
197 | /*std::vector<int> randomOrder(transparent.size(), 0); | ||
198 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
199 | RandomizeRange(randomOrder, SWAP::NONE, 1, 5); | ||
200 | ReassignTargets(transparent, randomOrder);*/ | ||
201 | } | ||
202 | |||
203 | void Randomizer::RandomizeDesert() { | ||
204 | //Randomize(desertPanels, SWAP::LINES); | ||
205 | |||
206 | // Turn off desert surface 8 | ||
207 | /*_memory->WriteEntityData<float>(0x09F94, POWER, {0.0, 0.0}); | ||
208 | // Turn off desert flood final | ||
209 | _memory->WriteEntityData<float>(0x18076, POWER, {0.0, 0.0}); | ||
210 | // Change desert floating target to desert flood final | ||
211 | _memory->WriteEntityData<int>(0x17ECA, TARGET, {0x18077});*/ | ||
212 | } | ||
213 | |||
214 | void Randomizer::RandomizeQuarry() { | ||
215 | } | ||
216 | 141 | ||
217 | void Randomizer::RandomizeTreehouse() { | ||
218 | // Ensure that whatever pivot panels we have are flagged as "pivotable" | 142 | // Ensure that whatever pivot panels we have are flagged as "pivotable" |
219 | // @Bug: Can return {}, be careful! | 143 | // @Bug: Can return {}, be careful! |
220 | int panelFlags = _memory->ReadEntityData<int>(0x17DD1, STYLE_FLAGS, 1)[0]; | 144 | int panelFlags = _memory->ReadEntityData<int>(0x17DD1, STYLE_FLAGS, 1)[0]; |
221 | _memory->WriteEntityData<int>(0x17DD1, STYLE_FLAGS, {panelFlags | 0x8000}); | 145 | _memory->WriteEntityData<int>(0x17DD1, STYLE_FLAGS, { panelFlags | 0x8000 }); |
222 | panelFlags = _memory->ReadEntityData<int>(0x17CE3, STYLE_FLAGS, 1)[0]; | 146 | panelFlags = _memory->ReadEntityData<int>(0x17CE3, STYLE_FLAGS, 1)[0]; |
223 | _memory->WriteEntityData<int>(0x17CE3, STYLE_FLAGS, {panelFlags | 0x8000}); | 147 | _memory->WriteEntityData<int>(0x17CE3, STYLE_FLAGS, { panelFlags | 0x8000 }); |
224 | panelFlags = _memory->ReadEntityData<int>(0x17DB7, STYLE_FLAGS, 1)[0]; | 148 | panelFlags = _memory->ReadEntityData<int>(0x17DB7, STYLE_FLAGS, 1)[0]; |
225 | _memory->WriteEntityData<int>(0x17DB7, STYLE_FLAGS, {panelFlags | 0x8000}); | 149 | _memory->WriteEntityData<int>(0x17DB7, STYLE_FLAGS, { panelFlags | 0x8000 }); |
226 | panelFlags = _memory->ReadEntityData<int>(0x17E52, STYLE_FLAGS, 1)[0]; | 150 | panelFlags = _memory->ReadEntityData<int>(0x17E52, STYLE_FLAGS, 1)[0]; |
227 | _memory->WriteEntityData<int>(0x17E52, STYLE_FLAGS, {panelFlags | 0x8000}); | 151 | _memory->WriteEntityData<int>(0x17E52, STYLE_FLAGS, { panelFlags | 0x8000 }); |
228 | } | ||
229 | |||
230 | void Randomizer::RandomizeKeep() { | ||
231 | } | ||
232 | |||
233 | void Randomizer::RandomizeShadows() { | ||
234 | // Change the shadows tutorial cable to only activate avoid | ||
235 | _memory->WriteEntityData<int>(0x319A8, CABLE_TARGET_2, {0}); | ||
236 | // Change shadows avoid 8 to power shadows follow | ||
237 | _memory->WriteEntityData<int>(0x1972F, TARGET, {0x1C34C}); | ||
238 | |||
239 | /*std::vector<int> randomOrder(shadowsPanels.size(), 0); | ||
240 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
241 | RandomizeRange(randomOrder, SWAP::NONE, 0, 8); // Tutorial | ||
242 | RandomizeRange(randomOrder, SWAP::NONE, 8, 16); // Avoid | ||
243 | RandomizeRange(randomOrder, SWAP::NONE, 16, 21); // Follow | ||
244 | ReassignTargets(shadowsPanels, randomOrder); | ||
245 | // Turn off original starting panel | ||
246 | _memory->WriteEntityData<float>(shadowsPanels[0], POWER, {0.0f, 0.0f}); | ||
247 | // Turn on new starting panel | ||
248 | _memory->WriteEntityData<float>(shadowsPanels[randomOrder[0]], POWER, {1.0f, 1.0f});*/ | ||
249 | } | ||
250 | |||
251 | void Randomizer::RandomizeTown() { | ||
252 | // @Hack...? To open the gate at the end | ||
253 | /*std::vector<int> randomOrder(orchard.size() + 1, 0); | ||
254 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
255 | RandomizeRange(randomOrder, SWAP::NONE, 1, 5); | ||
256 | // Ensure that we open the gate before the final puzzle (by swapping) | ||
257 | int panel3Index = find(randomOrder, 3); | ||
258 | int panel4Index = find(randomOrder, 4); | ||
259 | randomOrder[std::min(panel3Index, panel4Index)] = 3; | ||
260 | randomOrder[std::max(panel3Index, panel4Index)] = 4; | ||
261 | ReassignTargets(orchard, randomOrder);*/ | ||
262 | } | ||
263 | |||
264 | void Randomizer::RandomizeMonastery() { | ||
265 | /*std::vector<int> randomOrder(monasteryPanels.size(), 0); | ||
266 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
267 | RandomizeRange(randomOrder, SWAP::NONE, 3, 9); // Outer 2 & 3, Inner 1-4 | ||
268 | ReassignTargets(monasteryPanels, randomOrder);*/ | ||
269 | } | ||
270 | |||
271 | void Randomizer::RandomizeBunker() { | ||
272 | /*std::vector<int> randomOrder(bunkerPanels.size(), 0); | ||
273 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
274 | // Randomize Tutorial 2-Advanced Tutorial 4 + Glass 1 | ||
275 | // Tutorial 1 cannot be randomized, since no other panel can start on | ||
276 | // Glass 1 will become door + glass 1, due to the targetting system | ||
277 | RandomizeRange(randomOrder, SWAP::NONE, 1, 10); | ||
278 | // Randomize Glass 1-3 into everything after the door/glass 1 | ||
279 | const size_t glass1Index = find(randomOrder, 9); | ||
280 | RandomizeRange(randomOrder, SWAP::NONE, glass1Index + 1, 12); | ||
281 | ReassignTargets(bunkerPanels, randomOrder);*/ | ||
282 | } | ||
283 | |||
284 | void Randomizer::RandomizeJungle() { | ||
285 | /*std::vector<int> randomOrder(junglePanels.size(), 0); | ||
286 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
287 | // Waves 1 cannot be randomized, since no other panel can start on | ||
288 | RandomizeRange(randomOrder, SWAP::NONE, 1, 7); // Waves 2-7 | ||
289 | RandomizeRange(randomOrder, SWAP::NONE, 8, 13); // Pitches 1-6 | ||
290 | ReassignTargets(junglePanels, randomOrder);*/ | ||
291 | |||
292 | // Fix the wall's target to point back to the cable, and the cable to point to the pitches panel. | ||
293 | // auto wallTarget = _memory->ReadPanelData<int>(junglePanels[7], TARGET, 1); | ||
294 | // _memory->WritePanelData<int>(junglePanels[7], TARGET, {0x3C113}); | ||
295 | // _memory->WritePanelData<int>(0x3C112, CABLE_TARGET_1, wallTarget); | ||
296 | } | ||
297 | |||
298 | void Randomizer::RandomizeSwamp() { | ||
299 | } | ||
300 | |||
301 | void Randomizer::RandomizeMountain() { | ||
302 | // Randomize multipanel | ||
303 | //Randomize(mountainMultipanel, SWAP::LINES | SWAP::COLORS); | ||
304 | |||
305 | // Randomize final pillars order | ||
306 | /*std::vector<int> targets = {pillars[0] + 1}; | ||
307 | for (const int pillar : pillars) { | ||
308 | int target = _memory->ReadEntityData<int>(pillar, TARGET, 1)[0]; | ||
309 | targets.push_back(target); | ||
310 | } | ||
311 | targets[5] = pillars[5] + 1; | ||
312 | |||
313 | std::vector<int> randomOrder(pillars.size(), 0); | ||
314 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
315 | RandomizeRange(randomOrder, SWAP::NONE, 0, 4); // Left Pillars 1-4 | ||
316 | RandomizeRange(randomOrder, SWAP::NONE, 5, 9); // Right Pillars 1-4 | ||
317 | ReassignTargets(pillars, randomOrder, targets); | ||
318 | // Turn off original starting panels | ||
319 | _memory->WriteEntityData<float>(pillars[0], POWER, {0.0f, 0.0f}); | ||
320 | _memory->WriteEntityData<float>(pillars[5], POWER, {0.0f, 0.0f}); | ||
321 | // Turn on new starting panels | ||
322 | _memory->WriteEntityData<float>(pillars[randomOrder[0]], POWER, {1.0f, 1.0f}); | ||
323 | _memory->WriteEntityData<float>(pillars[randomOrder[5]], POWER, {1.0f, 1.0f});*/ | ||
324 | } | ||
325 | |||
326 | void Randomizer::RandomizeChallenge() { | ||
327 | /*ChallengeRandomizer cr(_memory, Random::RandInt(1, 0x7FFFFFFF)); // 0 will trigger an "RNG not initialized" block | ||
328 | for (int panel : challengePanels) { | ||
329 | _memory->WriteEntityData<int>(panel, POWER_OFF_ON_FAIL, {0}); | ||
330 | }*/ | ||
331 | } | ||
332 | |||
333 | void Randomizer::RandomizeAudioLogs() { | ||
334 | std::vector<int> randomOrder(audiologs.size(), 0); | ||
335 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
336 | Randomize(randomOrder, SWAP::NONE); | ||
337 | ReassignNames(audiologs, randomOrder); | ||
338 | } | ||
339 | |||
340 | void Randomizer::Randomize(std::vector<int>& panels, int flags) { | ||
341 | return RandomizeRange(panels, flags, 0, panels.size()); | ||
342 | } | ||
343 | |||
344 | // Range is [start, end) | ||
345 | void Randomizer::RandomizeRange(std::vector<int> &panels, int flags, size_t startIndex, size_t endIndex) { | ||
346 | if (panels.size() == 0) return; | ||
347 | if (startIndex >= endIndex) return; | ||
348 | if (endIndex >= panels.size()) endIndex = static_cast<int>(panels.size()); | ||
349 | for (size_t i = 0; i < panels.size(); i++) { | ||
350 | /*const int target = Random::RandInt(static_cast<int>(startIndex), static_cast<int>(i)); | ||
351 | if (i != target) { | ||
352 | // std::cout << "Swapping panels " << std::hex << panels[i] << " and " << std::hex << panels[target] << std::endl; | ||
353 | SwapPanels(panels[i], panels[target], flags); | ||
354 | //std::swap(panels[i], panels[target]); // Panel indices in the array | ||
355 | }*/ | ||
356 | |||
357 | } | ||
358 | } | 152 | } |
359 | 153 | ||
360 | void Randomizer::Tutorialise(int panel1, int tutorialStraight) { | 154 | void Randomizer::Tutorialise(int panel1, int tutorialStraight) { |
@@ -421,32 +215,3 @@ void Randomizer::Tutorialise(int panel1, int tutorialStraight) { | |||
421 | //arrays.push_back(SPECULAR_TEXTURE); | 215 | //arrays.push_back(SPECULAR_TEXTURE); |
422 | 216 | ||
423 | } | 217 | } |
424 | |||
425 | void Randomizer::ReassignTargets(const std::vector<int>& panels, const std::vector<int>& order, std::vector<int> targets) { | ||
426 | if (targets.empty()) { | ||
427 | // This list is offset by 1, so the target of the Nth panel is in position N (aka the N+1th element) | ||
428 | // The first panel may not have a wire to power it, so we use the panel ID itself. | ||
429 | targets = {panels[0] + 1}; | ||
430 | for (const int panel : panels) { | ||
431 | int target = _memory->ReadEntityData<int>(panel, TARGET, 1)[0]; | ||
432 | targets.push_back(target); | ||
433 | } | ||
434 | } | ||
435 | |||
436 | for (size_t i=0; i<order.size() - 1; i++) { | ||
437 | // Set the target of order[i] to order[i+1], using the "real" target as determined above. | ||
438 | const int panelTarget = targets[order[i+1]]; | ||
439 | _memory->WriteEntityData<int>(panels[order[i]], TARGET, {panelTarget}); | ||
440 | } | ||
441 | } | ||
442 | |||
443 | void Randomizer::ReassignNames(const std::vector<int>& panels, const std::vector<int>& order) { | ||
444 | std::vector<int64_t> names; | ||
445 | for (const int panel : panels) { | ||
446 | names.push_back(_memory->ReadEntityData<int64_t>(panel, AUDIO_LOG_NAME, 1)[0]); | ||
447 | } | ||
448 | |||
449 | for (int i=0; i<panels.size(); i++) { | ||
450 | _memory->WriteEntityData<int64_t>(panels[i], AUDIO_LOG_NAME, {names[order[i]]}); | ||
451 | } | ||
452 | } | ||
diff --git a/Source/Randomizer.h b/Source/Randomizer.h index ae89dc9..a26acab 100644 --- a/Source/Randomizer.h +++ b/Source/Randomizer.h | |||
@@ -4,42 +4,11 @@ class Randomizer { | |||
4 | public: | 4 | public: |
5 | Randomizer(const std::shared_ptr<Memory>& memory); | 5 | Randomizer(const std::shared_ptr<Memory>& memory); |
6 | void Randomize(); | 6 | void Randomize(); |
7 | void RandomizeChallenge(); | ||
8 | |||
9 | void AdjustSpeed(); | ||
10 | void RandomizeLasers(); | ||
11 | void PreventSnipes(); | ||
12 | |||
13 | enum SWAP { | ||
14 | NONE = 0, | ||
15 | TARGETS = 1, | ||
16 | LINES = 2, | ||
17 | AUDIO_NAMES = 4, | ||
18 | COLORS = 8, | ||
19 | }; | ||
20 | 7 | ||
21 | private: | 8 | private: |
22 | int _lastRandomizedFrame = 1 << 30; | 9 | int _lastRandomizedFrame = 1 << 30; |
23 | void RandomizeTutorial(); | ||
24 | void RandomizeSymmetry(); | ||
25 | void RandomizeDesert(); | ||
26 | void RandomizeQuarry(); | ||
27 | void RandomizeTreehouse(); | ||
28 | void RandomizeKeep(); | ||
29 | void RandomizeShadows(); | ||
30 | void RandomizeTown(); | ||
31 | void RandomizeMonastery(); | ||
32 | void RandomizeBunker(); | ||
33 | void RandomizeJungle(); | ||
34 | void RandomizeSwamp(); | ||
35 | void RandomizeMountain(); | ||
36 | void RandomizeAudioLogs(); | ||
37 | 10 | ||
38 | void Randomize(std::vector<int>& panels, int flags); | ||
39 | void RandomizeRange(std::vector<int> &panels, int flags, size_t startIndex, size_t endIndex); | ||
40 | void Tutorialise(int panel1, int copyFrom); | 11 | void Tutorialise(int panel1, int copyFrom); |
41 | void ReassignTargets(const std::vector<int>& panels, const std::vector<int>& order, std::vector<int> targets = {}); | ||
42 | void ReassignNames(const std::vector<int>& panels, const std::vector<int>& order); | ||
43 | 12 | ||
44 | std::shared_ptr<Memory> _memory; | 13 | std::shared_ptr<Memory> _memory; |
45 | 14 | ||
diff --git a/Source/Solver.cpp b/Source/Solver.cpp deleted file mode 100644 index 74fa099..0000000 --- a/Source/Solver.cpp +++ /dev/null | |||
@@ -1,76 +0,0 @@ | |||
1 | #include "pch.h" | ||
2 | #include "Solver.h" | ||
3 | #include "Validator.h" | ||
4 | |||
5 | int Solver::MAX_SOLUTIONS = 10000; | ||
6 | |||
7 | std::vector<Puzzle> Solver::Solve(Puzzle& p) { | ||
8 | std::vector<Puzzle> solutions; | ||
9 | // var start = (new Date()).getTime() | ||
10 | for (int x = 0; x < p.width; x++) { | ||
11 | for (int y = 0; y < p.height; y++) { | ||
12 | Cell cell = p.grid[x][y]; | ||
13 | if (cell.start) { | ||
14 | SolveLoop(p, x, y, solutions); | ||
15 | } | ||
16 | } | ||
17 | } | ||
18 | // var end = (new Date()).getTime() | ||
19 | // console.info('Solved', puzzle, 'in', (end-start)/1000, 'seconds') | ||
20 | return solutions; | ||
21 | } | ||
22 | |||
23 | void Solver::SolveLoop(Puzzle& p, int x, int y, std::vector<Puzzle>& solutions) { | ||
24 | // Stop trying to solve once we reach our goal | ||
25 | if (solutions.size() >= MAX_SOLUTIONS) return; | ||
26 | Cell cell = p.GetCell(x, y); | ||
27 | if (cell.undefined) return; | ||
28 | if (cell.gap != Cell::Gap::NONE) return; | ||
29 | |||
30 | if (p.symmetry == Puzzle::Symmetry::NONE) { | ||
31 | if (cell.color != Cell::Color::NONE) return; // Collided with ourselves | ||
32 | p.grid[x][y].color = Cell::Color::BLACK; // Otherwise, mark this cell as visited | ||
33 | } else { | ||
34 | // Get the symmetrical position, and try coloring it | ||
35 | auto sym = p.GetSymmetricalPos(x, y); | ||
36 | Cell::Color oldColor = p.GetLine(sym.x, sym.y); | ||
37 | p.grid[sym.x][sym.y].color = Cell::Color::YELLOW; | ||
38 | |||
39 | // Collided with ourselves or our reflection | ||
40 | if (cell.color != Cell::Color::NONE) { | ||
41 | p.grid[sym.x][sym.y].color = oldColor; | ||
42 | return; | ||
43 | } | ||
44 | p.grid[x][y].color = Cell::Color::BLUE; // Otherwise, mark this cell as visited | ||
45 | } | ||
46 | p.sequence.emplace_back(x, y); | ||
47 | |||
48 | if (cell.end != Cell::Dir::NONE) { | ||
49 | // Reached an endpoint, validate solution and keep going -- there may be other endpoints | ||
50 | Validator::Validate(p); | ||
51 | if (p.valid) { | ||
52 | Puzzle clone = p; | ||
53 | solutions.push_back(clone); | ||
54 | } | ||
55 | } | ||
56 | |||
57 | // Recursion order (LRUD) is optimized for BL->TR and mid-start puzzles | ||
58 | // Extend path left and right | ||
59 | if (y % 2 == 0) { | ||
60 | SolveLoop(p, x - 1, y, solutions); | ||
61 | SolveLoop(p, x + 1, y, solutions); | ||
62 | } | ||
63 | // Extend path up and down | ||
64 | if (x % 2 == 0) { | ||
65 | SolveLoop(p, x, y - 1, solutions); | ||
66 | SolveLoop(p, x, y + 1, solutions); | ||
67 | } | ||
68 | |||
69 | // Tail recursion: Back out of this cell | ||
70 | p.grid[x][y].color = Cell::Color::NONE; | ||
71 | p.sequence.pop_back(); | ||
72 | if (p.symmetry != Puzzle::Symmetry::NONE) { | ||
73 | auto sym = p.GetSymmetricalPos(x, y); | ||
74 | p.grid[sym.x][sym.y].color = Cell::Color::NONE; | ||
75 | } | ||
76 | } | ||
diff --git a/Source/Solver.h b/Source/Solver.h deleted file mode 100644 index 455d1eb..0000000 --- a/Source/Solver.h +++ /dev/null | |||
@@ -1,13 +0,0 @@ | |||
1 | #pragma once | ||
2 | #include <vector> | ||
3 | |||
4 | class Puzzle; | ||
5 | class Solver { | ||
6 | public: | ||
7 | static int MAX_SOLUTIONS; | ||
8 | static std::vector<Puzzle> Solve(Puzzle& p); | ||
9 | |||
10 | private: | ||
11 | static void SolveLoop(Puzzle& p, int x, int y, std::vector<Puzzle>& solutions); | ||
12 | }; | ||
13 | |||
diff --git a/Source/Source.vcxproj b/Source/Source.vcxproj index 8d6104c..7d6b20a 100644 --- a/Source/Source.vcxproj +++ b/Source/Source.vcxproj | |||
@@ -161,7 +161,6 @@ | |||
161 | </Link> | 161 | </Link> |
162 | </ItemDefinitionGroup> | 162 | </ItemDefinitionGroup> |
163 | <ItemGroup> | 163 | <ItemGroup> |
164 | <ClInclude Include="ChallengeRandomizer.h" /> | ||
165 | <ClInclude Include="json.hpp" /> | 164 | <ClInclude Include="json.hpp" /> |
166 | <ClInclude Include="Memory.h" /> | 165 | <ClInclude Include="Memory.h" /> |
167 | <ClInclude Include="MemoryException.h" /> | 166 | <ClInclude Include="MemoryException.h" /> |
@@ -172,11 +171,8 @@ | |||
172 | <ClInclude Include="PuzzleSerializer.h" /> | 171 | <ClInclude Include="PuzzleSerializer.h" /> |
173 | <ClInclude Include="Random.h" /> | 172 | <ClInclude Include="Random.h" /> |
174 | <ClInclude Include="Randomizer.h" /> | 173 | <ClInclude Include="Randomizer.h" /> |
175 | <ClInclude Include="Solver.h" /> | ||
176 | <ClInclude Include="Validator.h" /> | ||
177 | </ItemGroup> | 174 | </ItemGroup> |
178 | <ItemGroup> | 175 | <ItemGroup> |
179 | <ClCompile Include="ChallengeRandomizer.cpp" /> | ||
180 | <ClCompile Include="Memory.cpp" /> | 176 | <ClCompile Include="Memory.cpp" /> |
181 | <ClCompile Include="pch.cpp"> | 177 | <ClCompile Include="pch.cpp"> |
182 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> | 178 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> |
@@ -188,8 +184,6 @@ | |||
188 | <ClCompile Include="PuzzleSerializer.cpp" /> | 184 | <ClCompile Include="PuzzleSerializer.cpp" /> |
189 | <ClCompile Include="Random.cpp" /> | 185 | <ClCompile Include="Random.cpp" /> |
190 | <ClCompile Include="Randomizer.cpp" /> | 186 | <ClCompile Include="Randomizer.cpp" /> |
191 | <ClCompile Include="Solver.cpp" /> | ||
192 | <ClCompile Include="Validator.cpp" /> | ||
193 | </ItemGroup> | 187 | </ItemGroup> |
194 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | 188 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |
195 | <ImportGroup Label="ExtensionTargets"> | 189 | <ImportGroup Label="ExtensionTargets"> |
diff --git a/Source/Validator.cpp b/Source/Validator.cpp deleted file mode 100644 index 3b4020b..0000000 --- a/Source/Validator.cpp +++ /dev/null | |||
@@ -1,97 +0,0 @@ | |||
1 | #include "pch.h" | ||
2 | #include "Validator.h" | ||
3 | |||
4 | void Validator::Validate(Puzzle& p) { | ||
5 | // console.log('Validating', puzzle); | ||
6 | p.valid = true; // Assume valid until we find an invalid element | ||
7 | p.invalidElements.clear(); | ||
8 | p.negations.clear(); | ||
9 | |||
10 | bool puzzleHasSymbols = false; | ||
11 | bool puzzleHasStart = false; | ||
12 | bool puzzleHasEnd = false; | ||
13 | // Validate gap failures as an early exit. | ||
14 | for (int x = 0; x < p.width; x++) { | ||
15 | for (int y = 0; y < p.height; y++) { | ||
16 | const Cell& cell = p.grid[x][y]; | ||
17 | const auto& decoration = cell.decoration; | ||
18 | if (decoration) { | ||
19 | if (decoration->type == Type::Stone || | ||
20 | decoration->type == Type::Star || | ||
21 | decoration->type == Type::Nega || | ||
22 | decoration->type == Type::Poly || | ||
23 | decoration->type == Type::Ylop) { | ||
24 | puzzleHasSymbols = true; | ||
25 | continue; | ||
26 | } | ||
27 | if (decoration->type == Type::Triangle) { | ||
28 | int actualCount = 0; | ||
29 | if (p.GetLine(x - 1, y) != Cell::Color::NONE) actualCount++; | ||
30 | if (p.GetLine(x + 1, y) != Cell::Color::NONE) actualCount++; | ||
31 | if (p.GetLine(x, y - 1) != Cell::Color::NONE) actualCount++; | ||
32 | if (p.GetLine(x, y + 1) != Cell::Color::NONE) actualCount++; | ||
33 | if (decoration->count != actualCount) { | ||
34 | // console.log('Triangle at grid['+x+']['+y+'] has', actualCount, 'borders') | ||
35 | p.invalidElements.emplace_back(x, y); | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | if (cell.gap != Cell::Gap::NONE && cell.color != Cell::Color::NONE) { | ||
40 | // console.log('Gap at', x, y, 'is covered') | ||
41 | p.valid = false; | ||
42 | } | ||
43 | if (cell.dot != Cell::Dot::NONE) { | ||
44 | if (cell.color == Cell::Color::NONE) { | ||
45 | // console.log('Dot at', x, y, 'is not covered') | ||
46 | p.invalidElements.emplace_back(x, y); | ||
47 | } else if (cell.color == Cell::Color::BLUE && cell.dot == Cell::Dot::YELLOW) { | ||
48 | // console.log('Yellow dot at', x, y, 'is covered by blue line') | ||
49 | p.valid = false; | ||
50 | } else if (cell.color == Cell::Color::YELLOW && cell.dot == Cell::Dot::BLUE) { | ||
51 | // console.log('Blue dot at', x, y, 'is covered by yellow line') | ||
52 | p.valid = false; | ||
53 | } | ||
54 | } | ||
55 | if (cell.color != Cell::Color::NONE) { | ||
56 | if (cell.start == true) puzzleHasStart = true; | ||
57 | if (cell.end != Cell::Dir::NONE) puzzleHasEnd = true; | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | if (!puzzleHasStart || !puzzleHasEnd) { | ||
62 | // console.log('There is no covered start or endpoint') | ||
63 | p.valid = false; | ||
64 | } | ||
65 | |||
66 | // Perf optimization: We can skip computing regions if the grid has no symbols. | ||
67 | if (!puzzleHasSymbols) { // No additional symbols, and we already checked dots & gaps | ||
68 | p.valid &= (p.invalidElements.size() == 0); | ||
69 | } else { // Additional symbols, so we need to discard dots & divide them by region | ||
70 | /* | ||
71 | p.invalidElements.clear(); | ||
72 | std::vector<Region> regions = p.GetRegions(); | ||
73 | // console.log('Found', regions.length, 'regions'); | ||
74 | // console.debug(regions); | ||
75 | |||
76 | for (const Region& region : regions) { | ||
77 | std::string key = region.grid.ToString(); | ||
78 | auto regionData = puzzle.regionCache[key]; | ||
79 | if (regionData == undefined) { | ||
80 | console.log('Cache miss for region', region, 'key', key); | ||
81 | regionData = _regionCheckNegations(puzzle, region); | ||
82 | // Entirely for convenience | ||
83 | regionData.valid = (regionData.invalidElements.size() == 0) | ||
84 | // console.log('Region valid:', regionData.valid); | ||
85 | |||
86 | if (!DISABLE_CACHE) { | ||
87 | p.regionCache[key] = regionData; | ||
88 | } | ||
89 | } | ||
90 | p.negations = p.negations.concat(regionData.negations); | ||
91 | p.invalidElements = p.invalidElements.concat(regionData.invalidElements); | ||
92 | p.valid = p.valid && regionData.valid; | ||
93 | } | ||
94 | */ | ||
95 | } | ||
96 | // console.log('Puzzle has', puzzle.invalidElements.length, 'invalid elements') | ||
97 | } | ||
diff --git a/Source/Validator.h b/Source/Validator.h deleted file mode 100644 index 2a102dd..0000000 --- a/Source/Validator.h +++ /dev/null | |||
@@ -1,27 +0,0 @@ | |||
1 | #pragma once | ||
2 | #include <vector> | ||
3 | #include <tuple> | ||
4 | |||
5 | #ifndef NEGATIONS_CANCEL_NEGATIONS | ||
6 | #define NEGATIONS_CANCEL_NEGATIONS true | ||
7 | #endif | ||
8 | |||
9 | #ifndef SIMPLE_POLYOMINOS | ||
10 | #define SIMPLE_POLYOMINOS true | ||
11 | #endif | ||
12 | |||
13 | #ifndef DISABLE_CACHE | ||
14 | #define DISABLE_CACHE false | ||
15 | #endif | ||
16 | |||
17 | struct Region{}; | ||
18 | class Puzzle; | ||
19 | struct Pos; | ||
20 | class Validator { | ||
21 | public: | ||
22 | static void Validate(Puzzle& p); | ||
23 | |||
24 | private: | ||
25 | static void RegionCheckNegations(Puzzle& p, const Region& r); | ||
26 | static std::vector<Pos> RegionCheck(Puzzle& p, const Region& r); | ||
27 | }; | ||