diff options
-rw-r--r-- | App/Main.cpp | 180 | ||||
-rw-r--r-- | App/Version.h | 8 | ||||
-rw-r--r-- | Source/Randomizer2.cpp | 644 | ||||
-rw-r--r-- | Source/Randomizer2.h | 19 | ||||
-rw-r--r-- | Source/Randomizer2Core.cpp | 203 | ||||
-rw-r--r-- | Source/Randomizer2Core.h | 17 | ||||
-rw-r--r-- | Source/Source.vcxproj | 4 |
7 files changed, 23 insertions, 1052 deletions
diff --git a/App/Main.cpp b/App/Main.cpp index 00bf29a..ad3d127 100644 --- a/App/Main.cpp +++ b/App/Main.cpp | |||
@@ -5,17 +5,12 @@ | |||
5 | #include "Memory.h" | 5 | #include "Memory.h" |
6 | #include "Random.h" | 6 | #include "Random.h" |
7 | #include "Randomizer.h" | 7 | #include "Randomizer.h" |
8 | #include "Randomizer2.h" | ||
9 | #include "Panels_.h" | 8 | #include "Panels_.h" |
10 | 9 | ||
11 | #define HEARTBEAT 0x401 | 10 | #define HEARTBEAT 0x401 |
12 | #define RANDOMIZE_READY 0x402 | 11 | #define RANDOMIZE_READY 0x402 |
13 | #define RANDOMIZING 0403 | 12 | #define RANDOMIZING 0403 |
14 | #define RANDOMIZE_DONE 0x404 | 13 | #define RANDOMIZE_DONE 0x404 |
15 | #define RANDOMIZE_CHALLENGE_DONE 0x405 | ||
16 | #define CHALLENGE_ONLY 0x406 | ||
17 | #define DISABLE_SNIPES 0x407 | ||
18 | #define SPEED_UP_AUTOSCROLLERS 0x408 | ||
19 | 14 | ||
20 | /* ------- Temp ------- */ | 15 | /* ------- Temp ------- */ |
21 | #include "Solver.h" | 16 | #include "Solver.h" |
@@ -23,10 +18,6 @@ | |||
23 | #include <sstream> | 18 | #include <sstream> |
24 | #include <iomanip> | 19 | #include <iomanip> |
25 | 20 | ||
26 | #define TMP1 0x501 | ||
27 | #define TMP2 0x502 | ||
28 | #define TMP3 0x503 | ||
29 | #define TMP4 0x504 | ||
30 | 21 | ||
31 | HWND g_panelId; | 22 | HWND g_panelId; |
32 | Puzzle g_puzzle; | 23 | Puzzle g_puzzle; |
@@ -37,13 +28,11 @@ HWND g_rngDebug; | |||
37 | 28 | ||
38 | // Globals | 29 | // Globals |
39 | HWND g_hwnd; | 30 | HWND g_hwnd; |
40 | HWND g_seed; | 31 | //HWND g_seed; |
41 | HWND g_randomizerStatus; | 32 | HWND g_randomizerStatus; |
42 | HINSTANCE g_hInstance; | 33 | HINSTANCE g_hInstance; |
43 | auto g_witnessProc = std::make_shared<Memory>(L"witness64_d3d11.exe"); | 34 | auto g_witnessProc = std::make_shared<Memory>(L"witness64_d3d11.exe"); |
44 | std::shared_ptr<Randomizer> g_randomizer; | 35 | std::shared_ptr<Randomizer> g_randomizer; |
45 | std::shared_ptr<Randomizer2> g_randomizer2; | ||
46 | void SetRandomSeed(); | ||
47 | 36 | ||
48 | LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { | 37 | LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { |
49 | if (message == WM_DESTROY) { | 38 | if (message == WM_DESTROY) { |
@@ -64,171 +53,57 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |||
64 | // Shut down randomizer, wait for startup | 53 | // Shut down randomizer, wait for startup |
65 | if (g_randomizer) { | 54 | if (g_randomizer) { |
66 | g_randomizer = nullptr; | 55 | g_randomizer = nullptr; |
67 | g_randomizer2 = nullptr; | ||
68 | EnableWindow(g_randomizerStatus, FALSE); | 56 | EnableWindow(g_randomizerStatus, FALSE); |
69 | } | 57 | } |
70 | break; | 58 | break; |
71 | case ProcStatus::Running: | 59 | case ProcStatus::Running: |
72 | if (!g_randomizer) { | 60 | if (!g_randomizer) { |
73 | g_randomizer = std::make_shared<Randomizer>(g_witnessProc); | 61 | g_randomizer = std::make_shared<Randomizer>(g_witnessProc); |
74 | g_randomizer2 = std::make_shared<Randomizer2>(g_witnessProc); | ||
75 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_READY, NULL); | 62 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_READY, NULL); |
76 | } | 63 | } |
77 | break; | 64 | break; |
78 | case ProcStatus::NewGame: // This status will fire only once per new game | 65 | case ProcStatus::NewGame: // This status will fire only once per new game |
79 | SetWindowText(g_seed, L""); | 66 | //SetWindowText(g_seed, L""); |
80 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_READY, NULL); | 67 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_READY, NULL); |
81 | break; | 68 | break; |
82 | } | 69 | } |
83 | break; | 70 | break; |
84 | case RANDOMIZE_READY: | 71 | case RANDOMIZE_READY: |
85 | EnableWindow(g_randomizerStatus, TRUE); | 72 | EnableWindow(g_randomizerStatus, TRUE); |
86 | if (IsDlgButtonChecked(hwnd, CHALLENGE_ONLY)) { | 73 | SetWindowText(g_randomizerStatus, L"Tutorialise"); |
87 | SetWindowText(g_randomizerStatus, L"Randomize Challenge"); | ||
88 | } else { | ||
89 | SetWindowText(g_randomizerStatus, L"Randomize"); | ||
90 | } | ||
91 | break; | 74 | break; |
92 | case RANDOMIZING: | 75 | case RANDOMIZING: |
93 | if (!g_randomizer) break; // E.g. an enter press at the wrong time | 76 | if (!g_randomizer) break; // E.g. an enter press at the wrong time |
94 | EnableWindow(g_randomizerStatus, FALSE); | 77 | EnableWindow(g_randomizerStatus, FALSE); |
95 | 78 | ||
96 | SetRandomSeed(); | ||
97 | std::thread([]{ | 79 | std::thread([]{ |
98 | if (IsDlgButtonChecked(g_hwnd, DISABLE_SNIPES)) { | 80 | SetWindowText(g_randomizerStatus, L"Tutorialising..."); |
99 | MEMORY_CATCH(g_randomizer->PreventSnipes()); | 81 | g_randomizer->Randomize(); |
100 | } | 82 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_DONE, NULL); |
101 | if (IsDlgButtonChecked(g_hwnd, SPEED_UP_AUTOSCROLLERS)) { | ||
102 | MEMORY_CATCH(g_randomizer->AdjustSpeed()); | ||
103 | } | ||
104 | if (IsDlgButtonChecked(g_hwnd, CHALLENGE_ONLY)) { | ||
105 | SetWindowText(g_randomizerStatus, L"Randomizing Challenge..."); | ||
106 | MEMORY_CATCH(g_randomizer->RandomizeChallenge()); | ||
107 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_CHALLENGE_DONE, NULL); | ||
108 | } else { | ||
109 | SetWindowText(g_randomizerStatus, L"Randomizing..."); | ||
110 | g_randomizer->Randomize(); | ||
111 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_DONE, NULL); | ||
112 | } | ||
113 | }).detach(); | 83 | }).detach(); |
114 | break; | 84 | break; |
115 | case RANDOMIZE_DONE: | 85 | case RANDOMIZE_DONE: |
116 | EnableWindow(g_randomizerStatus, FALSE); | 86 | EnableWindow(g_randomizerStatus, FALSE); |
117 | SetWindowText(g_randomizerStatus, L"Randomized!"); | 87 | SetWindowText(g_randomizerStatus, L"Tutorialised!"); |
118 | break; | ||
119 | case RANDOMIZE_CHALLENGE_DONE: | ||
120 | EnableWindow(g_randomizerStatus, FALSE); | ||
121 | SetWindowText(g_randomizerStatus, L"Randomized Challenge!"); | ||
122 | std::thread([]{ | ||
123 | // Allow re-randomization for challenge -- it doesn't break the rest of the game. | ||
124 | std::this_thread::sleep_for(std::chrono::seconds(10)); | ||
125 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_READY, NULL); | ||
126 | }).detach(); | ||
127 | break; | ||
128 | case CHALLENGE_ONLY: | ||
129 | CheckDlgButton(hwnd, CHALLENGE_ONLY, !IsDlgButtonChecked(hwnd, CHALLENGE_ONLY)); | ||
130 | if (IsWindowEnabled(g_randomizerStatus)) { | ||
131 | PostMessage(g_hwnd, WM_COMMAND, RANDOMIZE_READY, NULL); | ||
132 | } | ||
133 | break; | ||
134 | case DISABLE_SNIPES: | ||
135 | CheckDlgButton(hwnd, DISABLE_SNIPES, !IsDlgButtonChecked(hwnd, DISABLE_SNIPES)); | ||
136 | break; | ||
137 | case SPEED_UP_AUTOSCROLLERS: | ||
138 | CheckDlgButton(hwnd, SPEED_UP_AUTOSCROLLERS, !IsDlgButtonChecked(hwnd, SPEED_UP_AUTOSCROLLERS)); | ||
139 | break; | ||
140 | case TMP1: | ||
141 | { | ||
142 | std::wstring text(128, L'\0'); | ||
143 | int length = GetWindowText(g_panelId, text.data(), static_cast<int>(text.size())); | ||
144 | text.resize(length); | ||
145 | std::wstringstream s; | ||
146 | int panelId; | ||
147 | s << text; | ||
148 | s >> std::hex >> panelId; | ||
149 | g_puzzle = PuzzleSerializer(g_witnessProc).ReadPuzzle(panelId); | ||
150 | } | ||
151 | break; | ||
152 | case TMP2: | ||
153 | { | ||
154 | std::wstring text(128, L'\0'); | ||
155 | int length = GetWindowText(g_panelId, text.data(), static_cast<int>(text.size())); | ||
156 | text.resize(length); | ||
157 | std::wstringstream s; | ||
158 | int panelId; | ||
159 | s << text; | ||
160 | s >> std::hex >> panelId; | ||
161 | PuzzleSerializer(g_witnessProc).WritePuzzle(g_puzzle, panelId); | ||
162 | } | ||
163 | break; | 88 | break; |
164 | case TMP3: | ||
165 | { | ||
166 | for (auto [key, value] : PANELS) { | ||
167 | std::stringstream out; | ||
168 | std::string name(value); | ||
169 | out << " {'id': 0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(5) << key << ", 'area':'"; | ||
170 | int k; | ||
171 | for (k=0; name[k] != ' '; k++) out << name[k]; | ||
172 | if (name[k+2] == ' ') { | ||
173 | out << name[k] << name[k+1]; | ||
174 | k += 2; | ||
175 | } | ||
176 | out << "', 'name':'"; | ||
177 | k++; | ||
178 | for (k; k < name.size(); k++) out << name[k]; | ||
179 | out << "', 'data':'"; | ||
180 | auto puzzle = PuzzleSerializer(g_witnessProc).ReadPuzzle(key); | ||
181 | out << puzzle.Serialize(); | ||
182 | out << "'},\r\n"; | ||
183 | DebugPrint(out.str()); | ||
184 | } | ||
185 | } | ||
186 | 89 | ||
187 | // Solver::Solve(g_puzzle); | ||
188 | break; | ||
189 | case TMP4: | ||
190 | SetRandomSeed(); | ||
191 | g_randomizer2->Randomize(); | ||
192 | case TMP5: | ||
193 | { | ||
194 | std::wstring text; | ||
195 | for (int i=0; i<10; i++) { | ||
196 | Random::SetSeed(i); | ||
197 | int rng = Random::RandInt(0, 999999); | ||
198 | text += std::to_wstring(rng) + L"\n"; | ||
199 | } | ||
200 | SetWindowText(g_rngDebug, text.c_str()); | ||
201 | } | ||
202 | } | 90 | } |
203 | } | 91 | } |
204 | return DefWindowProc(hwnd, message, wParam, lParam); | 92 | return DefWindowProc(hwnd, message, wParam, lParam); |
205 | } | 93 | } |
206 | 94 | ||
207 | void SetRandomSeed() { | ||
208 | std::wstring text(128, L'\0'); | ||
209 | int length = GetWindowText(g_seed, text.data(), static_cast<int>(text.size())); | ||
210 | if (length > 0) { // Set seed | ||
211 | text.resize(length); | ||
212 | Random::SetSeed(_wtoi(text.c_str())); | ||
213 | } else { // Random seed | ||
214 | int seed = Random::RandInt(0, 999999); | ||
215 | |||
216 | text = std::to_wstring(seed); | ||
217 | SetWindowText(g_seed, text.c_str()); | ||
218 | CHARRANGE range = {0, static_cast<long>(text.length())}; | ||
219 | PostMessage(g_seed, EM_EXSETSEL, NULL, (LPARAM)&range); | ||
220 | SetFocus(g_seed); | ||
221 | |||
222 | Random::SetSeed(seed); | ||
223 | } | ||
224 | } | ||
225 | |||
226 | HWND CreateLabel(int x, int y, int width, LPCWSTR text) { | 95 | HWND CreateLabel(int x, int y, int width, LPCWSTR text) { |
227 | return CreateWindow(L"STATIC", text, | 96 | return CreateWindow(L"STATIC", text, |
228 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | SS_LEFT, | 97 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | SS_LEFT, |
229 | x, y, width, 16, g_hwnd, NULL, g_hInstance, NULL); | 98 | x, y, width, 16, g_hwnd, NULL, g_hInstance, NULL); |
230 | } | 99 | } |
231 | 100 | ||
101 | HWND CreateMultiLabel(int x, int y, int width, int height, LPCWSTR text) { | ||
102 | return CreateWindow(L"STATIC", text, | ||
103 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | SS_LEFT, | ||
104 | x, y, width, height, g_hwnd, NULL, g_hInstance, NULL); | ||
105 | } | ||
106 | |||
232 | HWND CreateText(int x, int y, int width, LPCWSTR defaultText = L"") { | 107 | HWND CreateText(int x, int y, int width, LPCWSTR defaultText = L"") { |
233 | return CreateWindow(MSFTEDIT_CLASS, defaultText, | 108 | return CreateWindow(MSFTEDIT_CLASS, defaultText, |
234 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_BORDER, | 109 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_BORDER, |
@@ -271,32 +146,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance | |||
271 | RECT rect; | 146 | RECT rect; |
272 | GetClientRect(GetDesktopWindow(), &rect); | 147 | GetClientRect(GetDesktopWindow(), &rect); |
273 | g_hwnd = CreateWindow(WINDOW_CLASS, PRODUCT_NAME, WS_OVERLAPPEDWINDOW, | 148 | g_hwnd = CreateWindow(WINDOW_CLASS, PRODUCT_NAME, WS_OVERLAPPEDWINDOW, |
274 | rect.right - 550, 200, 500, 500, nullptr, nullptr, hInstance, nullptr); | 149 | rect.right - 550, 200, 500, 180, nullptr, nullptr, hInstance, nullptr); |
275 | 150 | ||
276 | CreateLabel(390, 15, 90, L"Version: " VERSION_STR); | 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."); |
277 | g_seed = CreateText(10, 10, 100); | 152 | CreateLabel(390, 110, 90, L"Version: " VERSION_STR); |
278 | PostMessage(g_seed, EM_SETEVENTMASK, 0, ENM_KEYEVENTS); | 153 | //g_seed = CreateText(10, 10, 100); |
279 | g_randomizerStatus = CreateButton(120, 10, 180, L"Randomize", RANDOMIZING); | 154 | //PostMessage(g_seed, EM_SETEVENTMASK, 0, ENM_KEYEVENTS); |
155 | g_randomizerStatus = CreateButton(120, 105, 180, L"Tutorialise", RANDOMIZING); | ||
280 | EnableWindow(g_randomizerStatus, FALSE); | 156 | EnableWindow(g_randomizerStatus, FALSE); |
281 | CreateCheckbox(10, 300, CHALLENGE_ONLY); | ||
282 | CreateLabel(30, 300, 200, L"Randomize the challenge only"); | ||
283 | CreateCheckbox(10, 320, DISABLE_SNIPES); | ||
284 | CheckDlgButton(g_hwnd, DISABLE_SNIPES, TRUE); | ||
285 | CreateLabel(30, 320, 240, L"Disable Swamp and Shadows snipes"); | ||
286 | CreateCheckbox(10, 340, SPEED_UP_AUTOSCROLLERS); | ||
287 | CreateLabel(30, 340, 205, L"Speed up various autoscrollers"); | ||
288 | 157 | ||
289 | // CreateButton(200, 50, 200, L"Test RNG", TMP5); | ||
290 | // g_rngDebug = CreateWindow(L"STATIC", L"", | ||
291 | // WS_TABSTOP | WS_VISIBLE | WS_CHILD | SS_LEFT, | ||
292 | // 200, 80, 200, 200, g_hwnd, NULL, g_hInstance, NULL); | ||
293 | #ifndef NDEBUG | ||
294 | g_panelId = CreateText(200, 100, 100, L"59"); | ||
295 | CreateButton(200, 130, 100, L"Read", TMP1); | ||
296 | CreateButton(200, 160, 100, L"Write", TMP2); | ||
297 | CreateButton(200, 190, 100, L"Solve", TMP3); | ||
298 | CreateButton(200, 220, 100, L"Randomize2", TMP4); | ||
299 | #endif | ||
300 | 158 | ||
301 | g_witnessProc->StartHeartbeat(g_hwnd, HEARTBEAT); | 159 | g_witnessProc->StartHeartbeat(g_hwnd, HEARTBEAT); |
302 | 160 | ||
diff --git a/App/Version.h b/App/Version.h index 1541697..204782f 100644 --- a/App/Version.h +++ b/App/Version.h | |||
@@ -3,12 +3,12 @@ | |||
3 | #define TO_STRING2(s) L#s | 3 | #define TO_STRING2(s) L#s |
4 | #define TO_STRING(s) TO_STRING2(s) | 4 | #define TO_STRING(s) TO_STRING2(s) |
5 | 5 | ||
6 | #define MAJOR 6 | 6 | #define MAJOR 0 |
7 | #define MINOR 0 | 7 | #define MINOR 1 |
8 | #define PATCH 0 | 8 | #define PATCH 0 |
9 | 9 | ||
10 | #define VERSION_STR TO_STRING(MAJOR) L"." TO_STRING(MINOR) L"." TO_STRING(PATCH) | 10 | #define VERSION_STR TO_STRING(MAJOR) L"." TO_STRING(MINOR) L"." TO_STRING(PATCH) |
11 | #define VERSION MAJOR, MINOR, PATCH | 11 | #define VERSION MAJOR, MINOR, PATCH |
12 | 12 | ||
13 | #define PRODUCT_NAME L"Witness Randomizer" | 13 | #define PRODUCT_NAME L"Witness Tutorialiser Mod" |
14 | #define WINDOW_CLASS L"WitnessRandomizer" | 14 | #define WINDOW_CLASS L"WitnessTutorialiser" |
diff --git a/Source/Randomizer2.cpp b/Source/Randomizer2.cpp deleted file mode 100644 index 421ce69..0000000 --- a/Source/Randomizer2.cpp +++ /dev/null | |||
@@ -1,644 +0,0 @@ | |||
1 | #include "pch.h" | ||
2 | #include "Randomizer2.h" | ||
3 | #include "PuzzleSerializer.h" | ||
4 | #include "Randomizer2Core.h" | ||
5 | #include "Random.h" | ||
6 | #include "Solver.h" | ||
7 | #include "Windows.h" | ||
8 | |||
9 | Randomizer2::Randomizer2(const PuzzleSerializer& serializer) : _serializer(serializer) { | ||
10 | } | ||
11 | |||
12 | void Randomizer2::Randomize() { | ||
13 | // RandomizeTutorial(); | ||
14 | // RandomizeGlassFactory(); | ||
15 | RandomizeSymmetryIsland(); | ||
16 | // RandomizeKeep(); | ||
17 | } | ||
18 | |||
19 | void Randomizer2::RandomizeTutorial() { | ||
20 | { // Far center | ||
21 | Puzzle p; | ||
22 | p.NewGrid(4, 4); | ||
23 | p.grid[0][8].start = true; | ||
24 | p.grid[8][0].end = Cell::Dir::UP; | ||
25 | |||
26 | for (Pos pos : Randomizer2Core::CutEdges(p, 14)) { | ||
27 | p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
28 | } | ||
29 | _serializer.WritePuzzle(p, 0x293); | ||
30 | } | ||
31 | |||
32 | { // Center left | ||
33 | Puzzle p; | ||
34 | p.NewGrid(6, 6); | ||
35 | |||
36 | int x = Random::RandInt(0, (p.width-1)/2)*2; | ||
37 | int y = Random::RandInt(0, (p.height-1)/2)*2; | ||
38 | int rng = Random::RandInt(1, 4); | ||
39 | if (rng == 1) p.grid[x][0].end = Cell::Dir::UP; | ||
40 | else if (rng == 2) p.grid[x][p.height-1].end = Cell::Dir::DOWN; | ||
41 | else if (rng == 3) p.grid[0][y].end = Cell::Dir::LEFT; | ||
42 | else if (rng == 4) p.grid[p.width-1][y].end = Cell::Dir::RIGHT; | ||
43 | |||
44 | // [4/6/8][4/6/8] | ||
45 | p.grid[Random::RandInt(0, 2)*2 + 4][Random::RandInt(0, 2)*2 + 4].start = true; | ||
46 | |||
47 | for (Pos pos : Randomizer2Core::CutEdges(p, 35)) { | ||
48 | p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
49 | } | ||
50 | |||
51 | _serializer.WritePuzzle(p, 0x295); | ||
52 | } | ||
53 | |||
54 | { // Far left | ||
55 | Puzzle p; | ||
56 | p.NewGrid(10, 10); | ||
57 | |||
58 | p.grid[0][20].start = true; | ||
59 | p.grid[20][0].end = Cell::Dir::RIGHT; | ||
60 | |||
61 | for (Pos pos : Randomizer2Core::CutEdges(p, 96)) { | ||
62 | p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
63 | } | ||
64 | _serializer.WritePuzzle(p, 0x2C2); | ||
65 | } | ||
66 | |||
67 | { // Back left | ||
68 | Puzzle p; | ||
69 | p.NewGrid(6, 6); | ||
70 | |||
71 | p.grid[0][12].start = true; | ||
72 | p.grid[12][0].end = Cell::Dir::RIGHT; | ||
73 | p.grid[12][12].end = Cell::Dir::RIGHT; | ||
74 | |||
75 | for (Pos pos : Randomizer2Core::CutEdges(p, 27)) { | ||
76 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
77 | } | ||
78 | _serializer.WritePuzzle(p, 0xA3B5); | ||
79 | } | ||
80 | |||
81 | { // Back right | ||
82 | Puzzle p; | ||
83 | p.NewGrid(6, 6); | ||
84 | |||
85 | p.grid[0][12].start = true; | ||
86 | p.grid[12][12].start = true; | ||
87 | p.grid[6][0].end = Cell::Dir::UP; | ||
88 | |||
89 | // @Cleanup | ||
90 | std::vector<Pos> cuts; | ||
91 | bool toTheRight; | ||
92 | // Start by generating a cut line, to ensure one of the two startpoints is inaccessible | ||
93 | int x, y; | ||
94 | switch (Random::RandInt(1, 4)) { | ||
95 | case 1: | ||
96 | x = 1; y = 1; | ||
97 | toTheRight = true; | ||
98 | cuts.emplace_back(0, 1); | ||
99 | break; | ||
100 | case 2: | ||
101 | x = 1; y = 1; | ||
102 | toTheRight = true; | ||
103 | cuts.emplace_back(1, 0); | ||
104 | break; | ||
105 | case 3: | ||
106 | x = 11; y = 1; | ||
107 | toTheRight = false; | ||
108 | cuts.emplace_back(12, 1); | ||
109 | break; | ||
110 | case 4: | ||
111 | x = 11; y = 1; | ||
112 | toTheRight = false; | ||
113 | cuts.emplace_back(11, 0); | ||
114 | break; | ||
115 | } | ||
116 | while (y < p.height) { // The final cut will push y below the bottom of the puzzle, which means we're done. | ||
117 | switch (Random::RandInt(1, 4)) { | ||
118 | case 1: // Go right | ||
119 | if (x < p.width-2) { | ||
120 | cuts.emplace_back(x+1, y); | ||
121 | x += 2; | ||
122 | } | ||
123 | break; | ||
124 | case 2: // Go left | ||
125 | if (x > 1) { | ||
126 | cuts.emplace_back(x-1, y); | ||
127 | x -= 2; | ||
128 | } | ||
129 | break; | ||
130 | case 3: | ||
131 | case 4: // Go down (biased x2) | ||
132 | cuts.emplace_back(x, y+1); | ||
133 | y += 2; | ||
134 | break; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | for (Pos pos : cuts) { | ||
139 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
140 | } | ||
141 | |||
142 | for (Pos pos : Randomizer2Core::CutEdges(p, 30 - cuts.size())) { | ||
143 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
144 | } | ||
145 | _serializer.WritePuzzle(p, 0xA3B2); | ||
146 | } | ||
147 | } | ||
148 | |||
149 | void Randomizer2::RandomizeGlassFactory() { | ||
150 | { // Back wall 1 | ||
151 | Puzzle p; | ||
152 | p.NewGrid(3, 3); | ||
153 | p.symmetry = Puzzle::Symmetry::X; | ||
154 | p.grid[0][6].start = true; | ||
155 | p.grid[6][6].start = true; | ||
156 | p.grid[2][0].end = Cell::Dir::UP; | ||
157 | p.grid[4][0].end = Cell::Dir::UP; | ||
158 | |||
159 | std::vector<Pos> cutEdges = Randomizer2Core::CutSymmetricalEdgePairs(p, 2); | ||
160 | for (Pos pos : cutEdges) { | ||
161 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
162 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
163 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
164 | } | ||
165 | _serializer.WritePuzzle(p, 0x86); | ||
166 | } | ||
167 | { // Back wall 2 | ||
168 | Puzzle p; | ||
169 | p.NewGrid(4, 4); | ||
170 | p.symmetry = Puzzle::Symmetry::X; | ||
171 | p.grid[0][8].start = true; | ||
172 | p.grid[8][8].start = true; | ||
173 | p.grid[2][0].end = Cell::Dir::UP; | ||
174 | p.grid[6][0].end = Cell::Dir::UP; | ||
175 | std::vector<Pos> cutEdges = Randomizer2Core::CutSymmetricalEdgePairs(p, 4); | ||
176 | for (int i=0; i<cutEdges.size(); i++) { | ||
177 | Pos pos = cutEdges[i]; | ||
178 | if (i%2 == 0) { | ||
179 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
180 | } else { | ||
181 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
182 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | _serializer.WritePuzzle(p, 0x87); | ||
187 | } | ||
188 | { // Back wall 3 | ||
189 | Puzzle p; | ||
190 | p.NewGrid(5, 6); | ||
191 | p.symmetry = Puzzle::Symmetry::X; | ||
192 | p.grid[2][10].start = true; | ||
193 | p.grid[8][10].start = true; | ||
194 | p.grid[4][0].end = Cell::Dir::UP; | ||
195 | p.grid[6][0].end = Cell::Dir::UP; | ||
196 | std::vector<Pos> cutEdges = Randomizer2Core::CutSymmetricalEdgePairs(p, 10); | ||
197 | for (int i=0; i<cutEdges.size(); i++) { | ||
198 | Pos pos = cutEdges[i]; | ||
199 | if (i%2 == 0) { | ||
200 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
201 | } else { | ||
202 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
203 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | _serializer.WritePuzzle(p, 0x59); | ||
208 | } | ||
209 | { // Back wall 4 | ||
210 | Puzzle p; | ||
211 | p.NewGrid(5, 8); | ||
212 | p.symmetry = Puzzle::Symmetry::X; | ||
213 | p.grid[2][16].start = true; | ||
214 | p.grid[8][16].start = true; | ||
215 | p.grid[4][0].end = Cell::Dir::UP; | ||
216 | p.grid[6][0].end = Cell::Dir::UP; | ||
217 | std::vector<Pos> cutEdges = Randomizer2Core::CutSymmetricalEdgePairs(p, 15); | ||
218 | for (int i=0; i<cutEdges.size(); i++) { | ||
219 | Pos pos = cutEdges[i]; | ||
220 | if (i%2 == 0) { | ||
221 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
222 | } else { | ||
223 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
224 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
225 | } | ||
226 | } | ||
227 | |||
228 | _serializer.WritePuzzle(p, 0x62); | ||
229 | } | ||
230 | // TODO: Positioning is off, slightly -- which means you can start from the bottom left, if you peek around. | ||
231 | { // Back wall 5 | ||
232 | Puzzle p; | ||
233 | p.NewGrid(11, 8); | ||
234 | p.symmetry = Puzzle::Symmetry::X; | ||
235 | p.grid[0][16].start = true; | ||
236 | p.grid[10][16].start = true; | ||
237 | p.grid[12][16].start = true; | ||
238 | p.grid[22][16].start = true; | ||
239 | p.grid[2][0].end = Cell::Dir::UP; | ||
240 | p.grid[8][0].end = Cell::Dir::UP; | ||
241 | p.grid[14][0].end = Cell::Dir::UP; | ||
242 | p.grid[20][0].end = Cell::Dir::UP; | ||
243 | |||
244 | Puzzle q; | ||
245 | q.NewGrid(5, 8); | ||
246 | q.symmetry = Puzzle::Symmetry::X; | ||
247 | |||
248 | for (Pos pos : Randomizer2Core::CutSymmetricalEdgePairs(q, 16)) { | ||
249 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
250 | } | ||
251 | for (Pos pos : Randomizer2Core::CutSymmetricalEdgePairs(q, 16)) { | ||
252 | p.grid[pos.x + 12][pos.y].gap = Cell::Gap::BREAK; | ||
253 | } | ||
254 | |||
255 | for (int y=0; y<p.height; y+=2) { | ||
256 | p.grid[5][y].gap = Cell::Gap::BREAK; | ||
257 | } | ||
258 | |||
259 | _serializer.WritePuzzle(p, 0x5C); | ||
260 | } | ||
261 | |||
262 | { // Rotational 1 | ||
263 | Puzzle p; | ||
264 | p.NewGrid(3, 3); | ||
265 | p.symmetry = Puzzle::Symmetry::XY; | ||
266 | p.grid[6][0].start = true; | ||
267 | p.grid[0][6].start = true; | ||
268 | p.grid[4][0].end = Cell::Dir::UP; | ||
269 | p.grid[2][6].end = Cell::Dir::DOWN; | ||
270 | |||
271 | p.grid[5][0].gap = Cell::Gap::BREAK; | ||
272 | p.grid[1][6].gap = Cell::Gap::BREAK; | ||
273 | |||
274 | for (Pos pos : Randomizer2Core::CutSymmetricalEdgePairs(p, 1)) { | ||
275 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
276 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
277 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
278 | } | ||
279 | _serializer.WritePuzzle(p, 0x8D); | ||
280 | } | ||
281 | { // Rotational 2 | ||
282 | Puzzle p; | ||
283 | p.NewGrid(3, 3); | ||
284 | p.symmetry = Puzzle::Symmetry::XY; | ||
285 | p.grid[6][0].start = true; | ||
286 | p.grid[0][6].start = true; | ||
287 | p.grid[4][0].end = Cell::Dir::UP; | ||
288 | p.grid[2][6].end = Cell::Dir::DOWN; | ||
289 | |||
290 | p.grid[5][0].gap = Cell::Gap::BREAK; | ||
291 | p.grid[1][6].gap = Cell::Gap::BREAK; | ||
292 | |||
293 | std::vector<Pos> cutEdges = Randomizer2Core::CutSymmetricalEdgePairs(p, 3); | ||
294 | for (int i=0; i<cutEdges.size(); i++) { | ||
295 | Pos pos = cutEdges[i]; | ||
296 | if (i%2 == 0) { | ||
297 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
298 | } else { | ||
299 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
300 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
301 | } | ||
302 | } | ||
303 | |||
304 | p.grid[1][6].gap = Cell::Gap::NONE; | ||
305 | |||
306 | _serializer.WritePuzzle(p, 0x81); | ||
307 | } | ||
308 | { // Rotational 3 | ||
309 | Puzzle p; | ||
310 | p.NewGrid(4, 4); | ||
311 | p.symmetry = Puzzle::Symmetry::XY; | ||
312 | p.grid[8][0].start = true; | ||
313 | p.grid[0][8].start = true; | ||
314 | p.grid[0][0].end = Cell::Dir::LEFT; | ||
315 | p.grid[8][8].end = Cell::Dir::RIGHT; | ||
316 | |||
317 | std::vector<Pos> cutEdges = Randomizer2Core::CutSymmetricalEdgePairs(p, 7); | ||
318 | for (int i=0; i<cutEdges.size(); i++) { | ||
319 | Pos pos = cutEdges[i]; | ||
320 | if (i%2 == 0) { | ||
321 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
322 | } else { | ||
323 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
324 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
325 | } | ||
326 | } | ||
327 | |||
328 | _serializer.WritePuzzle(p, 0x83); | ||
329 | } | ||
330 | { // Melting | ||
331 | Puzzle p; | ||
332 | p.NewGrid(6, 6); | ||
333 | p.symmetry = Puzzle::Symmetry::XY; | ||
334 | p.grid[12][0].start = true; | ||
335 | p.grid[0][12].start = true; | ||
336 | p.grid[0][0].end = Cell::Dir::LEFT; | ||
337 | p.grid[12][12].end = Cell::Dir::RIGHT; | ||
338 | Puzzle q = p; | ||
339 | |||
340 | std::vector<Pos> cutEdges = Randomizer2Core::CutSymmetricalEdgePairs(p, 15); | ||
341 | for (int i=0; i<cutEdges.size(); i++) { | ||
342 | Pos pos = cutEdges[i]; | ||
343 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
344 | |||
345 | if (i%2 == 0) { | ||
346 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
347 | } else { | ||
348 | p.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
349 | } | ||
350 | |||
351 | if (pos.x < sym.x) { | ||
352 | q.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
353 | } else { | ||
354 | q.grid[sym.x][sym.y].gap = Cell::Gap::BREAK; | ||
355 | } | ||
356 | } | ||
357 | |||
358 | _serializer.WritePuzzle(p, 0x84); // Melting 1 | ||
359 | _serializer.WritePuzzle(q, 0x82); // Melting 2 | ||
360 | _serializer.WritePuzzle(q, 0x343A); // Melting 3 | ||
361 | } | ||
362 | } | ||
363 | |||
364 | void Randomizer2::RandomizeSymmetryIsland() { | ||
365 | { // Entry door | ||
366 | Puzzle p; | ||
367 | p.NewGrid(4, 3); | ||
368 | p.grid[0][6].start = true; | ||
369 | p.grid[8][0].end = Cell::Dir::RIGHT; | ||
370 | p.grid[4][3].gap = Cell::Gap::FULL; | ||
371 | |||
372 | std::vector<Pos> corners; | ||
373 | std::vector<Pos> cells; | ||
374 | std::vector<Pos> edges; | ||
375 | for (int x=0; x<p.width; x++) { | ||
376 | for (int y=0; y<p.height; y++) { | ||
377 | if (x%2 == 0 && y%2 == 0) corners.emplace_back(Pos{x, y}); | ||
378 | else if (x%2 == 1 && y%2 == 1) cells.emplace_back(Pos{x, y}); | ||
379 | else edges.emplace_back(Pos{x, y}); | ||
380 | } | ||
381 | } | ||
382 | |||
383 | for (int j=0;; j++) { | ||
384 | std::vector<Pos> dots = Random::SelectFromSet(edges, 4); | ||
385 | for (Pos pos : dots) p.grid[pos.x][pos.y].dot = Cell::Dot::BLACK; | ||
386 | |||
387 | auto solutions = Solver::Solve(p); | ||
388 | if (solutions.size() > 0 && solutions.size() < 10) break; | ||
389 | |||
390 | for (Pos pos : dots) p.grid[pos.x][pos.y].dot = Cell::Dot::NONE; | ||
391 | } | ||
392 | |||
393 | _serializer.WritePuzzle(p, 0xB0); | ||
394 | } | ||
395 | |||
396 | { // Dots 1 | ||
397 | Puzzle p; | ||
398 | p.NewGrid(3, 3); | ||
399 | p.symmetry = Puzzle::Symmetry::Y; | ||
400 | p.grid[0][2].start = true; | ||
401 | p.grid[0][4].start = true; | ||
402 | p.grid[6][2].end = Cell::Dir::RIGHT; | ||
403 | p.grid[6][4].end = Cell::Dir::RIGHT; | ||
404 | |||
405 | std::vector<Pos> corners; | ||
406 | std::vector<Pos> cells; | ||
407 | std::vector<Pos> edges; | ||
408 | for (int x=0; x<p.width; x++) { | ||
409 | for (int y=0; y<p.height/2; y++) { | ||
410 | if (x%2 == 0 && y%2 == 0) corners.emplace_back(Pos{x, y}); | ||
411 | else if (x%2 == 1 && y%2 == 1) cells.emplace_back(Pos{x, y}); | ||
412 | else edges.emplace_back(Pos{x, y}); | ||
413 | } | ||
414 | } | ||
415 | edges.insert(edges.end(), corners.begin(), corners.end()); | ||
416 | |||
417 | std::vector<Pos> dots; | ||
418 | for (int j=0;; j++) { | ||
419 | dots = Random::SelectFromSet(edges, 3); | ||
420 | for (Pos pos : dots) p.grid[pos.x][pos.y].dot = Cell::Dot::BLACK; | ||
421 | |||
422 | auto solutions = Solver::Solve(p); | ||
423 | if (solutions.size() == 2) break; | ||
424 | |||
425 | for (Pos pos : dots) p.grid[pos.x][pos.y].dot = Cell::Dot::NONE; | ||
426 | } | ||
427 | |||
428 | for (Pos pos : dots) { | ||
429 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
430 | p.grid[sym.x][sym.y].dot = Cell::Dot::BLACK; | ||
431 | } | ||
432 | |||
433 | _serializer.WritePuzzle(p, 0x22); | ||
434 | } | ||
435 | { // Dots 2 | ||
436 | Puzzle p; | ||
437 | p.NewGrid(3, 3); | ||
438 | p.symmetry = Puzzle::Symmetry::Y; | ||
439 | p.grid[0][2].start = true; | ||
440 | p.grid[0][4].start = true; | ||
441 | p.grid[6][2].end = Cell::Dir::RIGHT; | ||
442 | p.grid[6][4].end = Cell::Dir::RIGHT; | ||
443 | |||
444 | std::vector<Pos> corners; | ||
445 | std::vector<Pos> cells; | ||
446 | std::vector<Pos> edges; | ||
447 | for (int x=0; x<p.width; x++) { | ||
448 | for (int y=0; y<p.height/2; y++) { | ||
449 | if (x%2 == 0 && y%2 == 0) corners.emplace_back(Pos{x, y}); | ||
450 | else if (x%2 == 1 && y%2 == 1) cells.emplace_back(Pos{x, y}); | ||
451 | else edges.emplace_back(Pos{x, y}); | ||
452 | } | ||
453 | } | ||
454 | edges.insert(edges.end(), corners.begin(), corners.end()); | ||
455 | |||
456 | std::vector<Pos> dots; | ||
457 | for (int j=0;; j++) { | ||
458 | dots = Random::SelectFromSet(edges, 3); | ||
459 | for (Pos pos : dots) p.grid[pos.x][pos.y].dot = Cell::Dot::BLACK; | ||
460 | |||
461 | auto solutions = Solver::Solve(p); | ||
462 | if (solutions.size() == 2) break; | ||
463 | |||
464 | for (Pos pos : dots) p.grid[pos.x][pos.y].dot = Cell::Dot::NONE; | ||
465 | } | ||
466 | |||
467 | Pos pos = dots[1]; | ||
468 | Pos sym = p.GetSymmetricalPos(pos.x, pos.y); | ||
469 | p.grid[pos.x][pos.y].dot = Cell::Dot::NONE; | ||
470 | p.grid[sym.x][sym.y].dot = Cell::Dot::BLACK; | ||
471 | |||
472 | _serializer.WritePuzzle(p, 0x23); | ||
473 | } | ||
474 | } | ||
475 | |||
476 | void Randomizer2::RandomizeKeep() { | ||
477 | { // Hedges 1 | ||
478 | Puzzle p; | ||
479 | p.NewGrid(4, 4); | ||
480 | |||
481 | p.grid[2][1].gap = Cell::Gap::FULL; | ||
482 | p.grid[4][1].gap = Cell::Gap::FULL; | ||
483 | p.grid[6][1].gap = Cell::Gap::FULL; | ||
484 | p.grid[3][2].gap = Cell::Gap::FULL; | ||
485 | p.grid[5][2].gap = Cell::Gap::FULL; | ||
486 | p.grid[8][3].gap = Cell::Gap::FULL; | ||
487 | p.grid[2][5].gap = Cell::Gap::FULL; | ||
488 | p.grid[6][5].gap = Cell::Gap::FULL; | ||
489 | p.grid[7][6].gap = Cell::Gap::FULL; | ||
490 | p.grid[2][7].gap = Cell::Gap::FULL; | ||
491 | p.grid[4][7].gap = Cell::Gap::FULL; | ||
492 | |||
493 | p.grid[4][8].start = true; | ||
494 | p.grid[6][0].end = Cell::Dir::UP; | ||
495 | |||
496 | std::vector<Pos> cutEdges = Randomizer2Core::CutInsideEdges(p, 5); | ||
497 | Puzzle copy = p; | ||
498 | std::vector<int> gates = {0x00344, 0x00488, 0x00489, 0x00495, 0x00496}; | ||
499 | for (int i=0; i<cutEdges.size(); i++) { | ||
500 | Pos pos = cutEdges[i]; | ||
501 | copy.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
502 | SetGate(gates[i], pos.x, pos.y); | ||
503 | } | ||
504 | auto solution = GetUniqueSolution(copy); | ||
505 | p.sequence = solution.sequence; | ||
506 | _serializer.WritePuzzle(p, 0x139); | ||
507 | } | ||
508 | |||
509 | { // Hedges 2 | ||
510 | Puzzle p; | ||
511 | p.NewGrid(4, 4); | ||
512 | |||
513 | p.grid[2][1].gap = Cell::Gap::FULL; | ||
514 | p.grid[1][2].gap = Cell::Gap::FULL; | ||
515 | p.grid[5][2].gap = Cell::Gap::FULL; | ||
516 | p.grid[7][4].gap = Cell::Gap::FULL; | ||
517 | p.grid[4][5].gap = Cell::Gap::FULL; | ||
518 | p.grid[6][5].gap = Cell::Gap::FULL; | ||
519 | p.grid[1][6].gap = Cell::Gap::FULL; | ||
520 | p.grid[2][7].gap = Cell::Gap::FULL; | ||
521 | p.grid[5][8].gap = Cell::Gap::FULL; | ||
522 | |||
523 | p.grid[0][8].start = true; | ||
524 | p.grid[8][0].end = Cell::Dir::RIGHT; | ||
525 | |||
526 | std::vector<Pos> cutEdges = Randomizer2Core::CutInsideEdges(p, 7); | ||
527 | for (Pos pos : cutEdges) { | ||
528 | p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
529 | } | ||
530 | auto solution = GetUniqueSolution(p); | ||
531 | |||
532 | Puzzle q; | ||
533 | q.NewGrid(4, 4); | ||
534 | q.grid[0][8].start = true; | ||
535 | q.grid[8][0].end = Cell::Dir::RIGHT; | ||
536 | q.sequence = solution.sequence; | ||
537 | for (Pos pos : cutEdges) { | ||
538 | q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
539 | } | ||
540 | // Cut to 6 of 9 additional edges | ||
541 | for (Pos pos : Randomizer2Core::CutInsideEdges(q, 6)) { | ||
542 | q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
543 | } | ||
544 | _serializer.WritePuzzle(q, 0x19DC); | ||
545 | } | ||
546 | |||
547 | { // Hedges 3 [WIP] | ||
548 | Puzzle p; | ||
549 | p.NewGrid(4, 4); | ||
550 | |||
551 | p.grid[2][1].gap = Cell::Gap::FULL; | ||
552 | p.grid[5][2].gap = Cell::Gap::FULL; | ||
553 | p.grid[7][2].gap = Cell::Gap::FULL; | ||
554 | p.grid[4][3].gap = Cell::Gap::FULL; | ||
555 | p.grid[1][4].gap = Cell::Gap::FULL; | ||
556 | p.grid[6][5].gap = Cell::Gap::FULL; | ||
557 | p.grid[1][6].gap = Cell::Gap::FULL; | ||
558 | p.grid[3][6].gap = Cell::Gap::FULL; | ||
559 | p.grid[6][7].gap = Cell::Gap::FULL; | ||
560 | |||
561 | p.grid[0][8].start = true; | ||
562 | p.grid[8][2].end = Cell::Dir::RIGHT; | ||
563 | |||
564 | std::vector<Pos> cutEdges = Randomizer2Core::CutInsideEdges(p, 7); | ||
565 | for (Pos pos : cutEdges) { | ||
566 | p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; | ||
567 | } | ||
568 | |||
569 | std::vector<int> pebbleMarkers = {0x034a9, 0x034b1, 0x034be, 0x034c4}; | ||
570 | |||
571 | // _serializer.WritePuzzle(p, 0x19E7); | ||
572 | } | ||
573 | |||
574 | { // Hedges 4 | ||
575 | Puzzle p; | ||
576 | p.NewGrid(4, 4); | ||
577 | |||
578 | p.grid[3][0].gap = Cell::Gap::FULL; | ||
579 | p.grid[4][1].gap = Cell::Gap::FULL; | ||
580 | p.grid[8][1].gap = Cell::Gap::FULL; | ||
581 | p.grid[1][2].gap = Cell::Gap::FULL; | ||
582 | p.grid[4][3].gap = Cell::Gap::FULL; | ||
583 | p.grid[8][3].gap = Cell::Gap::FULL; | ||
584 | p.grid[1][4].gap = Cell::Gap::FULL; | ||
585 | p.grid[5][4].gap = Cell::Gap::FULL; | ||
586 | p.grid[2][5].gap = Cell::Gap::FULL; | ||
587 | p.grid[6][5].gap = Cell::Gap::FULL; | ||
588 | p.grid[3][6].gap = Cell::Gap::FULL; | ||
589 | p.grid[0][7].gap = Cell::Gap::FULL; | ||
590 | p.grid[8][7].gap = Cell::Gap::FULL; | ||
591 | p.grid[5][8].gap = Cell::Gap::FULL; | ||
592 | |||
593 | p.grid[0][8].start = true; | ||
594 | p.grid[4][0].end = Cell::Dir::UP; | ||
595 | |||
596 | std::vector<Pos> cutEdges = Randomizer2Core::CutInsideEdges(p, 2); | ||
597 | for (Pos pos : cutEdges) { | ||
598 | p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
599 | } | ||
600 | auto solution = GetUniqueSolution(p); | ||
601 | |||
602 | Puzzle q; | ||
603 | q.NewGrid(4, 4); | ||
604 | q.grid[0][8].start = true; | ||
605 | q.grid[4][0].end = Cell::Dir::UP; | ||
606 | q.sequence = solution.sequence; | ||
607 | for (Pos pos : cutEdges) { | ||
608 | q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
609 | } | ||
610 | for (Pos pos : Randomizer2Core::CutInsideEdges(q, 7)) { | ||
611 | q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; | ||
612 | } | ||
613 | _serializer.WritePuzzle(q, 0x1A0F); | ||
614 | } | ||
615 | } | ||
616 | |||
617 | Puzzle Randomizer2::GetUniqueSolution(Puzzle& p) { | ||
618 | auto solutions = Solver::Solve(p); | ||
619 | assert(solutions.size() == 1); | ||
620 | return solutions[0]; | ||
621 | } | ||
622 | |||
623 | void Randomizer2::SetGate(int panel, int X, int Y) { | ||
624 | float x, y, z, w; | ||
625 | if (X%2 == 0 && Y%2 == 1) { // Horizontal | ||
626 | x = -1.49f * X + 0.22f * Y + 66.58f; | ||
627 | y = 0.275f * X + 1.6f * Y + 108.4f; | ||
628 | z = -.77f; | ||
629 | w = .63f; | ||
630 | } else { // Vertical | ||
631 | assert(X%2 == 1 && Y%2 == 0); | ||
632 | x = -1.6f * X + 0.35f * Y + 66.5f; | ||
633 | y = 0.25f * X + 1.6f * Y + 108.55f; | ||
634 | z = -0.1f; | ||
635 | w = 1.0f; | ||
636 | } | ||
637 | |||
638 | SetPos(panel, x, y, 19.2f); | ||
639 | // _memory->WriteEntityData<float>(panel, ORIENTATION, {0.0f, 0.0f, z, w}); | ||
640 | } | ||
641 | |||
642 | void Randomizer2::SetPos(int panel, float x, float y, float z) { | ||
643 | // _memory->WriteEntityData<float>(panel, POSITION, {x, y, z}); | ||
644 | } \ No newline at end of file | ||
diff --git a/Source/Randomizer2.h b/Source/Randomizer2.h deleted file mode 100644 index a2b5ebd..0000000 --- a/Source/Randomizer2.h +++ /dev/null | |||
@@ -1,19 +0,0 @@ | |||
1 | #pragma once | ||
2 | #include "PuzzleSerializer.h" | ||
3 | |||
4 | class Randomizer2 { | ||
5 | public: | ||
6 | Randomizer2(const PuzzleSerializer& serializer); | ||
7 | void Randomize(); | ||
8 | void RandomizeTutorial(); | ||
9 | void RandomizeGlassFactory(); | ||
10 | void RandomizeSymmetryIsland(); | ||
11 | void RandomizeKeep(); | ||
12 | |||
13 | private: | ||
14 | Puzzle GetUniqueSolution(Puzzle& p); | ||
15 | void SetGate(int panel, int X, int Y); | ||
16 | void SetPos(int panel, float x, float y, float z); | ||
17 | |||
18 | PuzzleSerializer _serializer; | ||
19 | }; | ||
diff --git a/Source/Randomizer2Core.cpp b/Source/Randomizer2Core.cpp deleted file mode 100644 index 867fa5a..0000000 --- a/Source/Randomizer2Core.cpp +++ /dev/null | |||
@@ -1,203 +0,0 @@ | |||
1 | #include "pch.h" | ||
2 | #include "Randomizer2Core.h" | ||
3 | #include "Random.h" | ||
4 | |||
5 | std::vector<Pos> Randomizer2Core::CutEdges(const Puzzle& p, size_t numEdges) { | ||
6 | return CutEdgesInternal(p, 0, p.width, 0, p.height, numEdges); | ||
7 | } | ||
8 | |||
9 | std::vector<Pos> Randomizer2Core::CutInsideEdges(const Puzzle& p, size_t numEdges) { | ||
10 | return CutEdgesInternal(p, 1, p.width-1, 1, p.height-1, numEdges); | ||
11 | } | ||
12 | |||
13 | std::vector<Pos> Randomizer2Core::CutSymmetricalEdgePairs(const Puzzle& p, size_t numEdges) { | ||
14 | Puzzle copy = p; | ||
15 | // Prevent cuts from landing on the midline | ||
16 | if (p.symmetry == Puzzle::Symmetry::X) { | ||
17 | for (int y=0; y<p.height; y++) { | ||
18 | copy.grid[p.width/2][y].gap = Cell::Gap::FULL; | ||
19 | } | ||
20 | return CutEdgesInternal(copy, 0, (p.width-1)/2, 0, p.height, numEdges); | ||
21 | } else if (p.symmetry == Puzzle::Symmetry::Y) { | ||
22 | for (int x=0; x<p.width; x++) { | ||
23 | copy.grid[x][p.height/2].gap = Cell::Gap::FULL; | ||
24 | } | ||
25 | return CutEdgesInternal(copy, 0, p.width, 0, (p.height-1)/2, numEdges); | ||
26 | } else { | ||
27 | assert(p.symmetry == Puzzle::Symmetry::XY); | ||
28 | int midX = p.width/2; | ||
29 | int midY = p.height/2; | ||
30 | if (p.width%4 == 1 && p.height%4 == 1) { // For double-even grids, cut around the center | ||
31 | copy.grid[midX-1][midY].gap = Cell::Gap::FULL; | ||
32 | copy.grid[midX][midY-1].gap = Cell::Gap::FULL; | ||
33 | copy.grid[midX][midY+1].gap = Cell::Gap::FULL; | ||
34 | copy.grid[midX+1][midY].gap = Cell::Gap::FULL; | ||
35 | } else if (p.width%4 == 1 && p.height%4 == 3) { // For half-even grids, there's only one line to cut | ||
36 | copy.grid[midX][midY].gap = Cell::Gap::FULL; | ||
37 | } else if (p.width%4 == 3 && p.height%4 == 1) { // For half-even grids, there's only one line to cut | ||
38 | copy.grid[midX][midY].gap = Cell::Gap::FULL; | ||
39 | } | ||
40 | return CutEdgesInternal(copy, 0, p.width, 0, p.height, numEdges); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | std::vector<Pos> Randomizer2Core::CutEdgesInternal(const Puzzle& p, int xMin, int xMax, int yMin, int yMax, size_t numEdges) { | ||
45 | std::vector<Pos> edges; | ||
46 | for (int x=xMin; x<xMax; x++) { | ||
47 | for (int y=yMin; y<yMax; y++) { | ||
48 | if (x%2 == y%2) continue; | ||
49 | if (p.grid[x][y].gap != Cell::Gap::NONE) continue; | ||
50 | if (p.grid[x][y].start) continue; | ||
51 | if (p.grid[x][y].end != Cell::Dir::NONE) continue; | ||
52 | |||
53 | if (p.symmetry == Puzzle::Symmetry::XY) { | ||
54 | assert(p.width == p.height); // TODO: This solution only supports square rotational symmetry. | ||
55 | if (x > y) continue; // Only allow cuts bottom-left of the diagonal | ||
56 | } | ||
57 | |||
58 | // If the puzzle already has a sequence, don't cut along it. | ||
59 | bool inSequence = false; | ||
60 | for (Pos pos : p.sequence) inSequence |= (pos.x == x && pos.y == y); | ||
61 | if (inSequence) continue; | ||
62 | edges.emplace_back(x, y); | ||
63 | } | ||
64 | } | ||
65 | assert(numEdges <= edges.size()); | ||
66 | |||
67 | auto [colorGrid, numColors] = CreateColorGrid(p); | ||
68 | assert(numEdges <= numColors); | ||
69 | |||
70 | // @Hack... sort of. I couldn't think of a better way to do this. | ||
71 | if (p.symmetry == Puzzle::Symmetry::XY) { | ||
72 | // Recolor the diagonal so that opposite cells share a color. This is because we're only cutting along half their edges, | ||
73 | // so they are in fact two sides of the same cell. | ||
74 | for (int x=1; x<p.width/2; x+=2) { | ||
75 | assert(p.width == p.height); // TODO: This solution only supports square rotational symmetry. | ||
76 | colorGrid[x][x] = colorGrid[p.width-x-1][p.width-x-1]; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | std::vector<Pos> cutEdges; | ||
81 | for (int i=0; i<numEdges; i++) { | ||
82 | while (edges.size() > 0) { | ||
83 | int edge = Random::RandInt(0, static_cast<int>(edges.size() - 1)); | ||
84 | Pos pos = edges[edge]; | ||
85 | edges.erase(edges.begin() + edge); | ||
86 | |||
87 | int color1 = 0; | ||
88 | int color2 = 0; | ||
89 | if (pos.x%2 == 0 && pos.y%2 == 1) { // Vertical | ||
90 | if (pos.x > 0) color1 = colorGrid[pos.x-1][pos.y]; | ||
91 | else color1 = 1; | ||
92 | |||
93 | if (pos.x < p.width - 1) color2 = colorGrid[pos.x+1][pos.y]; | ||
94 | else color2 = 1; | ||
95 | } else { // Horizontal | ||
96 | assert(pos.x%2 == 1 && pos.y%2 == 0); | ||
97 | if (pos.y > 0) color1 = colorGrid[pos.x][pos.y-1]; | ||
98 | else color1 = 1; | ||
99 | |||
100 | if (pos.y < p.height - 1) color2 = colorGrid[pos.x][pos.y+1]; | ||
101 | else color2 = 1; | ||
102 | } | ||
103 | // Enforce color1 < color2 | ||
104 | if (color1 > color2) std::swap(color1, color2); | ||
105 | |||
106 | // Colors mismatch, valid cut | ||
107 | if (color1 != color2) { | ||
108 | // @Performance... have a lookup table instead? | ||
109 | for (int x=0; x<p.width; x++) { | ||
110 | for (int y=0; y<p.height; y++) { | ||
111 | if (colorGrid[x][y] == color2) colorGrid[x][y] = color1; | ||
112 | } | ||
113 | } | ||
114 | cutEdges.emplace_back(pos); | ||
115 | break; | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | assert(cutEdges.size() == numEdges); | ||
120 | return cutEdges; | ||
121 | } | ||
122 | |||
123 | #ifndef NDEBUG | ||
124 | #include <Windows.h> | ||
125 | #endif | ||
126 | |||
127 | void Randomizer2Core::DebugColorGrid(const std::vector<std::vector<int>>& colorGrid) { | ||
128 | #ifndef NDEBUG | ||
129 | static std::string colors = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||
130 | for (int y=0; y<colorGrid[0].size(); y++) { | ||
131 | std::string row; | ||
132 | for (int x=0; x<colorGrid.size(); x++) { | ||
133 | row += colors[colorGrid[x][y]]; | ||
134 | } | ||
135 | row += "\n"; | ||
136 | OutputDebugStringA(row.c_str()); | ||
137 | } | ||
138 | OutputDebugStringA("\n"); | ||
139 | #endif | ||
140 | } | ||
141 | |||
142 | void Randomizer2Core::FloodFill(const Puzzle& p, std::vector<std::vector<int>>& colorGrid, int color, int x, int y) { | ||
143 | if (!p.SafeCell(x, y)) return; | ||
144 | if (colorGrid[x][y] != 0) return; // Already processed. | ||
145 | colorGrid[x][y] = color; | ||
146 | |||
147 | FloodFill(p, colorGrid, color, x, y+1); | ||
148 | FloodFill(p, colorGrid, color, x, y-1); | ||
149 | FloodFill(p, colorGrid, color, x+1, y); | ||
150 | FloodFill(p, colorGrid, color, x-1, y); | ||
151 | } | ||
152 | |||
153 | void Randomizer2Core::FloodFillOutside(const Puzzle& p, std::vector<std::vector<int>>& colorGrid, int x, int y) { | ||
154 | if (!p.SafeCell(x, y)) return; | ||
155 | if (colorGrid[x][y] != 0) return; // Already processed. | ||
156 | if (x%2 != y%2 && p.grid[x][y].gap == Cell::Gap::NONE) return; // Only flood-fill through gaps | ||
157 | colorGrid[x][y] = 1; // Outside color | ||
158 | |||
159 | FloodFillOutside(p, colorGrid, x, y+1); | ||
160 | FloodFillOutside(p, colorGrid, x, y-1); | ||
161 | FloodFillOutside(p, colorGrid, x+1, y); | ||
162 | FloodFillOutside(p, colorGrid, x-1, y); | ||
163 | } | ||
164 | |||
165 | // Color key: | ||
166 | // 0 (default): Uncolored | ||
167 | // 1: Outside color and separator color | ||
168 | // 2+: Flood-filled region color | ||
169 | std::tuple<std::vector<std::vector<int>>, int> Randomizer2Core::CreateColorGrid(const Puzzle& p) { | ||
170 | std::vector<std::vector<int>> colorGrid; | ||
171 | colorGrid.resize(p.width); | ||
172 | |||
173 | for (int x=0; x<p.width; x++) { | ||
174 | colorGrid[x].resize(p.height); | ||
175 | for (int y=0; y<p.height; y++) { | ||
176 | if (x%2 == 1 && y%2 == 1) continue; | ||
177 | // Mark all unbroken edges and intersections as 'do not color' | ||
178 | if (p.grid[x][y].gap == Cell::Gap::NONE) colorGrid[x][y] = 1; | ||
179 | } | ||
180 | } | ||
181 | |||
182 | // @Future: Skip this loop if pillar = true; | ||
183 | for (int y=0; y<p.height; y++) { | ||
184 | FloodFillOutside(p, colorGrid, 0, y); | ||
185 | FloodFillOutside(p, colorGrid, p.width - 1, y); | ||
186 | } | ||
187 | |||
188 | for (int x=0; x<p.width; x++) { | ||
189 | FloodFillOutside(p, colorGrid, x, 0); | ||
190 | FloodFillOutside(p, colorGrid, x, p.height - 1); | ||
191 | } | ||
192 | |||
193 | int color = 1; | ||
194 | for (int x=0; x<p.width; x++) { | ||
195 | for (int y=0; y<p.height; y++) { | ||
196 | if (colorGrid[x][y] != 0) continue; // No dead colors | ||
197 | color++; | ||
198 | FloodFill(p, colorGrid, color, x, y); | ||
199 | } | ||
200 | } | ||
201 | |||
202 | return {colorGrid, color}; | ||
203 | } \ No newline at end of file | ||
diff --git a/Source/Randomizer2Core.h b/Source/Randomizer2Core.h deleted file mode 100644 index df98de8..0000000 --- a/Source/Randomizer2Core.h +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | #pragma once | ||
2 | |||
3 | class Randomizer2Core { | ||
4 | public: | ||
5 | // CAUTION: These do not actually cut edges, they just returns a list of suggested cuts. | ||
6 | static std::vector<Pos> CutEdges(const Puzzle& p, size_t numEdges); | ||
7 | static std::vector<Pos> CutInsideEdges(const Puzzle& p, size_t numEdges); | ||
8 | static std::vector<Pos> CutSymmetricalEdgePairs(const Puzzle& p, size_t numEdges); | ||
9 | |||
10 | private: | ||
11 | static std::vector<Pos> CutEdgesInternal(const Puzzle& p, int xMin, int xMax, int yMin, int yMax, size_t numEdges); | ||
12 | static void DebugColorGrid(const std::vector<std::vector<int>>& colorGrid); | ||
13 | static void FloodFill(const Puzzle& p, std::vector<std::vector<int>>& colorGrid, int color, int x, int y); | ||
14 | static void FloodFillOutside(const Puzzle& p, std::vector<std::vector<int>>& colorGrid, int x, int y); | ||
15 | static std::tuple<std::vector<std::vector<int>>, int> CreateColorGrid(const Puzzle& p); | ||
16 | }; | ||
17 | |||
diff --git a/Source/Source.vcxproj b/Source/Source.vcxproj index 1cfb484..8d6104c 100644 --- a/Source/Source.vcxproj +++ b/Source/Source.vcxproj | |||
@@ -172,8 +172,6 @@ | |||
172 | <ClInclude Include="PuzzleSerializer.h" /> | 172 | <ClInclude Include="PuzzleSerializer.h" /> |
173 | <ClInclude Include="Random.h" /> | 173 | <ClInclude Include="Random.h" /> |
174 | <ClInclude Include="Randomizer.h" /> | 174 | <ClInclude Include="Randomizer.h" /> |
175 | <ClInclude Include="Randomizer2.h" /> | ||
176 | <ClInclude Include="Randomizer2Core.h" /> | ||
177 | <ClInclude Include="Solver.h" /> | 175 | <ClInclude Include="Solver.h" /> |
178 | <ClInclude Include="Validator.h" /> | 176 | <ClInclude Include="Validator.h" /> |
179 | </ItemGroup> | 177 | </ItemGroup> |
@@ -190,8 +188,6 @@ | |||
190 | <ClCompile Include="PuzzleSerializer.cpp" /> | 188 | <ClCompile Include="PuzzleSerializer.cpp" /> |
191 | <ClCompile Include="Random.cpp" /> | 189 | <ClCompile Include="Random.cpp" /> |
192 | <ClCompile Include="Randomizer.cpp" /> | 190 | <ClCompile Include="Randomizer.cpp" /> |
193 | <ClCompile Include="Randomizer2.cpp" /> | ||
194 | <ClCompile Include="Randomizer2Core.cpp" /> | ||
195 | <ClCompile Include="Solver.cpp" /> | 191 | <ClCompile Include="Solver.cpp" /> |
196 | <ClCompile Include="Validator.cpp" /> | 192 | <ClCompile Include="Validator.cpp" /> |
197 | </ItemGroup> | 193 | </ItemGroup> |