summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2021-08-21 17:12:29 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2021-08-21 17:12:29 -0400
commitc60df0e75e63f488d94fd744ad70df8124dc7724 (patch)
tree92940cd8c0da9102c7f1f9ef37ed4d4b5d341985
parent02327bf40bb9f6ef8d5e17fa982da70a3fe93eb4 (diff)
downloadwitness-tutorializer-c60df0e75e63f488d94fd744ad70df8124dc7724.tar.gz
witness-tutorializer-c60df0e75e63f488d94fd744ad70df8124dc7724.tar.bz2
witness-tutorializer-c60df0e75e63f488d94fd744ad70df8124dc7724.zip
Souped up the UI
-rw-r--r--App/Main.cpp180
-rw-r--r--App/Version.h8
-rw-r--r--Source/Randomizer2.cpp644
-rw-r--r--Source/Randomizer2.h19
-rw-r--r--Source/Randomizer2Core.cpp203
-rw-r--r--Source/Randomizer2Core.h17
-rw-r--r--Source/Source.vcxproj4
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
31HWND g_panelId; 22HWND g_panelId;
32Puzzle g_puzzle; 23Puzzle g_puzzle;
@@ -37,13 +28,11 @@ HWND g_rngDebug;
37 28
38// Globals 29// Globals
39HWND g_hwnd; 30HWND g_hwnd;
40HWND g_seed; 31//HWND g_seed;
41HWND g_randomizerStatus; 32HWND g_randomizerStatus;
42HINSTANCE g_hInstance; 33HINSTANCE g_hInstance;
43auto g_witnessProc = std::make_shared<Memory>(L"witness64_d3d11.exe"); 34auto g_witnessProc = std::make_shared<Memory>(L"witness64_d3d11.exe");
44std::shared_ptr<Randomizer> g_randomizer; 35std::shared_ptr<Randomizer> g_randomizer;
45std::shared_ptr<Randomizer2> g_randomizer2;
46void SetRandomSeed();
47 36
48LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { 37LRESULT 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
207void 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
226HWND CreateLabel(int x, int y, int width, LPCWSTR text) { 95HWND 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
101HWND 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
232HWND CreateText(int x, int y, int width, LPCWSTR defaultText = L"") { 107HWND 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
9Randomizer2::Randomizer2(const PuzzleSerializer& serializer) : _serializer(serializer) {
10}
11
12void Randomizer2::Randomize() {
13 // RandomizeTutorial();
14 // RandomizeGlassFactory();
15 RandomizeSymmetryIsland();
16 // RandomizeKeep();
17}
18
19void 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
149void 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
364void 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
476void 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
617Puzzle Randomizer2::GetUniqueSolution(Puzzle& p) {
618 auto solutions = Solver::Solve(p);
619 assert(solutions.size() == 1);
620 return solutions[0];
621}
622
623void 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
642void 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
4class Randomizer2 {
5public:
6 Randomizer2(const PuzzleSerializer& serializer);
7 void Randomize();
8 void RandomizeTutorial();
9 void RandomizeGlassFactory();
10 void RandomizeSymmetryIsland();
11 void RandomizeKeep();
12
13private:
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
5std::vector<Pos> Randomizer2Core::CutEdges(const Puzzle& p, size_t numEdges) {
6 return CutEdgesInternal(p, 0, p.width, 0, p.height, numEdges);
7}
8
9std::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
13std::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
44std::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
127void 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
142void 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
153void 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
169std::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
3class Randomizer2Core {
4public:
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
10private:
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>