diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | LICENSE | 21 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | ext/wittle_generator/.clang-format | 2 | ||||
-rw-r--r-- | ext/wittle_generator/CMakeLists.txt | 6 | ||||
-rw-r--r-- | ext/wittle_generator/Generate.cpp | 2056 | ||||
-rw-r--r-- | ext/wittle_generator/Generate.h | 248 | ||||
-rw-r--r-- | ext/wittle_generator/Panel.cpp | 116 | ||||
-rw-r--r-- | ext/wittle_generator/Panel.h | 448 | ||||
-rw-r--r-- | ext/wittle_generator/PuzzleSymbols.h | 59 | ||||
-rw-r--r-- | ext/wittle_generator/Random.cpp | 5 | ||||
-rw-r--r-- | ext/wittle_generator/Random.h | 16 | ||||
-rw-r--r-- | ext/wittle_generator/Test.cpp | 10 | ||||
-rw-r--r-- | lib/keep | 0 |
14 files changed, 2991 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caaa972 --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1 @@ | |||
ext/wittle_generator/build | |||
diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3e5ac40 --- /dev/null +++ b/LICENSE | |||
@@ -0,0 +1,21 @@ | |||
1 | MIT License | ||
2 | |||
3 | Copyright (c) 2018 jbzdarkid, Sigma144, hatkirby | ||
4 | |||
5 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
6 | of this software and associated documentation files (the "Software"), to deal | ||
7 | in the Software without restriction, including without limitation the rights | ||
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
9 | copies of the Software, and to permit persons to whom the Software is | ||
10 | furnished to do so, subject to the following conditions: | ||
11 | |||
12 | The above copyright notice and this permission notice shall be included in all | ||
13 | copies or substantial portions of the Software. | ||
14 | |||
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
21 | SOFTWARE. | ||
diff --git a/README.md b/README.md new file mode 100644 index 0000000..28d37c6 --- /dev/null +++ b/README.md | |||
@@ -0,0 +1,3 @@ | |||
1 | # wittle-generator | ||
2 | |||
3 | Ruby gem that can generate Witness puzzles. Adapted from [Sigma144's Witness Random Puzzle Generator](https://github.com/sigma144/witness-randomizer), which is itself based on [darkid's Witness Randomizer](https://github.com/darkid/witness-randomizer). | ||
diff --git a/ext/wittle_generator/.clang-format b/ext/wittle_generator/.clang-format new file mode 100644 index 0000000..8de7fe6 --- /dev/null +++ b/ext/wittle_generator/.clang-format | |||
@@ -0,0 +1,2 @@ | |||
1 | --- | ||
2 | BasedOnStyle: Google | ||
diff --git a/ext/wittle_generator/CMakeLists.txt b/ext/wittle_generator/CMakeLists.txt new file mode 100644 index 0000000..236cc83 --- /dev/null +++ b/ext/wittle_generator/CMakeLists.txt | |||
@@ -0,0 +1,6 @@ | |||
1 | cmake_minimum_required (VERSION 3.1) | ||
2 | project (wittle_generator) | ||
3 | |||
4 | add_executable(wittle_generator Generate.cpp Panel.cpp Random.cpp Test.cpp) | ||
5 | set_property(TARGET wittle_generator PROPERTY CXX_STANDARD 17) | ||
6 | set_property(TARGET wittle_generator PROPERTY CXX_STANDARD_REQUIRED ON) | ||
diff --git a/ext/wittle_generator/Generate.cpp b/ext/wittle_generator/Generate.cpp new file mode 100644 index 0000000..22c211f --- /dev/null +++ b/ext/wittle_generator/Generate.cpp | |||
@@ -0,0 +1,2056 @@ | |||
1 | #include "Generate.h" | ||
2 | |||
3 | #include <iostream> | ||
4 | |||
5 | std::vector<Point> Generate::_DIRECTIONS1 = {Point(0, 1), Point(0, -1), | ||
6 | Point(1, 0), Point(-1, 0)}; | ||
7 | std::vector<Point> Generate::_8DIRECTIONS1 = { | ||
8 | Point(0, 1), Point(0, -1), Point(1, 0), Point(-1, 0), | ||
9 | Point(1, 1), Point(1, -1), Point(-1, -1), Point(-1, 1)}; | ||
10 | std::vector<Point> Generate::_DIRECTIONS2 = {Point(0, 2), Point(0, -2), | ||
11 | Point(2, 0), Point(-2, 0)}; | ||
12 | std::vector<Point> Generate::_8DIRECTIONS2 = { | ||
13 | Point(0, 2), Point(0, -2), Point(2, 0), Point(-2, 0), | ||
14 | Point(2, 2), Point(2, -2), Point(-2, -2), Point(-2, 2)}; | ||
15 | std::vector<Point> Generate::_DISCONNECT = { | ||
16 | Point(0, 2), Point(0, -2), Point(2, 0), | ||
17 | Point(-2, 0), Point(2, 2), Point(2, -2), | ||
18 | Point(-2, -2), Point(-2, 2), Point(0, 2), | ||
19 | Point(0, -2), Point(2, 0), Point(-2, 0), | ||
20 | Point(2, 2), Point(2, -2), Point(-2, -2), | ||
21 | Point(-2, 2), Point(0, 4), Point(0, -4), | ||
22 | Point(4, 0), Point(-4, 0), // Used to make the discontiguous shapes | ||
23 | }; | ||
24 | std::vector<Point> Generate::_SHAPEDIRECTIONS = | ||
25 | {}; // This will eventually be set to one of the above lists | ||
26 | |||
27 | // Make a maze puzzle. The maze will have one solution. id - id of the puzzle | ||
28 | /*void Generate::generateMaze(int id) { | ||
29 | while (!generate_maze(id, 0, 0)) | ||
30 | ; | ||
31 | } | ||
32 | |||
33 | // Make a maze puzzle. The maze will have one solution. id - id of the puzzle. | ||
34 | // numStarts - how many starts to add (only one will be valid). numExits - how | ||
35 | // many exits to add. All will work Setting numStarts or numExits to 0 will keep | ||
36 | // the starts/exits where they originally were, otherwise the starts/exits | ||
37 | // originally there will be removed and new ones randomly placed. | ||
38 | void Generate::generateMaze(int id, int numStarts, int numExits) { | ||
39 | while (!generate_maze(id, numStarts, numExits)) | ||
40 | ; | ||
41 | }*/ | ||
42 | |||
43 | // Read in default panel data, such as dimensions, symmetry, starts/exits, etc. | ||
44 | // id - id of the puzzle | ||
45 | void Generate::initPanel() { | ||
46 | _panel = std::make_unique<Panel>(); | ||
47 | _panel->Resize(_width, _height); | ||
48 | |||
49 | // erase_path(); | ||
50 | |||
51 | //_panelData.resize(_height); | ||
52 | // for (auto& row : _panelData) row.resize(_width); | ||
53 | |||
54 | if (hasFlag(Config::TreehouseLayout)) { | ||
55 | init_treehouse_layout(); | ||
56 | } | ||
57 | |||
58 | // Fill gridpos with every available grid block | ||
59 | _gridpos.clear(); | ||
60 | for (int x = 1; x < _panel->width(); x += 2) { | ||
61 | for (int y = 1; y < _panel->height(); y += 2) { | ||
62 | /*if (!(hasFlag(Config::PreserveStructure) && | ||
63 | (get(x, y) & Decoration::Empty) == Decoration::Empty))*/ | ||
64 | _gridpos.emplace(Point(x, y)); | ||
65 | } | ||
66 | } | ||
67 | // Init the open positions available for symbols. Defaults to every grid block | ||
68 | // unless a custom openpos has been specified | ||
69 | if (openPos.size() > 0) | ||
70 | _openpos = openPos; | ||
71 | else | ||
72 | _openpos = _gridpos; | ||
73 | for (Point p : blockPos) | ||
74 | _openpos.erase(p); // Remove the points which the user has defined to not | ||
75 | // place symbols on | ||
76 | for (Point p : _splitPoints) | ||
77 | _openpos.erase(p); // The split points will have erasers and cannot have | ||
78 | // any other symbols placed on them | ||
79 | _fullGaps = hasFlag(Config::FullGaps); | ||
80 | _panel->symmetry = | ||
81 | _symmetry; // Init user-defined puzzle symmetry if not "None". | ||
82 | // 0x00076 (Symmetry Island Fading Lines 7) and 0x01D3F (Keep Blue Pressure | ||
83 | // Plates) are exceptions because they need to have symmetry removed | ||
84 | if (pathWidth != 1) | ||
85 | _panel->pathWidth = pathWidth; // Init path scale. "1" is considered the | ||
86 | // default, and therefore means no change. | ||
87 | } | ||
88 | |||
89 | // Place a specific symbol into the puzzle at the specified location. The | ||
90 | // generator will add other symbols, but will leave the set ones where they are. | ||
91 | // symbol - the symbol to place. //x, y - the coordinates to put it at. (0, 0) | ||
92 | // is at top left. Lines are at even coordinates and grid blocks at odd | ||
93 | // coordinates | ||
94 | void Generate::setSymbol(Decoration::Shape symbol, int x, int y) { | ||
95 | if (_custom_grid.size() < x + 1) { | ||
96 | _custom_grid.resize(x + 1, std::vector<int>()); | ||
97 | for (auto& row : _custom_grid) { | ||
98 | row.resize(_custom_grid[0].size(), 0); | ||
99 | } | ||
100 | } | ||
101 | for (auto& row : _custom_grid) { | ||
102 | if (row.size() < y + 1) { | ||
103 | row.resize(y + 1, 0); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | if (symbol == Decoration::Start) | ||
108 | _starts.emplace(Point(x, y)); | ||
109 | else if (symbol == Decoration::Exit) | ||
110 | _exits.emplace(Point(x, y)); | ||
111 | else | ||
112 | _custom_grid[x][y] = symbol; // Starts and exits are not set into the grid | ||
113 | } | ||
114 | |||
115 | // Set the dimensions of the puzzles. This setting will persist between puzzle | ||
116 | // generation calls. (0, 0) will have the generator use the same dimensions as | ||
117 | // the orignal puzzle. width, height - the dimensions to use, measured in grid | ||
118 | // blocks. | ||
119 | void Generate::setGridSize(int width, int height) { | ||
120 | if (width <= 0 || height <= 0) { | ||
121 | _width = 0; | ||
122 | _height = 0; | ||
123 | } else { | ||
124 | _width = width * 2 + 1; | ||
125 | _height = height * 2 + 1; | ||
126 | } | ||
127 | } | ||
128 | |||
129 | // Set the type of symmetry to use. This setting will persist between puzzle | ||
130 | // generation calls. Using "None" will make the generator use the existing | ||
131 | // puzzle symmetry. | ||
132 | void Generate::setSymmetry(Panel::Symmetry symmetry) { | ||
133 | _symmetry = symmetry; | ||
134 | if (_symmetry == Panel::Symmetry::ParallelV || | ||
135 | _symmetry == Panel::Symmetry::ParallelVFlip) { | ||
136 | std::vector<Point> points; | ||
137 | for (int y = 0; y < _height; y += 2) | ||
138 | points.emplace_back(Point(_width / 2, y)); | ||
139 | setObstructions(points); // This prevents the generator from invalidly | ||
140 | // passing through the center line | ||
141 | } | ||
142 | if (_symmetry == Panel::Symmetry::ParallelH || | ||
143 | _symmetry == Panel::Symmetry::ParallelHFlip) { | ||
144 | std::vector<Point> points; | ||
145 | for (int x = 0; x < _width; x += 2) | ||
146 | points.emplace_back(Point(x, _height / 2)); | ||
147 | setObstructions(points); // This prevents the generator from invalidly | ||
148 | // passing through the center line | ||
149 | } | ||
150 | } | ||
151 | |||
152 | // Write out panel data to the puzzle with the given id | ||
153 | void Generate::write(int id) { | ||
154 | std::vector<std::vector<int>> backupGrid; | ||
155 | /* if (hasFlag(Config::DisableReset)) | ||
156 | backupGrid = | ||
157 | _panel->_grid; // Allows panel data to be preserved after writing. | ||
158 | // Normally writing erases the panel data.*/ | ||
159 | |||
160 | erase_path(); | ||
161 | |||
162 | // TODO: write | ||
163 | |||
164 | // Undo any one-time config changes | ||
165 | if (_oneTimeAdd) { | ||
166 | _config &= ~_oneTimeAdd; | ||
167 | _oneTimeAdd = 0; | ||
168 | } | ||
169 | if (_oneTimeRemove) { | ||
170 | _config |= _oneTimeRemove; | ||
171 | _oneTimeRemove = 0; | ||
172 | } | ||
173 | // Manually advance seed by 1 each generation to prevent seeds "funneling" | ||
174 | // from repeated fails | ||
175 | Random::seed(_seed); | ||
176 | _seed = Random::rand(); | ||
177 | } | ||
178 | |||
179 | // Reset all config flags and persistent settings, including width/height and | ||
180 | // symmetry. | ||
181 | void Generate::resetConfig() { | ||
182 | setGridSize(0, 0); | ||
183 | _symmetry = Panel::Symmetry::None; | ||
184 | pathWidth = 1; | ||
185 | if (hasFlag(Config::DisableReset)) { | ||
186 | resetVars(); | ||
187 | } | ||
188 | _config = 0; | ||
189 | _oneTimeAdd = Config::None; | ||
190 | _oneTimeRemove = Config::None; | ||
191 | arrowColor = backgroundColor = successColor = {0, 0, 0, 0}; | ||
192 | } | ||
193 | |||
194 | //----------------------Private-------------------------- | ||
195 | |||
196 | // Add the point (pos) to the intended solution path, using symmetry if | ||
197 | // applicable. | ||
198 | void Generate::set_path(Point pos) { | ||
199 | set(pos, PATH); | ||
200 | _path.insert(pos); | ||
201 | if (_panel->symmetry) { | ||
202 | _path1.insert(pos); | ||
203 | Point sp = get_sym_point(pos); | ||
204 | set(sp, PATH); | ||
205 | _path.insert(sp); | ||
206 | _path2.insert(sp); | ||
207 | } | ||
208 | } | ||
209 | |||
210 | // Remove the path and all symbols from the grid. This does not affect | ||
211 | // starts/exits. If PreserveStructure is active, open gaps will be kept. If a | ||
212 | // custom grid is set, this will reset it back to the custom grid state. | ||
213 | void Generate::clear() { | ||
214 | if (_custom_grid.size() > 0) { | ||
215 | for (int x = 0; x < _panel->width(); x++) { | ||
216 | for (int y = 0; y < _panel->height(); y++) { | ||
217 | set(x, y, _custom_grid[x][y]); | ||
218 | } | ||
219 | } | ||
220 | } else | ||
221 | for (int x = 0; x < _panel->width(); x++) { | ||
222 | for (int y = 0; y < _panel->height(); y++) { | ||
223 | /*if (hasFlag(Config::PreserveStructure) && | ||
224 | (_panel->_grid[x][y] == OPEN || | ||
225 | (_panel->_grid[x][y] & 0x60000f) == NO_POINT || | ||
226 | (_panel->_grid[x][y] & Decoration::Empty) == Decoration::Empty)) | ||
227 | continue;*/ | ||
228 | set(x, y, 0); | ||
229 | } | ||
230 | } | ||
231 | //_panel->_style &= ~0x2ff8; // Remove all element flags | ||
232 | _path.clear(); | ||
233 | _path1.clear(); | ||
234 | _path2.clear(); | ||
235 | } | ||
236 | |||
237 | // Reset generator variables and lists used when generating puzzles. (not config | ||
238 | // settings) | ||
239 | void Generate::resetVars() { | ||
240 | _panel = NULL; // This is needed for the generator to read in the next panel | ||
241 | _starts.clear(); | ||
242 | _exits.clear(); | ||
243 | _custom_grid.clear(); | ||
244 | hitPoints.clear(); | ||
245 | _obstructions.clear(); | ||
246 | openPos.clear(); | ||
247 | blockPos.clear(); | ||
248 | _splitPoints.clear(); | ||
249 | } | ||
250 | |||
251 | // Place start and exits in central positions like in the treehouse | ||
252 | void Generate::init_treehouse_layout() { | ||
253 | // bool pivot = _panel->_endpoints.size() > 2; | ||
254 | bool pivot = false; | ||
255 | setSymbol(Decoration::Start, _panel->width() / 2, _panel->height() - 1); | ||
256 | setSymbol(Decoration::Exit, _panel->width() / 2, 0); | ||
257 | if (pivot) { | ||
258 | setSymbol(Decoration::Exit, _panel->width() - 1, _panel->height() / 2); | ||
259 | setSymbol(Decoration::Exit, 0, _panel->height() / 2); | ||
260 | } | ||
261 | } | ||
262 | /* | ||
263 | // Private version of generateMaze. Should be called again if false is returned. | ||
264 | // The algorithm works by generating a correct path, then extending lines off of | ||
265 | // it until the maze is filled. | ||
266 | bool Generate::generate_maze(int id, int numStarts, int numExits) { | ||
267 | initPanel(id); | ||
268 | |||
269 | if (numStarts > 0) place_start(numStarts); | ||
270 | if (numExits > 0) place_exit(numExits); | ||
271 | |||
272 | // Prevent start and exit from overlapping, except in one one particular | ||
273 | // puzzle (0x00083). | ||
274 | if (id == 0x00083 && _width == 15 && _height == 15) { | ||
275 | clear(); | ||
276 | _panel->_endpoints.clear(); | ||
277 | _exits.clear(); | ||
278 | Point start = pick_random(_starts); | ||
279 | _panel->SetGridSymbol(start.first, start.second, Decoration::Exit, | ||
280 | Decoration::Color::None); | ||
281 | Point sp = get_sym_point(start); | ||
282 | _panel->SetGridSymbol(sp.first, sp.second, Decoration::Exit, | ||
283 | Decoration::Color::None); | ||
284 | set_path(start); | ||
285 | set_path(sp); | ||
286 | } else { | ||
287 | for (Point p : _starts) | ||
288 | if (_exits.count(p)) return false; | ||
289 | |||
290 | clear(); | ||
291 | if (hasFlag(Generate::Config::ShortPath)) { | ||
292 | while (!generate_path_length( | ||
293 | (_panel->width() + _panel->height()), | ||
294 | min((_panel->width() + _panel->height()) * 2, | ||
295 | (_panel->width() / 2 + 1) * (_panel->height() / 2 + 1) * 1 / 2))) | ||
296 | clear(); | ||
297 | } | ||
298 | while (!generate_path_length( | ||
299 | (_panel->width() + _panel->height()), | ||
300 | min((_panel->width() + _panel->height()) * 2, | ||
301 | (_panel->width() / 2 + 1) * (_panel->height() / 2 + 1) * 4 / 5))) | ||
302 | clear(); | ||
303 | } | ||
304 | |||
305 | std::set<Point> path = _path; // Backup | ||
306 | |||
307 | // Extra false starts are tracked in a separate list so that the generator can | ||
308 | // make sure to extend each of them by a higher amount than usual. | ||
309 | std::set<Point> extraStarts; | ||
310 | for (Point pos : _starts) { | ||
311 | if (!_path.count(pos)) { | ||
312 | extraStarts.insert(pos); | ||
313 | } | ||
314 | set_path(pos); | ||
315 | } | ||
316 | // Check to see if the correct path runs over any of the false start points. | ||
317 | // If so, start over | ||
318 | if (extraStarts.size() != | ||
319 | (_panel->symmetry ? _starts.size() / 2 - 1 : _starts.size() - 1)) | ||
320 | return false; | ||
321 | |||
322 | std::set<Point> check; | ||
323 | std::vector<Point> deadEndH, deadEndV; | ||
324 | for (Point p : _path) { | ||
325 | if (p.first % 2 == 0 && p.second % 2 == 0) | ||
326 | check.insert(p); // Only extend off of the points at grid intersections. | ||
327 | } | ||
328 | while (check.size() > 0) { | ||
329 | // Pick a random extendable point and extend it for some randomly chosen | ||
330 | // amount of units. | ||
331 | Point randomPos = (extraStarts.size() > 0 ? pick_random(extraStarts) | ||
332 | : pick_random(check)); | ||
333 | Point pos = randomPos; | ||
334 | for (int i = (extraStarts.size() > 0 ? 7 : 1); i >= 0; | ||
335 | i--) { // False starts are extended by up to 7 units. Other points are | ||
336 | // extended 1 unit at a time | ||
337 | std::vector<Point> validDir; | ||
338 | for (Point dir : _DIRECTIONS2) { | ||
339 | if (!off_edge(pos + dir) && get(pos + dir) == 0) { | ||
340 | validDir.push_back(dir); | ||
341 | } | ||
342 | } | ||
343 | if (validDir.size() < 2) | ||
344 | check.erase(pos); // If there are 0 or 1 open directions, the point | ||
345 | // cannot be extended again. | ||
346 | if (validDir.size() == 0) { | ||
347 | if (extraStarts.size() > 0) { | ||
348 | return false; // Not all the starts were extended successfully. | ||
349 | } | ||
350 | // If full gaps mode is enabled, detect dead ends, so that square tips | ||
351 | // can be put on them | ||
352 | if (_fullGaps && !_exits.count(pos) && !_starts.count(pos)) { | ||
353 | int countOpenRow = 0, countOpenColumn = 0; | ||
354 | for (Point dir2 : _DIRECTIONS1) { | ||
355 | Point added = pos + dir2; | ||
356 | if (!off_edge(added) && _drawnPath[added.second][added.first]) { | ||
357 | if (dir2.first == 0) | ||
358 | countOpenColumn++; | ||
359 | else | ||
360 | countOpenRow++; | ||
361 | } | ||
362 | } | ||
363 | if (countOpenRow + countOpenColumn == 1) { | ||
364 | if (countOpenRow) | ||
365 | deadEndH.push_back(pos); | ||
366 | else | ||
367 | deadEndV.push_back(pos); | ||
368 | } | ||
369 | } | ||
370 | break; // A dead end has been reached, extend a different point | ||
371 | } | ||
372 | Point dir = pick_random(validDir); | ||
373 | Point newPos = pos + dir; | ||
374 | set_path(newPos); | ||
375 | set_path(pos + dir / 2); | ||
376 | check.insert(newPos); | ||
377 | pos = newPos; | ||
378 | } | ||
379 | if (extraStarts.size() > 0) extraStarts.erase(randomPos); | ||
380 | } | ||
381 | // Put openings or gaps in any unused row or column segment | ||
382 | for (int y = 0; y < _panel->height(); y++) { | ||
383 | for (int x = (y + 1) % 2; x < _panel->width(); x += 2) { | ||
384 | if (!_drawnPath[y][x]) { | ||
385 | _panel->SetGridSymbol(x, y, | ||
386 | _fullGaps ? OPEN | ||
387 | : x % 2 == 0 ? Decoration::Gap_Column | ||
388 | : Decoration::Gap_Row); | ||
389 | if (_panel->symmetry) { | ||
390 | Point sp = get_sym_point(Point(x, y)); | ||
391 | if (sp.first == x && sp.second == y || | ||
392 | sp.first == x && x % 2 == 0 && abs(sp.second - y) <= 2 || | ||
393 | sp.second == y && y % 2 == 0 && abs(sp.first - x) <= 2 || | ||
394 | abs(sp.first - x) == 1) { | ||
395 | _drawnPath[y][x] = true; | ||
396 | } else if (Random::rand() % 2 == 0) { | ||
397 | _drawnPath[sp.second][sp.first] = true; | ||
398 | } else { | ||
399 | _drawnPath[y][x] = true; | ||
400 | _panel->SetGridSymbol(sp, _fullGaps ? OPEN | ||
401 | : x % 2 == 0 ? Decoration::Gap_Column | ||
402 | : Decoration::Gap_Row); | ||
403 | } | ||
404 | } | ||
405 | } | ||
406 | } | ||
407 | } | ||
408 | // Put square ends on any dead ends | ||
409 | for (Point p : deadEndH) { | ||
410 | _panel->SetGridSymbol(p, Decoration::Gap_Row); | ||
411 | } | ||
412 | for (Point p : deadEndV) { | ||
413 | _panel->SetGridSymbol(p, Decoration::Gap_Column); | ||
414 | } | ||
415 | _path = path; // Restore backup of the correct solution for testing purposes | ||
416 | std::vector<std::string> solution; // For debugging only | ||
417 | for (int y = 0; y < _panel->height(); y++) { | ||
418 | std::string row; | ||
419 | for (int x = 0; x < _panel->width(); x++) { | ||
420 | if (_path.count(Point(x, y))) { | ||
421 | row += "xx"; | ||
422 | } else | ||
423 | row += " "; | ||
424 | } | ||
425 | solution.push_back(row); | ||
426 | } | ||
427 | if (!hasFlag(Config::DisableWrite)) write(id); | ||
428 | return true; | ||
429 | }*/ | ||
430 | |||
431 | void Generate::generate(int width, int height, PuzzleSymbols symbols) { | ||
432 | while (!generateInternal(width, height, symbols)) | ||
433 | ; | ||
434 | } | ||
435 | |||
436 | // The primary generation function. id - id of the puzzle. symbols - a structure | ||
437 | // representing the amount and types of each symbol to add to the puzzle The | ||
438 | // algorithm works by making a random path and then adding the chosen symbols to | ||
439 | // the grid in such a way that they will be satisfied by the path. if at some | ||
440 | // point the generator fails to add a symbol while still making the solution | ||
441 | // correct, the function returns false and must be called again. | ||
442 | bool Generate::generateInternal(int width, int height, PuzzleSymbols symbols) { | ||
443 | _width = width; | ||
444 | _height = height; | ||
445 | |||
446 | initPanel(); | ||
447 | |||
448 | // Multiple erasers are forced to be separate by default. This is because | ||
449 | // combining them causes unpredictable and inconsistent behavior. | ||
450 | if (symbols.getNum(Decoration::Eraser) > 1 && | ||
451 | !hasFlag(Config::CombineErasers)) { | ||
452 | setSymbol(Decoration::Gap_Row, 1, 0); | ||
453 | setSymbol(Decoration::Gap_Row, _panel->width() - 2, _panel->height() - 1); | ||
454 | _splitPoints = {Point(1, 1), | ||
455 | Point(_panel->width() - 2, _panel->height() - 2)}; | ||
456 | // initPanel(id); // Re-initing to account for the newly added information | ||
457 | } | ||
458 | |||
459 | // Init parity for full dot puzzles | ||
460 | if (symbols.getNum(Decoration::Dot) >= _panel->get_num_grid_points() - 2) | ||
461 | _parity = | ||
462 | (_panel->get_parity() + | ||
463 | (!symbols.any(Decoration::Start) ? get_parity(pick_random(_starts)) | ||
464 | : !symbols.any(Decoration::Exit) ? get_parity(pick_random(_exits)) | ||
465 | : Random::rand() % 2)) % | ||
466 | 2; | ||
467 | else | ||
468 | _parity = -1; //-1 indicates a non-full dot puzzle | ||
469 | |||
470 | if (symbols.any(Decoration::Start)) | ||
471 | place_start(symbols.getNum(Decoration::Start)); | ||
472 | if (symbols.any(Decoration::Exit)) | ||
473 | place_exit(symbols.getNum(Decoration::Exit)); | ||
474 | |||
475 | // Make a random path unless a fixed one has been defined | ||
476 | if (customPath.size() == 0) { | ||
477 | int fails = 0; | ||
478 | while (!generate_path(symbols)) { | ||
479 | if (fails++ > 20) | ||
480 | return false; // It gets several chances to make a path so that the | ||
481 | // whole init process doesn't have to be repeated so many | ||
482 | // times | ||
483 | } | ||
484 | } else | ||
485 | _path = customPath; | ||
486 | |||
487 | std::vector<std::string> solution; // For debugging only | ||
488 | for (int y = 0; y < _panel->height(); y++) { | ||
489 | std::string row; | ||
490 | for (int x = 0; x < _panel->width(); x++) { | ||
491 | if (get(x, y) == PATH) { | ||
492 | row += "xx"; | ||
493 | } else | ||
494 | row += " "; | ||
495 | } | ||
496 | solution.push_back(row); | ||
497 | } | ||
498 | |||
499 | // Attempt to add the symbols | ||
500 | if (!place_all_symbols(symbols)) return false; | ||
501 | |||
502 | for (const auto& row : solution) { | ||
503 | std::cout << row << std::endl; | ||
504 | } | ||
505 | |||
506 | return true; | ||
507 | } | ||
508 | |||
509 | // Place the provided symbols onto the puzzle. symbols - a structure describing | ||
510 | // types and amounts of symbols to add. | ||
511 | bool Generate::place_all_symbols(PuzzleSymbols& symbols) { | ||
512 | std::vector<int> eraseSymbols; | ||
513 | std::vector<int> eraserColors; | ||
514 | // If erasers are present, choose symbols to be erased and remove them | ||
515 | // pre-emptively | ||
516 | for (std::pair<int, int> s : symbols[Decoration::Eraser]) { | ||
517 | for (int i = 0; i < s.second; i++) { | ||
518 | eraserColors.push_back(s.first & 0xf); | ||
519 | eraseSymbols.push_back(hasFlag(Config::FalseParity) | ||
520 | ? Decoration::Dot_Intersection | ||
521 | : symbols.popRandomSymbol()); | ||
522 | } | ||
523 | } | ||
524 | |||
525 | // Symbols are placed in stages according to their type | ||
526 | // In each of these loops, s.first is the symbol and s.second is the amount of | ||
527 | // it to add | ||
528 | |||
529 | _SHAPEDIRECTIONS = | ||
530 | (hasFlag(Config::DisconnectShapes) ? _DISCONNECT : _DIRECTIONS2); | ||
531 | int numShapes = 0, numRotate = 0, numNegative = 0; | ||
532 | std::vector<int> colors, negativeColors; | ||
533 | for (std::pair<int, int> s : symbols[Decoration::Poly]) { | ||
534 | for (int i = 0; i < s.second; i++) { | ||
535 | if (s.first & Decoration::Can_Rotate) numRotate++; | ||
536 | if (s.first & Decoration::Negative) { | ||
537 | numNegative++; | ||
538 | negativeColors.push_back(s.first & 0xf); | ||
539 | } else { | ||
540 | numShapes++; | ||
541 | colors.push_back(s.first & 0xf); | ||
542 | } | ||
543 | } | ||
544 | } | ||
545 | if (numShapes > 0 && !place_shapes(colors, negativeColors, numShapes, | ||
546 | numRotate, numNegative) || | ||
547 | numShapes == 0 && numNegative > 0) | ||
548 | return false; | ||
549 | |||
550 | _stoneTypes = static_cast<int>(symbols[Decoration::Stone].size()); | ||
551 | _bisect = true; // This flag helps the generator prevent making two adjacent | ||
552 | // regions of stones the same color | ||
553 | for (std::pair<int, int> s : symbols[Decoration::Stone]) | ||
554 | if (!place_stones(s.first & 0xf, s.second)) return false; | ||
555 | for (std::pair<int, int> s : symbols[Decoration::Triangle]) | ||
556 | if (!place_triangles(s.first & 0xf, s.second, s.first >> 16)) return false; | ||
557 | for (std::pair<int, int> s : symbols[Decoration::Arrow]) | ||
558 | if (!place_arrows(s.first & 0xf, s.second, s.first >> 12)) return false; | ||
559 | for (std::pair<int, int> s : symbols[Decoration::Star]) | ||
560 | if (!place_stars(s.first & 0xf, s.second)) return false; | ||
561 | if (symbols.style == Panel::Style::HAS_STARS && | ||
562 | hasFlag(Generate::Config::TreehouseLayout) && !checkStarZigzag()) | ||
563 | return false; | ||
564 | if (eraserColors.size() > 0 && !place_erasers(eraserColors, eraseSymbols)) | ||
565 | return false; | ||
566 | for (std::pair<int, int> s : symbols[Decoration::Dot]) | ||
567 | if (!place_dots(s.second, static_cast<Decoration::Color>(s.first & 0xf), | ||
568 | (s.first & ~0xf) == Decoration::Dot_Intersection)) | ||
569 | return false; | ||
570 | for (std::pair<int, int> s : symbols[Decoration::Gap]) | ||
571 | if (!place_gaps(s.second)) return false; | ||
572 | return true; | ||
573 | } | ||
574 | |||
575 | // Generate a random path for a puzzle with the provided symbols. | ||
576 | // The path starts at a random start and will not cross through walls or | ||
577 | // symbols. Puzzle symbols are provided because they can influence how long the | ||
578 | // path should be. | ||
579 | bool Generate::generate_path(PuzzleSymbols& symbols) { | ||
580 | clear(); | ||
581 | |||
582 | if (_obstructions.size() > 0) { | ||
583 | std::vector<Point> walls = pick_random(_obstructions); | ||
584 | for (Point p : walls) | ||
585 | if (get(p) == 0) | ||
586 | set(p, p.first % 2 == 0 ? Decoration::Gap_Column : Decoration::Gap_Row); | ||
587 | bool result = | ||
588 | (hasFlag(Config::ShortPath) ? generate_path_length(1) | ||
589 | : _parity != -1 ? generate_longest_path() | ||
590 | : hitPoints.size() > 0 | ||
591 | ? generate_special_path() | ||
592 | : generate_path_length(_panel->get_num_grid_points() * 3 / 4)); | ||
593 | for (Point p : walls) | ||
594 | if (get(p) & Decoration::Gap) set(p, 0); | ||
595 | return result; | ||
596 | } | ||
597 | |||
598 | if (hitPoints.size() > 0) { | ||
599 | return generate_special_path(); | ||
600 | } | ||
601 | |||
602 | if (_parity != -1 || hasFlag(Generate::LongestPath)) { | ||
603 | return generate_longest_path(); | ||
604 | } | ||
605 | |||
606 | if (hasFlag(Config::ShortPath)) return generate_path_length(1); | ||
607 | |||
608 | // The diagonal symmetry puzzles have a lot of points that can't be hit, so I | ||
609 | // have to reduce the path length | ||
610 | if (_panel->symmetry == Panel::Symmetry::FlipXY || | ||
611 | _panel->symmetry == Panel::Symmetry::FlipNegXY) { | ||
612 | return generate_path_length(_panel->get_num_grid_points() * 3 / 4 - | ||
613 | _panel->width() / 2); | ||
614 | } | ||
615 | |||
616 | // Dot puzzles have a longer path by default. Vertical/horizontal symmetry | ||
617 | // puzzles are also longer because they tend to be too simple otherwise | ||
618 | if (hasFlag(Config::LongPath) || | ||
619 | symbols.style == Panel::Style::HAS_DOTS && | ||
620 | !hasFlag(Config::PreserveStructure) && | ||
621 | !(_panel->symmetry == Panel::Symmetry::Vertical && | ||
622 | (_panel->width() / 2) % 2 == 0 || | ||
623 | _panel->symmetry == Panel::Symmetry::Horizontal && | ||
624 | (_panel->height() / 2) % 2 == 0)) { | ||
625 | return generate_path_length(_panel->get_num_grid_points() * 7 / 8); | ||
626 | } | ||
627 | |||
628 | // For stone puzzles, the path must have a certain number of regions | ||
629 | if (symbols.style == Panel::Style::HAS_STONES && _splitPoints.size() == 0) | ||
630 | return generate_path_regions( | ||
631 | std::min(symbols.getNum(Decoration::Stone), | ||
632 | (_panel->width() / 2 + _panel->height() / 2) / 2 + 1)); | ||
633 | |||
634 | if (symbols.style == Panel::Style::HAS_SHAPERS) { | ||
635 | if (hasFlag(Config::SplitShapes)) { | ||
636 | return generate_path_regions(symbols.getNum(Decoration::Poly) + 1); | ||
637 | } | ||
638 | return generate_path_length(_panel->get_num_grid_points() / 2); | ||
639 | } | ||
640 | |||
641 | return generate_path_length(_panel->get_num_grid_points() * 3 / 4); | ||
642 | } | ||
643 | |||
644 | // Generate a random path with the provided minimum length. | ||
645 | bool Generate::generate_path_length(int minLength, int maxLength) { | ||
646 | int fails = 0; | ||
647 | Point pos = adjust_point(pick_random(_starts)); | ||
648 | Point exit = adjust_point(pick_random(_exits)); | ||
649 | if (off_edge(pos) || off_edge(exit)) return false; | ||
650 | set_path(pos); | ||
651 | while (pos != exit) { | ||
652 | if (fails++ > 20) return false; | ||
653 | Point dir = pick_random(_DIRECTIONS2); | ||
654 | Point newPos = pos + dir; | ||
655 | Point directed = pos + dir / 2; | ||
656 | if (off_edge(newPos) || hasSymbolOrPath(newPos) || | ||
657 | hasSymbolOrPath(directed) || | ||
658 | newPos == exit && _path.size() / 2 + 2 < minLength) | ||
659 | continue; | ||
660 | if (_panel->symmetry && | ||
661 | (off_edge(get_sym_point(newPos)) || newPos == get_sym_point(newPos))) | ||
662 | continue; | ||
663 | set_path(newPos); | ||
664 | set_path(pos + dir / 2); | ||
665 | pos = newPos; | ||
666 | fails = 0; | ||
667 | } | ||
668 | return _path.size() / 2 + 1 >= minLength && _path.size() / 2 + 1 <= maxLength; | ||
669 | } | ||
670 | |||
671 | // Generate a path with the provided number of regions. | ||
672 | bool Generate::generate_path_regions(int minRegions) { | ||
673 | int fails = 0; | ||
674 | int regions = 1; | ||
675 | Point pos = adjust_point(pick_random(_starts)); | ||
676 | Point exit = adjust_point(pick_random(_exits)); | ||
677 | if (off_edge(pos) || off_edge(exit)) return false; | ||
678 | set_path(pos); | ||
679 | while (pos != exit) { | ||
680 | if (fails++ > 20) return false; | ||
681 | Point dir = pick_random(_DIRECTIONS2); | ||
682 | Point newPos = pos + dir; | ||
683 | Point directed = pos + dir / 2; | ||
684 | if (off_edge(newPos) || hasSymbolOrPath(newPos) || | ||
685 | hasSymbolOrPath(directed) || newPos == exit && regions < minRegions) | ||
686 | continue; | ||
687 | if (_panel->symmetry && | ||
688 | (off_edge(get_sym_point(newPos)) || newPos == get_sym_point(newPos))) | ||
689 | continue; | ||
690 | set_path(newPos); | ||
691 | set_path(pos + dir / 2); | ||
692 | if (!on_edge(newPos) && on_edge(pos)) { | ||
693 | regions++; | ||
694 | if (_panel->symmetry) regions++; | ||
695 | } | ||
696 | pos = newPos; | ||
697 | fails = 0; | ||
698 | } | ||
699 | return regions >= minRegions; | ||
700 | } | ||
701 | |||
702 | // Generate a path that covers the maximum number of points. | ||
703 | bool Generate::generate_longest_path() { | ||
704 | Point pos = adjust_point(pick_random(_starts)); | ||
705 | Point exit = adjust_point(pick_random(_exits)); | ||
706 | if (off_edge(pos) || off_edge(exit)) return false; | ||
707 | Point block(-10, -10); | ||
708 | if (hasFlag(Config::FalseParity)) { // If false parity, one dot must be left | ||
709 | // uncovered | ||
710 | if (get_parity(pos + exit) == _panel->get_parity()) return false; | ||
711 | block = Point(Random::rand() % (_panel->width() / 2 + 1) * 2, | ||
712 | Random::rand() % (_panel->height() / 2 + 1) * 2); | ||
713 | while (pos == block || exit == block) { | ||
714 | block = Point(Random::rand() % (_panel->width() / 2 + 1) * 2, | ||
715 | Random::rand() % (_panel->height() / 2 + 1) * 2); | ||
716 | } | ||
717 | set_path(block); | ||
718 | } else if (get_parity(pos + exit) != _panel->get_parity()) | ||
719 | return false; | ||
720 | int fails = 0; | ||
721 | int reqLength = | ||
722 | _panel->get_num_grid_points() + static_cast<int>(_path.size()) / 2; | ||
723 | bool centerFlag = !on_edge(pos); | ||
724 | set_path(pos); | ||
725 | while (pos != exit && !(_panel->symmetry && get_sym_point(pos) == exit)) { | ||
726 | std::vector<std::string> solution; // For debugging only | ||
727 | for (int y = 0; y < _panel->height(); y++) { | ||
728 | std::string row; | ||
729 | for (int x = 0; x < _panel->width(); x++) { | ||
730 | if (get(x, y) == PATH) { | ||
731 | row += "xx"; | ||
732 | } else | ||
733 | row += " "; | ||
734 | } | ||
735 | solution.push_back(row); | ||
736 | } | ||
737 | if (fails++ > 20) return false; | ||
738 | Point dir = pick_random(_DIRECTIONS2); | ||
739 | for (Point checkDir : _DIRECTIONS2) { | ||
740 | Point check = pos + checkDir; | ||
741 | if (off_edge(check) || hasSymbolOrPath(check)) continue; | ||
742 | if (check == exit) continue; | ||
743 | int open = 0; | ||
744 | for (Point checkDir2 : _DIRECTIONS2) { | ||
745 | Point added = check + checkDir2; | ||
746 | if (!off_edge(added) && !hasSymbolOrPath(added)) { | ||
747 | if (++open >= 2) break; | ||
748 | } | ||
749 | } | ||
750 | if (open < 2) { | ||
751 | dir = checkDir; | ||
752 | break; | ||
753 | } | ||
754 | } | ||
755 | Point newPos = pos + dir; | ||
756 | Point directed = pos + dir / 2; | ||
757 | // Various checks to see if going this direction will lead to any issues | ||
758 | if (off_edge(newPos) || hasSymbolOrPath(newPos) || | ||
759 | hasSymbolOrPath(directed) || | ||
760 | newPos == exit && _path.size() / 2 + 3 < reqLength || | ||
761 | _panel->symmetry && get_sym_point(newPos) == exit && | ||
762 | _path.size() / 2 + 3 < reqLength) | ||
763 | continue; | ||
764 | if (_panel->symmetry && | ||
765 | (off_edge(get_sym_point(newPos)) || newPos == get_sym_point(newPos))) | ||
766 | continue; | ||
767 | Point added = newPos + dir; | ||
768 | if (on_edge(newPos) && _panel->symmetry != Panel::Symmetry::Horizontal && | ||
769 | added != block && (off_edge(added) || hasSymbolOrPath(added))) { | ||
770 | if (centerFlag && off_edge(added)) { | ||
771 | centerFlag = false; | ||
772 | } else { | ||
773 | int open = 0; | ||
774 | for (Point checkDir : _DIRECTIONS2) { | ||
775 | Point extorted = newPos + checkDir; | ||
776 | if (!off_edge(extorted) && !hasSymbolOrPath(extorted)) { | ||
777 | if (++open >= 2) break; | ||
778 | } | ||
779 | } | ||
780 | if (open >= 2) continue; | ||
781 | } | ||
782 | } | ||
783 | set_path(newPos); | ||
784 | set_path(pos + dir / 2); | ||
785 | pos = newPos; | ||
786 | fails = 0; | ||
787 | } | ||
788 | if (!off_edge(block)) // Uncover the one dot for false parity | ||
789 | set(block, 0); | ||
790 | return _path.size() / 2 + 1 == reqLength; | ||
791 | } | ||
792 | |||
793 | // Generate path that passes through all of the hitPoints in order | ||
794 | bool Generate::generate_special_path() { | ||
795 | Point pos = adjust_point(pick_random(_starts)); | ||
796 | Point exit = adjust_point(pick_random(_exits)); | ||
797 | if (off_edge(pos) || off_edge(exit)) return false; | ||
798 | set_path(pos); | ||
799 | for (Point p : hitPoints) { | ||
800 | set(p, PATH); | ||
801 | } | ||
802 | int hitIndex = 0; | ||
803 | int minLength = _panel->get_num_grid_points() * 3 / 4; | ||
804 | while (pos != exit) { | ||
805 | std::vector<Point> validDir; | ||
806 | for (Point dir : _DIRECTIONS2) { | ||
807 | Point newPos = pos + dir; | ||
808 | if (off_edge(newPos)) continue; | ||
809 | Point connectPos = pos + dir / 2; | ||
810 | // Go through the hit point if passing next to it | ||
811 | if (get(connectPos) == PATH && hitIndex < hitPoints.size() && | ||
812 | connectPos == hitPoints[hitIndex]) { | ||
813 | validDir = {dir}; | ||
814 | hitIndex++; | ||
815 | break; | ||
816 | } | ||
817 | if (hasSymbolOrPath(newPos) || hasSymbolOrPath(connectPos) || | ||
818 | newPos == exit && (hitIndex != hitPoints.size() || | ||
819 | _path.size() / 2 + 2 < minLength)) | ||
820 | continue; | ||
821 | if (_panel->symmetry && newPos == get_sym_point(newPos)) continue; | ||
822 | bool fail = false; | ||
823 | for (Point dir : _DIRECTIONS1) { | ||
824 | Point added = newPos + dir; | ||
825 | if (!off_edge(added) && get(added) == PATH && | ||
826 | newPos + dir != hitPoints[hitIndex]) { | ||
827 | fail = true; | ||
828 | break; | ||
829 | } | ||
830 | } | ||
831 | if (fail) continue; | ||
832 | validDir.push_back(dir); | ||
833 | } | ||
834 | if (validDir.size() == 0) return false; | ||
835 | Point dir = pick_random(validDir); | ||
836 | set_path(pos + dir); | ||
837 | set_path(pos + dir / 2); | ||
838 | pos = pos + dir; | ||
839 | } | ||
840 | return hitIndex == hitPoints.size() && _path.size() >= minLength; | ||
841 | } | ||
842 | |||
843 | // Eerase the path from the puzzle grid | ||
844 | void Generate::erase_path() { | ||
845 | /*_drawnPath.clear(); | ||
846 | _drawnPath.resize(_height); | ||
847 | for (auto& row : _drawnPath) row.resize(_width);*/ | ||
848 | for (int x = 0; x < _panel->width(); x++) { | ||
849 | for (int y = 0; y < _panel->height(); y++) { | ||
850 | if (get(x, y) == PATH) set(x, y, 0); | ||
851 | } | ||
852 | } | ||
853 | } | ||
854 | |||
855 | // If a point is on an edge, bump it randomly to an adjacent vertex. Otherwise, | ||
856 | // the point is untouched | ||
857 | Point Generate::adjust_point(Point pos) { | ||
858 | if (pos.first % 2 != 0) { | ||
859 | if (hasSymbolOrPath(pos)) return {-10, -10}; | ||
860 | set_path(pos); | ||
861 | return Point(pos.first - 1 + Random::rand() % 2 * 2, pos.second); | ||
862 | } | ||
863 | if (pos.second % 2 != 0) { | ||
864 | if (hasSymbolOrPath(pos)) return {-10, -10}; | ||
865 | set_path(pos); | ||
866 | return Point(pos.first, pos.second - 1 + Random::rand() % 2 * 2); | ||
867 | } | ||
868 | if (_panel->symmetry && _exits.count(pos) && | ||
869 | !_exits.count(get_sym_point(pos))) | ||
870 | return {-10, -10}; | ||
871 | return pos; | ||
872 | } | ||
873 | |||
874 | // Get the set of points in region containing the point (pos) | ||
875 | std::set<Point> Generate::get_region(Point pos) { | ||
876 | std::set<Point> region; | ||
877 | std::vector<Point> check; | ||
878 | check.push_back(pos); | ||
879 | region.insert(pos); | ||
880 | while (check.size() > 0) { | ||
881 | Point p = check[check.size() - 1]; | ||
882 | check.pop_back(); | ||
883 | for (Point dir : _DIRECTIONS1) { | ||
884 | Point p1 = p + dir; | ||
885 | if (on_edge(p1)) continue; | ||
886 | if (get(p1) == PATH || get(p1) == OPEN) continue; | ||
887 | Point p2 = p + dir * 2; | ||
888 | if ((get(p2) & Decoration::Empty) == Decoration::Empty) continue; | ||
889 | if (region.insert(p2).second) { | ||
890 | check.push_back(p2); | ||
891 | } | ||
892 | } | ||
893 | } | ||
894 | return region; | ||
895 | } | ||
896 | |||
897 | // Get all the symbols in the region containing including the point (pos) | ||
898 | std::vector<int> Generate::get_symbols_in_region(Point pos) { | ||
899 | return get_symbols_in_region(get_region(pos)); | ||
900 | } | ||
901 | |||
902 | // Get all the symbols in the given region | ||
903 | std::vector<int> Generate::get_symbols_in_region( | ||
904 | const std::set<Point>& region) { | ||
905 | std::vector<int> symbols; | ||
906 | for (Point p : region) { | ||
907 | if (get(p)) symbols.push_back(get(p)); | ||
908 | } | ||
909 | return symbols; | ||
910 | } | ||
911 | |||
912 | // Place a start point in a random location | ||
913 | bool Generate::place_start(int amount) { | ||
914 | _starts.clear(); | ||
915 | //_panel->_startpoints.clear(); | ||
916 | while (amount > 0) { | ||
917 | Point pos = Point(Random::rand() % (_panel->width() / 2 + 1) * 2, | ||
918 | Random::rand() % (_panel->height() / 2 + 1) * 2); | ||
919 | if (hasFlag(Config::StartEdgeOnly)) switch (Random::rand() % 4) { | ||
920 | case 0: | ||
921 | pos.first = 0; | ||
922 | break; | ||
923 | case 1: | ||
924 | pos.second = 0; | ||
925 | break; | ||
926 | case 2: | ||
927 | pos.first = _panel->width() - 1; | ||
928 | break; | ||
929 | case 3: | ||
930 | pos.second = _panel->height() - 1; | ||
931 | break; | ||
932 | } | ||
933 | if (_parity != -1 && get_parity(pos) != (amount == 1 ? _parity : !_parity)) | ||
934 | continue; | ||
935 | if (_starts.count(pos) || _exits.count(pos)) continue; | ||
936 | if (_panel->symmetry && pos == get_sym_point(pos)) continue; | ||
937 | // Highly discourage putting start points adjacent | ||
938 | bool adjacent = false; | ||
939 | for (Point dir : _DIRECTIONS2) { | ||
940 | if (!off_edge(pos + dir) && get(pos + dir) == Decoration::Start) { | ||
941 | adjacent = true; | ||
942 | break; | ||
943 | } | ||
944 | } | ||
945 | if (adjacent && Random::rand() % 10 > 0) continue; | ||
946 | _starts.insert(pos); | ||
947 | set(pos.first, pos.second, Decoration::Start | Decoration::Color::None); | ||
948 | amount--; | ||
949 | if (_panel->symmetry) { | ||
950 | Point sp = get_sym_point(pos); | ||
951 | _starts.insert(sp); | ||
952 | set(sp.first, sp.second, Decoration::Start | Decoration::Color::None); | ||
953 | } | ||
954 | } | ||
955 | return true; | ||
956 | } | ||
957 | |||
958 | // Place an exit point in a random location on the edge of the grid | ||
959 | bool Generate::place_exit(int amount) { | ||
960 | _exits.clear(); | ||
961 | //_panel->_endpoints.clear(); | ||
962 | while (amount > 0) { | ||
963 | Point pos = Point(Random::rand() % (_panel->width() / 2 + 1) * 2, | ||
964 | Random::rand() % (_panel->height() / 2 + 1) * 2); | ||
965 | switch (Random::rand() % 4) { | ||
966 | case 0: | ||
967 | pos.first = 0; | ||
968 | break; | ||
969 | case 1: | ||
970 | pos.second = 0; | ||
971 | break; | ||
972 | case 2: | ||
973 | pos.first = _panel->width() - 1; | ||
974 | break; | ||
975 | case 3: | ||
976 | pos.second = _panel->height() - 1; | ||
977 | break; | ||
978 | } | ||
979 | if (_parity != -1 && (get_parity(pos) + _panel->get_parity()) % 2 != | ||
980 | (amount == 1 ? _parity : !_parity)) | ||
981 | continue; | ||
982 | if (_starts.count(pos) || _exits.count(pos)) continue; | ||
983 | if (_panel->symmetry && pos == get_sym_point(pos)) continue; | ||
984 | if (_panel->symmetry && get_sym_point(pos).first != 0 && | ||
985 | get_sym_point(pos).second != 0) | ||
986 | continue; | ||
987 | // Prevent putting exit points adjacent | ||
988 | bool adjacent = false; | ||
989 | for (Point dir : _8DIRECTIONS2) { | ||
990 | if (!off_edge(pos + dir) && get(pos + dir) == Decoration::Exit) { | ||
991 | adjacent = true; | ||
992 | break; | ||
993 | } | ||
994 | } | ||
995 | if (adjacent) continue; | ||
996 | _exits.insert(pos); | ||
997 | set(pos.first, pos.second, Decoration::Exit | Decoration::Color::None); | ||
998 | amount--; | ||
999 | if (_panel->symmetry) { | ||
1000 | Point sp = get_sym_point(pos); | ||
1001 | _exits.insert(sp); | ||
1002 | set(sp.first, sp.second, Decoration::Exit | Decoration::Color::None); | ||
1003 | } | ||
1004 | } | ||
1005 | return true; | ||
1006 | } | ||
1007 | |||
1008 | // Check if a gap can be placed at pos. | ||
1009 | bool Generate::can_place_gap(Point pos) { | ||
1010 | // Prevent putting open gaps at edges of the puzzle | ||
1011 | if (pos.first == 0 || pos.second == 0) { | ||
1012 | if (hasFlag(Config::FullGaps)) return false; | ||
1013 | } else if (Random::rand() % 2 == 0) | ||
1014 | return false; // Encourages gaps on outside border | ||
1015 | // Prevent putting a gap on top of a start/end point | ||
1016 | if (_starts.count(pos) || _exits.count(pos)) return false; | ||
1017 | // For symmetry puzzles, prevent putting two gaps symmetrically opposite | ||
1018 | if (_panel->symmetry && (get_sym_point(pos) == pos) || | ||
1019 | (get(get_sym_point(pos)) & Decoration::Gap)) | ||
1020 | return false; | ||
1021 | if ((_panel->symmetry == Panel::Symmetry::ParallelH || | ||
1022 | _panel->symmetry == Panel::Symmetry::ParallelHFlip) && | ||
1023 | pos.second == _panel->height() / 2) | ||
1024 | return false; | ||
1025 | if ((_panel->symmetry == Panel::Symmetry::ParallelV || | ||
1026 | _panel->symmetry == Panel::Symmetry::ParallelVFlip) && | ||
1027 | pos.first == _panel->width() / 2) | ||
1028 | return false; | ||
1029 | if (_panel->symmetry == Panel::Symmetry::FlipNegXY && | ||
1030 | (pos.first + pos.second == _width - 1 || | ||
1031 | pos.first + pos.second == _width + 1)) | ||
1032 | return false; | ||
1033 | if (_panel->symmetry == Panel::Symmetry::FlipXY && | ||
1034 | (pos.first - pos.second == 1 || pos.first - pos.second == -1)) | ||
1035 | return false; | ||
1036 | if (hasFlag(Config::FullGaps)) { // Prevent forming dead ends with open gaps | ||
1037 | std::vector<Point> checkPoints = | ||
1038 | (pos.first % 2 == 0 | ||
1039 | ? std::vector<Point>({Point(pos.first, pos.second - 1), | ||
1040 | Point(pos.first, pos.second + 1)}) | ||
1041 | : std::vector<Point>({Point(pos.first - 1, pos.second), | ||
1042 | Point(pos.first + 1, pos.second)})); | ||
1043 | for (Point check : checkPoints) { | ||
1044 | int valid = 4; | ||
1045 | for (Point dir : _DIRECTIONS1) { | ||
1046 | Point p = check + dir; | ||
1047 | if (off_edge(p) || get(p) & GAP || get(p) == OPEN) { | ||
1048 | if (--valid <= 2) { | ||
1049 | return false; | ||
1050 | } | ||
1051 | } | ||
1052 | } | ||
1053 | } | ||
1054 | } | ||
1055 | return true; | ||
1056 | } | ||
1057 | |||
1058 | // Place the given amount of gaps radomly around the puzzle | ||
1059 | bool Generate::place_gaps(int amount) { | ||
1060 | std::set<Point> open; | ||
1061 | for (int y = 0; y < _panel->height(); y++) { | ||
1062 | for (int x = (y + 1) % 2; x < _panel->width(); x += 2) { | ||
1063 | if (get(x, y) == 0 && (!_fullGaps || !on_edge(Point(x, y)))) { | ||
1064 | open.emplace(Point(x, y)); | ||
1065 | } | ||
1066 | } | ||
1067 | } | ||
1068 | |||
1069 | while (amount > 0) { | ||
1070 | if (open.size() == 0) return false; | ||
1071 | Point pos = pick_random(open); | ||
1072 | if (can_place_gap(pos)) { | ||
1073 | set(pos, _fullGaps ? static_cast<Decoration::Shape>(OPEN) | ||
1074 | : pos.first % 2 == 0 ? Decoration::Gap_Column | ||
1075 | : Decoration::Gap_Row); | ||
1076 | amount--; | ||
1077 | } | ||
1078 | open.erase(pos); | ||
1079 | } | ||
1080 | return true; | ||
1081 | } | ||
1082 | |||
1083 | // Check if a dot can be placed at pos. | ||
1084 | bool Generate::can_place_dot(Point pos, bool intersectionOnly) { | ||
1085 | if (get(pos) & DOT) return false; | ||
1086 | if (_panel->symmetry) { | ||
1087 | // For symmetry puzzles, make sure the current pos and symmetric pos are | ||
1088 | // both valid | ||
1089 | Point symPos = get_sym_point(pos); | ||
1090 | if (symPos == pos) return false; | ||
1091 | Panel::Symmetry backupSym = _panel->symmetry; | ||
1092 | _panel->symmetry = Panel::Symmetry::None; // To prevent endless recursion | ||
1093 | // if (!can_place_dot(get_sym_point(pos))) { | ||
1094 | if (!can_place_dot(symPos, intersectionOnly)) { | ||
1095 | _panel->symmetry = backupSym; | ||
1096 | return false; | ||
1097 | } | ||
1098 | _panel->symmetry = backupSym; | ||
1099 | } | ||
1100 | if (_panel->symmetry == Panel::Symmetry::RotateLeft && _path1.count(pos) && | ||
1101 | _path2.count(pos)) | ||
1102 | return false; // Prevent sharing of dots between symmetry lines | ||
1103 | if (hasFlag(Config::DisableDotIntersection)) return true; | ||
1104 | for (Point dir : _8DIRECTIONS1) { | ||
1105 | Point p = pos + dir; | ||
1106 | if (!off_edge(p) && (get(p) & DOT)) { | ||
1107 | // Don't allow adjacent dots | ||
1108 | if (dir.first == 0 || dir.second == 0) return false; | ||
1109 | // Allow diagonally adjacent placement some of the time | ||
1110 | if (Random::rand() % 2 > 0) return false; | ||
1111 | } | ||
1112 | } | ||
1113 | // Allow 2-space horizontal/vertical placement some of the time | ||
1114 | if (Random::rand() % (intersectionOnly ? 10 : 5) > 0) { | ||
1115 | for (Point dir : _DIRECTIONS2) { | ||
1116 | Point p = pos + dir; | ||
1117 | if (!off_edge(p) && (get(p) & DOT)) { | ||
1118 | return false; | ||
1119 | } | ||
1120 | } | ||
1121 | } | ||
1122 | return true; | ||
1123 | } | ||
1124 | |||
1125 | // Place the given amount of dots at random points on the path | ||
1126 | bool Generate::place_dots(int amount, Decoration::Color color, | ||
1127 | bool intersectionOnly) { | ||
1128 | if (_parity != -1) { // For full dot puzzles, don't put dots on the starts | ||
1129 | // and exits unless there are multiple | ||
1130 | for (int x = 0; x < _panel->width(); x += 2) { | ||
1131 | for (int y = 0; y < _panel->height(); y += 2) { | ||
1132 | if (_starts.size() == 1 && _starts.count(Point(x, y))) continue; | ||
1133 | if (_exits.size() == 1 && _exits.count(Point(x, y))) continue; | ||
1134 | if (!hasSymbolOrPath(x, y)) continue; | ||
1135 | set(x, y, Decoration::Dot_Intersection); | ||
1136 | } | ||
1137 | } | ||
1138 | amount -= _panel->get_num_grid_points(); | ||
1139 | if (amount <= 0) return true; | ||
1140 | intersectionOnly = false; | ||
1141 | setFlagOnce(Config::DisableDotIntersection); | ||
1142 | } | ||
1143 | |||
1144 | /*if (color == Decoration::Color::Blue || color == Decoration::Color::Cyan) | ||
1145 | color = IntersectionFlags::DOT_IS_BLUE; | ||
1146 | else if (color == Decoration::Color::Yellow || | ||
1147 | color == Decoration::Color::Orange) | ||
1148 | color = IntersectionFlags::DOT_IS_ORANGE; | ||
1149 | else | ||
1150 | color = 0;*/ | ||
1151 | |||
1152 | std::set<Point> open = | ||
1153 | (color == 0 ? _path | ||
1154 | : (color == Decoration::Color::Blue || color == Decoration::Color::Cyan) | ||
1155 | ? _path1 | ||
1156 | : _path2); | ||
1157 | for (Point p : _starts) open.erase(p); | ||
1158 | for (Point p : _exits) open.erase(p); | ||
1159 | for (Point p : blockPos) open.erase(p); | ||
1160 | if (intersectionOnly) { | ||
1161 | std::set<Point> intersections; | ||
1162 | for (Point p : open) { | ||
1163 | if (p.first % 2 == 0 && p.second % 2 == 0) intersections.insert(p); | ||
1164 | } | ||
1165 | open = intersections; | ||
1166 | } | ||
1167 | if (hasFlag(Config::DisableDotIntersection)) { | ||
1168 | std::set<Point> intersections; | ||
1169 | for (Point p : open) { | ||
1170 | if (p.first % 2 != 0 || p.second % 2 != 0) intersections.insert(p); | ||
1171 | } | ||
1172 | open = intersections; | ||
1173 | } | ||
1174 | |||
1175 | while (amount > 0) { | ||
1176 | if (open.size() == 0) return false; | ||
1177 | Point pos = pick_random(open); | ||
1178 | open.erase(pos); | ||
1179 | if (!can_place_dot(pos, intersectionOnly)) continue; | ||
1180 | Decoration::Shape symbol = (pos.first & 1) == 1 ? Decoration::Dot_Row | ||
1181 | : (pos.second & 1) == 1 | ||
1182 | ? Decoration::Dot_Column | ||
1183 | : Decoration::Dot_Intersection; | ||
1184 | set(pos, symbol | color); | ||
1185 | for (Point dir : _DIRECTIONS1) { | ||
1186 | open.erase(pos + dir); | ||
1187 | } // If symmetry, set a flag to break the point symmetric to the dot | ||
1188 | if (_panel->symmetry) { | ||
1189 | Point sp = get_sym_point(pos); | ||
1190 | symbol = (sp.first & 1) == 1 ? Decoration::Dot_Row | ||
1191 | : (sp.second & 1) == 1 ? Decoration::Dot_Column | ||
1192 | : Decoration::Dot_Intersection; | ||
1193 | if (symbol != Decoration::Dot_Intersection) | ||
1194 | set(sp, symbol & ~Decoration::Dot); | ||
1195 | open.erase(sp); | ||
1196 | for (Point dir : _DIRECTIONS1) { | ||
1197 | open.erase(sp + dir); | ||
1198 | } | ||
1199 | } | ||
1200 | amount--; | ||
1201 | } | ||
1202 | return true; | ||
1203 | } | ||
1204 | |||
1205 | // Check if a stone can be placed at pos. | ||
1206 | bool Generate::can_place_stone(const std::set<Point>& region, int color) { | ||
1207 | for (Point p : region) { | ||
1208 | int sym = get(p); | ||
1209 | if (get_symbol_type(sym) == Decoration::Stone) return (sym & 0xf) == color; | ||
1210 | } | ||
1211 | return true; | ||
1212 | } | ||
1213 | |||
1214 | // Place the given amount of stones with the given color | ||
1215 | bool Generate::place_stones(int color, int amount) { | ||
1216 | std::set<Point> open = _openpos; | ||
1217 | std::set<Point> | ||
1218 | open2; // Used to store open points removed from the first pass, to make | ||
1219 | // sure a stone is put in every non-adjacent region | ||
1220 | int passCount = 0; | ||
1221 | int originalAmount = amount; | ||
1222 | while (amount > 0) { | ||
1223 | if (open.size() == 0) { | ||
1224 | // Make sure there is room for the remaining stones and enough partitions | ||
1225 | // have been made (based on the grid size) | ||
1226 | if (open2.size() < amount || | ||
1227 | _bisect && | ||
1228 | passCount < std::min(originalAmount, (_panel->width() / 2 + | ||
1229 | _panel->height() / 2 + 2) / | ||
1230 | 4)) | ||
1231 | return false; | ||
1232 | // Put remaining stones wherever they will fit | ||
1233 | Point pos = pick_random(open2); | ||
1234 | set(pos, Decoration::Stone | color); | ||
1235 | _openpos.erase(pos); | ||
1236 | open2.erase(pos); | ||
1237 | amount--; | ||
1238 | continue; | ||
1239 | } | ||
1240 | Point pos = pick_random(open); | ||
1241 | std::set<Point> region = get_region(pos); | ||
1242 | if (!can_place_stone(region, color)) { | ||
1243 | for (Point p : region) { | ||
1244 | open.erase(p); | ||
1245 | } | ||
1246 | continue; | ||
1247 | } | ||
1248 | if (_stoneTypes > 2) { // If more than two colors, group stones together, | ||
1249 | // otherwise it takes too long to generate. | ||
1250 | open.clear(); | ||
1251 | for (Point p : region) { | ||
1252 | if (_openpos.count(p)) open.insert(p); | ||
1253 | } | ||
1254 | } | ||
1255 | open.erase(pos); | ||
1256 | if (_panel->symmetry) { | ||
1257 | open.erase(get_sym_point(pos)); | ||
1258 | } | ||
1259 | if (_stoneTypes == 2) { | ||
1260 | for (Point p : region) { | ||
1261 | if (open.erase(p)) open2.insert(p); | ||
1262 | } // Remove adjacent regions from the open list | ||
1263 | for (Point p : region) { | ||
1264 | for (Point dir : _8DIRECTIONS2) { | ||
1265 | Point pos2 = p + dir; | ||
1266 | if (open.count(pos2) && !region.count(pos2)) { | ||
1267 | for (Point P : get_region(pos2)) { | ||
1268 | open.erase(P); | ||
1269 | } | ||
1270 | } | ||
1271 | } | ||
1272 | } | ||
1273 | } | ||
1274 | set(pos, Decoration::Stone | color); | ||
1275 | _openpos.erase(pos); | ||
1276 | amount--; | ||
1277 | passCount++; | ||
1278 | } | ||
1279 | _bisect = false; // After placing one color, adjacent regions are allowed | ||
1280 | _stoneTypes--; | ||
1281 | return true; | ||
1282 | } | ||
1283 | |||
1284 | // Generate a random shape. region - the region of points to choose from; points | ||
1285 | // chosen will be removed. bufferRegion - points that may be chosen twice due to | ||
1286 | // overlapping shapes; points will be removed from here before points in region. | ||
1287 | // maxSize - the maximum size of the generated shape. Whether the points can be | ||
1288 | // contiguous or not is determined by local variable _SHAPEDIRECTIONS | ||
1289 | Shape Generate::generate_shape(std::set<Point>& region, | ||
1290 | std::set<Point>& bufferRegion, Point pos, | ||
1291 | int maxSize) { | ||
1292 | Shape shape; | ||
1293 | shape.insert(pos); | ||
1294 | if (!bufferRegion.erase(pos)) region.erase(pos); | ||
1295 | while (shape.size() < maxSize && region.size() > 0) { | ||
1296 | pos = pick_random(shape); | ||
1297 | int i = 0; | ||
1298 | for (; i < 10; i++) { | ||
1299 | Point dir = pick_random(_SHAPEDIRECTIONS); | ||
1300 | Point p = pos + dir; | ||
1301 | if (region.count(p) && !shape.count(p)) { | ||
1302 | shape.insert(p); | ||
1303 | if (!bufferRegion.erase(p)) region.erase(p); | ||
1304 | break; | ||
1305 | } | ||
1306 | } | ||
1307 | if (i == 10) return shape; | ||
1308 | } | ||
1309 | return shape; | ||
1310 | } | ||
1311 | |||
1312 | // Get the integer representing the shape, accounting for whether it is rotated | ||
1313 | // or negative. -1 rotation means a random rotation, depth is for controlling | ||
1314 | // recursion and should be set to 0 | ||
1315 | int Generate::make_shape_symbol(Shape shape, bool rotated, bool negative, | ||
1316 | int rotation, int depth) { | ||
1317 | int symbol = static_cast<int>(Decoration::Poly); | ||
1318 | if (rotated) { | ||
1319 | if (rotation == -1) { | ||
1320 | if (make_shape_symbol(shape, rotated, negative, 0, depth + 1) == | ||
1321 | make_shape_symbol(shape, rotated, negative, 1, depth + 1)) | ||
1322 | return 0; // Check to make sure the shape is not the same when rotated | ||
1323 | rotation = Random::rand() % 4; | ||
1324 | } | ||
1325 | symbol |= Decoration::Can_Rotate; | ||
1326 | Shape newShape; // Rotate shape points according to rotation | ||
1327 | for (Point p : shape) { | ||
1328 | switch (rotation) { | ||
1329 | case 0: | ||
1330 | newShape.insert(p); | ||
1331 | break; | ||
1332 | case 1: | ||
1333 | newShape.emplace(Point(p.second, -p.first)); | ||
1334 | break; | ||
1335 | case 2: | ||
1336 | newShape.emplace(Point(-p.second, p.first)); | ||
1337 | break; | ||
1338 | case 3: | ||
1339 | newShape.emplace(Point(-p.first, -p.second)); | ||
1340 | break; | ||
1341 | } | ||
1342 | } | ||
1343 | shape = newShape; | ||
1344 | } | ||
1345 | if (negative) symbol |= Decoration::Negative; | ||
1346 | int xmin = INT_MAX, xmax = INT_MIN, ymin = INT_MAX, ymax = INT_MIN; | ||
1347 | for (Point p : shape) { | ||
1348 | if (p.first < xmin) xmin = p.first; | ||
1349 | if (p.first > xmax) xmax = p.first; | ||
1350 | if (p.second < ymin) ymin = p.second; | ||
1351 | if (p.second > ymax) ymax = p.second; | ||
1352 | } | ||
1353 | if (xmax - xmin > 6 || | ||
1354 | ymax - ymin > 6) { // Shapes cannot be more than 4 in width and height | ||
1355 | return 0; | ||
1356 | } | ||
1357 | // Translate to the corner and set bit flags (16 bits, 1 where a shape block | ||
1358 | // is present) | ||
1359 | for (Point p : shape) { | ||
1360 | symbol |= (1 << ((p.first - xmin) / 2 + (ymax - p.second) * 2)) << 16; | ||
1361 | } | ||
1362 | if (Random::rand() % 4 > | ||
1363 | 0) { // The generator makes a certain type of symbol way too often (2x2 | ||
1364 | // square with another square attached), this makes it much less | ||
1365 | // frequent | ||
1366 | int type = symbol >> 16; | ||
1367 | if (type == 0x0331 || type == 0x0332 || type == 0x0037 || type == 0x0067 || | ||
1368 | type == 0x0133 || type == 0x0233 || type == 0x0073 || type == 0x0076) | ||
1369 | return 0; | ||
1370 | } | ||
1371 | return symbol; | ||
1372 | } | ||
1373 | |||
1374 | // Place the given amount of shapes with random colors selected from the color | ||
1375 | // vectors. colors - colors for regular shapes, negativeColors - colors for | ||
1376 | // negative shapes, amount - how many normal shapes numRotated - how many | ||
1377 | // rotated shapes, numNegative - how many negative shapes | ||
1378 | bool Generate::place_shapes(const std::vector<int>& colors, | ||
1379 | const std::vector<int>& negativeColors, int amount, | ||
1380 | int numRotated, int numNegative) { | ||
1381 | std::set<Point> open = _openpos; | ||
1382 | int shapeSize = hasFlag(Config::SmallShapes) ? 2 | ||
1383 | : hasFlag(Config::BigShapes) ? amount == 1 ? 8 : 6 | ||
1384 | : 4; | ||
1385 | int targetArea = amount * shapeSize * 7 / | ||
1386 | 8; // Average size must be at least 7/8 of the target size | ||
1387 | if (amount * shapeSize > _panel->get_num_grid_blocks()) | ||
1388 | targetArea = _panel->get_num_grid_blocks(); | ||
1389 | int originalAmount = amount; | ||
1390 | if (hasFlag(Generate::Config::MountainFloorH) && | ||
1391 | _panel->width() == | ||
1392 | 9) { // The 4 small puzzles shape size may vary depending on the path | ||
1393 | targetArea = 0; | ||
1394 | removeFlag(Generate::Config::MountainFloorH); | ||
1395 | } | ||
1396 | int totalArea = 0; | ||
1397 | int minx = _panel->width(), miny = _panel->height(), maxx = 0, maxy = 0; | ||
1398 | int colorIndex = Random::rand() % colors.size(); | ||
1399 | int colorIndexN = Random::rand() % (negativeColors.size() + 1); | ||
1400 | bool shapesCanceled = false, shapesCombined = false, flatShapes = true; | ||
1401 | if (amount == 1) shapesCombined = true; | ||
1402 | while (amount > 0) { | ||
1403 | if (open.size() == 0) return false; | ||
1404 | Point pos = pick_random(open); | ||
1405 | std::set<Point> region = get_region(pos); | ||
1406 | std::set<Point> bufferRegion; | ||
1407 | std::set<Point> open2; // Open points for just that region | ||
1408 | for (Point p : region) { | ||
1409 | if (open.erase(p)) open2.insert(p); | ||
1410 | } | ||
1411 | if (region.size() + totalArea == _panel->get_num_grid_blocks() && | ||
1412 | targetArea != _panel->get_num_grid_blocks()) | ||
1413 | continue; // To prevent shapes from filling every grid point | ||
1414 | std::vector<Shape> shapes; | ||
1415 | std::vector<Shape> shapesN; | ||
1416 | int numShapesN = std::min( | ||
1417 | Random::rand() % (numNegative + 1), | ||
1418 | static_cast<int>(region.size()) / | ||
1419 | 3); // Negative blocks may be at max 1/3 of the regular blocks | ||
1420 | if (amount == 1) numShapesN = numNegative; | ||
1421 | if (numShapesN) { | ||
1422 | std::set<Point> regionN = _gridpos; | ||
1423 | int maxSize = static_cast<int>(region.size()) - | ||
1424 | numShapesN * 3; // Max size of negative shapes | ||
1425 | if (maxSize == 0) maxSize = 1; | ||
1426 | for (int i = 0; i < numShapesN; i++) { | ||
1427 | pos = pick_random(region); | ||
1428 | // Try to pick a random point adjacent to a shape | ||
1429 | for (int i = 0; i < 10; i++) { | ||
1430 | Point p = pos + pick_random(_SHAPEDIRECTIONS); | ||
1431 | if (regionN.count(p) && !region.count(p)) { | ||
1432 | pos = p; | ||
1433 | break; | ||
1434 | } | ||
1435 | } | ||
1436 | if (!regionN.count(pos)) return false; | ||
1437 | Shape shape = generate_shape(regionN, pos, | ||
1438 | std::min(Random::rand() % 3 + 1, maxSize)); | ||
1439 | shapesN.push_back(shape); | ||
1440 | for (Point p : shape) { | ||
1441 | if (region.count(p)) | ||
1442 | bufferRegion.insert( | ||
1443 | p); // Buffer region stores overlap between shapes | ||
1444 | else | ||
1445 | region.insert(p); | ||
1446 | } | ||
1447 | } | ||
1448 | } | ||
1449 | int numShapes = | ||
1450 | static_cast<int>(region.size() + bufferRegion.size()) / | ||
1451 | (shapeSize + 1) + | ||
1452 | 1; // Pick a number of shapes to make. I tried different ones until I | ||
1453 | // found something that made a good variety of shapes | ||
1454 | if (numShapes == 1 && bufferRegion.size() > 0) | ||
1455 | numShapes++; // If there is any overlap, we need at least two shapes | ||
1456 | if (numShapes < amount && region.size() > shapeSize && | ||
1457 | Random::rand() % 2 == 1) | ||
1458 | numShapes++; // Adds more variation to the shape sizes | ||
1459 | if (region.size() <= shapeSize + 1 && bufferRegion.size() == 0 && | ||
1460 | Random::rand() % 2 == 1) | ||
1461 | numShapes = 1; // For more variation, sometimes make a bigger shape than | ||
1462 | // the target if the size is close | ||
1463 | if (hasFlag(Config::MountainFloorH)) { | ||
1464 | if (region.size() < 19) continue; | ||
1465 | numShapes = 6; // The big mountain floor puzzle on hard mode needs | ||
1466 | // additional shapes since some combine | ||
1467 | targetArea = 19; | ||
1468 | } | ||
1469 | if (hasFlag(Config::SplitShapes) && numShapes != 1) continue; | ||
1470 | if (hasFlag(Config::RequireCombineShapes) && numShapes == 1) continue; | ||
1471 | bool balance = false; | ||
1472 | if (numShapes > | ||
1473 | amount // The region is too big for the number of shapes chosen | ||
1474 | ) { | ||
1475 | if (numNegative < 2 || hasFlag(Config::DisableCancelShapes)) continue; | ||
1476 | // Make balancing shapes - Positive and negative will be switched so that | ||
1477 | // code can be reused | ||
1478 | balance = true; | ||
1479 | std::set<Point> regionN = _gridpos; | ||
1480 | numShapes = std::max( | ||
1481 | 2, Random::rand() % numNegative + 1); // Actually the negative shapes | ||
1482 | numShapesN = std::min(amount, 1); // Actually the positive shapes | ||
1483 | if (numShapesN >= numShapes * 3 || numShapesN * 5 <= numShapes) continue; | ||
1484 | shapes.clear(); | ||
1485 | shapesN.clear(); | ||
1486 | region.clear(); | ||
1487 | bufferRegion.clear(); | ||
1488 | for (int i = 0; i < numShapesN; i++) { | ||
1489 | Shape shape = | ||
1490 | generate_shape(regionN, pick_random(regionN), | ||
1491 | std::min(shapeSize + 1, numShapes * 2 / numShapesN + | ||
1492 | Random::rand() % 3 - 1)); | ||
1493 | shapesN.push_back(shape); | ||
1494 | for (Point p : shape) { | ||
1495 | region.insert(p); | ||
1496 | } | ||
1497 | } | ||
1498 | shapesCanceled = true; | ||
1499 | // Let the rest of the algorithm create the cancelling shapes | ||
1500 | } | ||
1501 | if (_panel->symmetry && numShapes == originalAmount && numShapes >= 3 && | ||
1502 | !region.count(Point((_panel->width() / 4) * 2 + 1, | ||
1503 | (_panel->height() / 4) * 2 + 1))) | ||
1504 | continue; // Prevent it from shoving all shapes to one side of symmetry | ||
1505 | if ((_panel->symmetry == Panel::Symmetry::ParallelH || | ||
1506 | _panel->symmetry == Panel::Symmetry::ParallelV || | ||
1507 | _panel->symmetry == Panel::Symmetry::ParallelHFlip || | ||
1508 | _panel->symmetry == Panel::Symmetry::ParallelVFlip) && | ||
1509 | region.count(Point((_panel->width() / 4) * 2 + 1, | ||
1510 | (_panel->height() / 4) * 2 + 1))) | ||
1511 | continue; // Prevent parallel symmetry from making regions through the | ||
1512 | // center line (this tends to make the puzzles way too hard) | ||
1513 | if (!balance && numShapesN && | ||
1514 | (numShapesN > 1 && numRotated > 0 || numShapesN > 2 || | ||
1515 | numShapes + numShapesN > 6)) | ||
1516 | continue; // Trying to prevent the game's shape calculator from lagging | ||
1517 | // too much | ||
1518 | if (!(hasFlag(Config::MountainFloorH) && _panel->width() == 11) && | ||
1519 | open2.size() < numShapes + numShapesN) | ||
1520 | continue; // Not enough space to put the symbols | ||
1521 | if (numShapes == 1) { | ||
1522 | shapes.push_back(region); | ||
1523 | region.clear(); | ||
1524 | } else | ||
1525 | for (; numShapes > 0; numShapes--) { | ||
1526 | if (region.size() == 0) break; | ||
1527 | Shape shape = | ||
1528 | generate_shape(region, bufferRegion, pick_random(region), | ||
1529 | balance ? Random::rand() % 3 + 1 : shapeSize); | ||
1530 | if (!balance && numShapesN) | ||
1531 | for (Shape s : shapesN) | ||
1532 | if (std::equal(shape.begin(), shape.end(), s.begin(), s.end())) | ||
1533 | return false; // Prevent unintentional in-group canceling | ||
1534 | shapes.push_back(shape); | ||
1535 | } | ||
1536 | // Take remaining area and try to stick it to existing shapes | ||
1537 | multibreak: | ||
1538 | while (region.size() > 0) { | ||
1539 | pos = pick_random(region); | ||
1540 | for (Shape& shape : shapes) { | ||
1541 | if (shape.size() > shapeSize || shape.count(pos) > 0) continue; | ||
1542 | for (Point p : shape) { | ||
1543 | for (Point dir : _DIRECTIONS2) { | ||
1544 | if (pos + dir == p) { | ||
1545 | shape.insert(pos); | ||
1546 | if (!bufferRegion.erase(pos)) region.erase(pos); | ||
1547 | goto multibreak; | ||
1548 | } | ||
1549 | } | ||
1550 | } | ||
1551 | } | ||
1552 | // Failed to cover entire region, need to pick a different region | ||
1553 | break; | ||
1554 | } | ||
1555 | if (region.size() > 0) continue; | ||
1556 | if (balance) { // Undo swap for balancing shapes | ||
1557 | std::swap(shapes, shapesN); | ||
1558 | } | ||
1559 | numShapes = static_cast<int>(shapes.size()); | ||
1560 | for (Shape& shape : shapesN) { | ||
1561 | shapes.push_back(shape); | ||
1562 | } | ||
1563 | if (hasFlag(Config::DisconnectShapes)) { | ||
1564 | // Make sure at least one shape is disconnected | ||
1565 | bool disconnect = false; | ||
1566 | for (Shape& shape : shapes) { | ||
1567 | if (shape.size() == 1) continue; | ||
1568 | disconnect = true; | ||
1569 | for (Point p : shape) { | ||
1570 | for (Point dir : _DIRECTIONS2) { | ||
1571 | if (shape.count(p + dir)) { | ||
1572 | disconnect = false; | ||
1573 | break; | ||
1574 | } | ||
1575 | } | ||
1576 | if (!disconnect) break; | ||
1577 | } | ||
1578 | if (disconnect) break; | ||
1579 | } | ||
1580 | if (!disconnect) continue; | ||
1581 | } | ||
1582 | if (numShapes > 1) shapesCombined = true; | ||
1583 | numNegative -= static_cast<int>(shapesN.size()); | ||
1584 | if (hasFlag(Generate::Config::MountainFloorH) && | ||
1585 | amount == | ||
1586 | 6) { // For mountain floor, combine some of the shapes together | ||
1587 | if (!combine_shapes(shapes) || | ||
1588 | !combine_shapes( | ||
1589 | shapes)) // Must call this twice b/c there are two combined areas | ||
1590 | return false; | ||
1591 | amount -= 2; | ||
1592 | } | ||
1593 | for (Shape& shape : shapes) { | ||
1594 | int symbol = | ||
1595 | make_shape_symbol(shape, (numRotated-- > 0), (numShapes-- <= 0)); | ||
1596 | if (symbol == 0) return false; | ||
1597 | if (!((symbol >> 16) == 0x000F || (symbol >> 16) == 0x1111)) | ||
1598 | flatShapes = false; | ||
1599 | // Attempt not to put shape symbols adjacent | ||
1600 | Point pos; | ||
1601 | for (int i = 0; i < 10; i++) { | ||
1602 | if (open2.size() == 0) return false; | ||
1603 | pos = pick_random(open2); | ||
1604 | bool pass = true; | ||
1605 | for (Point dir : _8DIRECTIONS2) { | ||
1606 | Point p = pos + dir; | ||
1607 | if (!off_edge(p) && get(p) & Decoration::Poly) { | ||
1608 | pass = false; | ||
1609 | break; | ||
1610 | } | ||
1611 | } | ||
1612 | if (pass) break; | ||
1613 | } | ||
1614 | if (symbol & Decoration::Negative) | ||
1615 | set(pos, | ||
1616 | symbol | negativeColors[(colorIndexN++) % negativeColors.size()]); | ||
1617 | else { | ||
1618 | set(pos, symbol | colors[(colorIndex++) % colors.size()]); | ||
1619 | totalArea += static_cast<int>(shape.size()); | ||
1620 | amount--; | ||
1621 | } | ||
1622 | open2.erase(pos); | ||
1623 | _openpos.erase(pos); | ||
1624 | if (_panel->symmetry && originalAmount >= 3) { | ||
1625 | for (const Point& p : shape) { | ||
1626 | if (p.first < minx) minx = p.first; | ||
1627 | if (p.second < miny) miny = p.second; | ||
1628 | if (p.first > maxx) maxx = p.first; | ||
1629 | if (p.second > maxy) maxy = p.second; | ||
1630 | } | ||
1631 | } | ||
1632 | } | ||
1633 | } // Do some final checks - make sure targetArea has been reached, all shapes | ||
1634 | // have been placed, and that config requirements have been met | ||
1635 | if (totalArea < targetArea || numNegative > 0 || | ||
1636 | hasFlag(Config::RequireCancelShapes) && !shapesCanceled || | ||
1637 | hasFlag(Config::RequireCombineShapes) && !shapesCombined || | ||
1638 | originalAmount > 1 && flatShapes) | ||
1639 | return false; | ||
1640 | // If symmetry, make sure it didn't shove all the shapes to one side | ||
1641 | if (_panel->symmetry && originalAmount >= 3 && | ||
1642 | (minx >= _panel->width() / 2 || maxx <= _panel->width() / 2 || | ||
1643 | miny >= _panel->height() / 2 || maxy <= _panel->height() / 2)) | ||
1644 | return false; | ||
1645 | return true; | ||
1646 | } | ||
1647 | |||
1648 | // Count the occurrence of the given symbol color in the given region (for the | ||
1649 | // stars) | ||
1650 | int Generate::count_color(const std::set<Point>& region, int color) { | ||
1651 | int count = 0; | ||
1652 | for (Point p : region) { | ||
1653 | int sym = get(p); | ||
1654 | if (sym && (sym & 0xf) == color) | ||
1655 | if (count++ == 2) return count; | ||
1656 | } | ||
1657 | return count; | ||
1658 | } | ||
1659 | |||
1660 | // Place the given amount of stars with the given color | ||
1661 | bool Generate::place_stars(int color, int amount) { | ||
1662 | std::set<Point> open = _openpos; | ||
1663 | while (amount > 0) { | ||
1664 | if (open.size() == 0) return false; | ||
1665 | Point pos = pick_random(open); | ||
1666 | std::set<Point> region = get_region(pos); | ||
1667 | std::set<Point> open2; // All of the open points in that region | ||
1668 | for (Point p : region) { | ||
1669 | if (open.erase(p)) open2.insert(p); | ||
1670 | } | ||
1671 | int count = count_color(region, color); | ||
1672 | if (count >= 2) continue; // Too many of that color | ||
1673 | if (open2.size() + count < 2) | ||
1674 | continue; // Not enough space to get 2 of that color | ||
1675 | if (count == 0 && amount == 1) | ||
1676 | continue; // If one star is left, it needs a pair | ||
1677 | set(pos, Decoration::Star | color); | ||
1678 | _openpos.erase(pos); | ||
1679 | amount--; | ||
1680 | if (count == 0) { // Add a second star of the same color | ||
1681 | open2.erase(pos); | ||
1682 | if (open2.size() == 0) return false; | ||
1683 | pos = pick_random(open2); | ||
1684 | set(pos, Decoration::Star | color); | ||
1685 | _openpos.erase(pos); | ||
1686 | amount--; | ||
1687 | } | ||
1688 | } | ||
1689 | return true; | ||
1690 | } | ||
1691 | |||
1692 | // Check if there is a star in the given region | ||
1693 | bool Generate::has_star(const std::set<Point>& region, int color) { | ||
1694 | for (Point p : region) { | ||
1695 | if (get(p) == (Decoration::Star | color)) return true; | ||
1696 | } | ||
1697 | return false; | ||
1698 | } | ||
1699 | |||
1700 | bool Generate::checkStarZigzag() { | ||
1701 | if (_panel->width() <= 5 || _panel->height() <= 5) return true; | ||
1702 | for (int y = 1; y < _panel->height(); y += 2) { | ||
1703 | std::map<int, int> colorCount; | ||
1704 | for (int x = 1; x < _panel->width(); x += 2) { | ||
1705 | int color = get(x, y); | ||
1706 | if (color == 0) continue; | ||
1707 | if (!colorCount.count(color)) colorCount[color] = 0; | ||
1708 | colorCount[color] += 1; | ||
1709 | } | ||
1710 | for (std::pair<int, int> count : colorCount) | ||
1711 | if (count.second % 2 != 0) return true; | ||
1712 | } | ||
1713 | return false; | ||
1714 | } | ||
1715 | |||
1716 | // Place the given amount of triangles with the given color. targetCount is how | ||
1717 | // many triangles are in the symbol, or 0 for random | ||
1718 | bool Generate::place_triangles(int color, int amount, int targetCount) { | ||
1719 | /*if (_panel->id == 0x033EA) { // Keep Yellow Pressure Plate | ||
1720 | int count = count_sides({1, 3}); | ||
1721 | set({1, 3}, Decoration::Triangle | (count << 16) | color); | ||
1722 | _openpos.erase({1, 3}); | ||
1723 | }*/ | ||
1724 | std::set<Point> open = _openpos; | ||
1725 | int count1 = 0, count2 = 0, count3 = 0; | ||
1726 | while (amount > 0) { | ||
1727 | if (open.size() == 0) return false; | ||
1728 | Point pos = pick_random(open); | ||
1729 | int count = count_sides(pos); | ||
1730 | open.erase(pos); | ||
1731 | if (_panel->symmetry) { | ||
1732 | open.erase(get_sym_point(pos)); | ||
1733 | } | ||
1734 | if (count == 0 || targetCount && count != targetCount) continue; | ||
1735 | if (hasFlag(Config::TreehouseLayout) /*|| | ||
1736 | _panel->id == 0x289E7*/) { // If the block is adjacent to a start or | ||
1737 | // exit, don't place a triangle there | ||
1738 | bool found = false; | ||
1739 | for (Point dir : _DIRECTIONS1) { | ||
1740 | if (_starts.count(pos + dir) || _exits.count(pos + dir)) { | ||
1741 | found = true; | ||
1742 | break; | ||
1743 | } | ||
1744 | } | ||
1745 | if (found) continue; | ||
1746 | } | ||
1747 | if (count == 1) { | ||
1748 | if (!targetCount && count1 * 2 > count2 + count3 && | ||
1749 | Random::rand() % 2 == 0) | ||
1750 | continue; | ||
1751 | count1++; | ||
1752 | } | ||
1753 | if (count == 2) { | ||
1754 | if (!targetCount && count2 * 2 > count1 + count3 && | ||
1755 | Random::rand() % 2 == 0) | ||
1756 | continue; | ||
1757 | count2++; | ||
1758 | } | ||
1759 | if (count == 3) { | ||
1760 | if (!targetCount && count3 * 2 > count1 + count2 && | ||
1761 | Random::rand() % 2 == 0) | ||
1762 | continue; | ||
1763 | count3++; | ||
1764 | } | ||
1765 | set(pos, Decoration::Triangle | (count << 16) | color); | ||
1766 | _openpos.erase(pos); | ||
1767 | amount--; | ||
1768 | } | ||
1769 | return true; | ||
1770 | } | ||
1771 | |||
1772 | // Count how many sides are touched by the line (for the triangles) | ||
1773 | int Generate::count_sides(Point pos) { | ||
1774 | int count = 0; | ||
1775 | for (Point dir : _DIRECTIONS1) { | ||
1776 | Point p = pos + dir; | ||
1777 | if (!off_edge(p) && get(p) == PATH) { | ||
1778 | count++; | ||
1779 | } | ||
1780 | } | ||
1781 | return count; | ||
1782 | } | ||
1783 | |||
1784 | // Place the given amount of arrows with the given color. targetCount is how | ||
1785 | // many ticks on the arrows, or 0 for random The color won't actually be | ||
1786 | // reflected, ArrowRecolor must be used instead | ||
1787 | bool Generate::place_arrows(int color, int amount, int targetCount) { | ||
1788 | std::set<Point> open = _openpos; | ||
1789 | while (amount > 0) { | ||
1790 | if (open.size() == 0) return false; | ||
1791 | Point pos = pick_random(open); | ||
1792 | open.erase(pos); | ||
1793 | if (pos.first == _panel->width() / 2) | ||
1794 | continue; // Because of a glitch where arrows in the center column won't | ||
1795 | // draw right | ||
1796 | int fails = 0; | ||
1797 | while (fails++ < 20) { // Keep picking random directions until one works | ||
1798 | int choice = (_parity == -1 ? Random::rand() % 8 : Random::rand() % 4); | ||
1799 | Point dir = _8DIRECTIONS2[choice]; | ||
1800 | int count = count_crossings(pos, dir); | ||
1801 | if (count == 0 || count > 3 || targetCount && count != targetCount) | ||
1802 | continue; | ||
1803 | if (dir.first < 0 && count == (pos.first + 1) / 2 || | ||
1804 | dir.first > 0 && count == (_panel->width() - pos.first) / 2 || | ||
1805 | dir.second < 0 && count == (pos.second + 1) / 2 || | ||
1806 | dir.second > 0 && count == (_panel->height() - pos.second) / 2 && | ||
1807 | Random::rand() % 10 > 0) | ||
1808 | continue; // Make it so that there will be some possible edges that | ||
1809 | // aren't passed, in the vast majority of cases | ||
1810 | //_panel->SetGridSymbol(pos, Decoration::Arrow | color | (count << 12) | | ||
1811 | //(choice << 16)); | ||
1812 | _openpos.erase(pos); | ||
1813 | amount--; | ||
1814 | break; | ||
1815 | } | ||
1816 | } | ||
1817 | return true; | ||
1818 | } | ||
1819 | |||
1820 | // Count the number of times the given vector is passed through (for the arrows) | ||
1821 | int Generate::count_crossings(Point pos, Point dir) { | ||
1822 | pos = pos + dir / 2; | ||
1823 | int count = 0; | ||
1824 | while (!off_edge(pos)) { | ||
1825 | if (get(pos) == PATH) count++; | ||
1826 | pos = pos + dir; | ||
1827 | } | ||
1828 | return count; | ||
1829 | } | ||
1830 | |||
1831 | // Place the given amount of erasers with the given colors. eraseSymbols are the | ||
1832 | // symbols that were erased | ||
1833 | bool Generate::place_erasers(const std::vector<int>& colors, | ||
1834 | const std::vector<int>& eraseSymbols) { | ||
1835 | std::set<Point> open = _openpos; | ||
1836 | /*if (_panel->id == 0x288FC && hasFlag(Generate::Config::DisableWrite)) | ||
1837 | open.erase({5, 5}); // For the puzzle in the cave with a pillar in middle*/ | ||
1838 | int amount = static_cast<int>(colors.size()); | ||
1839 | while (amount > 0) { | ||
1840 | if (open.size() == 0) return false; | ||
1841 | int toErase = eraseSymbols[amount - 1]; | ||
1842 | int color = colors[amount - 1]; | ||
1843 | Point pos = pick_random(open); | ||
1844 | std::set<Point> region = get_region(pos); | ||
1845 | std::set<Point> open2; | ||
1846 | for (Point p : region) { | ||
1847 | if (open.erase(p)) open2.insert(p); | ||
1848 | } | ||
1849 | if (_splitPoints.size() > | ||
1850 | 0) { // Make sure this is one of the split point regions | ||
1851 | bool found = false; | ||
1852 | for (Point p : _splitPoints) { | ||
1853 | if (region.count(p)) { | ||
1854 | found = true; | ||
1855 | break; | ||
1856 | } | ||
1857 | } | ||
1858 | if (!found) continue; | ||
1859 | } | ||
1860 | /*if (_panel->id == 0x288FC && hasFlag(Generate::Config::DisableWrite) && | ||
1861 | !region.count({5, 5})) | ||
1862 | continue; // For the puzzle in the cave with a pillar in middle*/ | ||
1863 | if (hasFlag(Config::MakeStonesUnsolvable)) { | ||
1864 | std::set<Point> valid; | ||
1865 | for (Point p : open2) { | ||
1866 | // Try to make a checkerboard pattern with the stones | ||
1867 | if (!off_edge(p + Point(2, 2)) && get(p + Point(2, 2)) == toErase && | ||
1868 | get(p + Point(0, 2)) != 0 && get(p + Point(0, 2)) != toErase && | ||
1869 | get(p + Point(2, 0)) != 0 && get(p + Point(2, 0)) != toErase || | ||
1870 | !off_edge(p + Point(-2, 2)) && get(p + Point(-2, 2)) == toErase && | ||
1871 | get(p + Point(0, 2)) != 0 && get(p + Point(0, 2)) != toErase && | ||
1872 | get(p + Point(-2, 0)) != 0 && | ||
1873 | get(p + Point(-2, 0)) != toErase || | ||
1874 | !off_edge(p + Point(2, -2)) && get(p + Point(2, -2)) == toErase && | ||
1875 | get(p + Point(0, -2)) != 0 && | ||
1876 | get(p + Point(0, -2)) != toErase && get(p + Point(2, 0)) != 0 && | ||
1877 | get(p + Point(2, 0)) != toErase || | ||
1878 | !off_edge(p + Point(-2, -2)) && get(p + Point(-2, -2)) == toErase && | ||
1879 | get(p + Point(0, -2)) != 0 && | ||
1880 | get(p + Point(0, -2)) != toErase && | ||
1881 | get(p + Point(-2, 0)) != 0 && get(p + Point(-2, 0)) != toErase) | ||
1882 | valid.insert(p); | ||
1883 | } | ||
1884 | open2 = valid; | ||
1885 | } | ||
1886 | if ((open2.size() == 0 || _splitPoints.size() == 0 && open2.size() == 1) && | ||
1887 | !(toErase & Decoration::Dot)) | ||
1888 | continue; | ||
1889 | bool canPlace = false; | ||
1890 | if (get_symbol_type(toErase) == Decoration::Stone) { | ||
1891 | canPlace = !can_place_stone(region, (toErase & 0xf)); | ||
1892 | } else if (get_symbol_type(toErase) == Decoration::Star) { | ||
1893 | canPlace = (count_color(region, (toErase & 0xf)) + | ||
1894 | (color == (toErase & 0xf) ? 1 : 0) != | ||
1895 | 1); | ||
1896 | } else | ||
1897 | canPlace = true; | ||
1898 | if (!canPlace) continue; | ||
1899 | |||
1900 | if (get_symbol_type(toErase) == Decoration::Stone || | ||
1901 | get_symbol_type(toErase) == Decoration::Star) { | ||
1902 | set(pos, toErase); | ||
1903 | } else if (toErase & | ||
1904 | Decoration::Dot) { // Find an open edge to put the dot on | ||
1905 | std::set<Point> openEdge; | ||
1906 | for (Point p : region) { | ||
1907 | for (Point dir : _8DIRECTIONS1) { | ||
1908 | if (toErase == Decoration::Dot_Intersection && | ||
1909 | (dir.first == 0 || dir.second == 0)) | ||
1910 | continue; | ||
1911 | Point p2 = p + dir; | ||
1912 | if (!hasSymbolOrPath(p2) && | ||
1913 | (hasFlag(Config::FalseParity) || can_place_dot(p2, false))) { | ||
1914 | openEdge.insert(p2); | ||
1915 | } | ||
1916 | } | ||
1917 | } | ||
1918 | if (openEdge.size() == 0) continue; | ||
1919 | pos = pick_random(openEdge); | ||
1920 | toErase &= ~IntersectionFlags::INTERSECTION; | ||
1921 | if ((toErase & 0xf) == Decoration::Color::Blue || | ||
1922 | (toErase & 0xf) == Decoration::Color::Cyan) | ||
1923 | toErase |= IntersectionFlags::DOT_IS_BLUE; | ||
1924 | if ((toErase & 0xf) == Decoration::Color::Yellow || | ||
1925 | (toErase & 0xf) == Decoration::Color::Orange) | ||
1926 | toErase |= IntersectionFlags::DOT_IS_ORANGE; | ||
1927 | toErase &= ~0x4000f; // Take away extra flags from the symbol | ||
1928 | if ((pos.first & 1) == 0 && (pos.second & 1) == 0) | ||
1929 | toErase |= Decoration::Dot_Intersection; | ||
1930 | else if ((pos.second & 1) == 0) | ||
1931 | toErase |= Decoration::Dot_Row; | ||
1932 | set(pos, ((pos.first & 1) == 1 ? Decoration::Dot_Row | ||
1933 | : (pos.second & 1) == 1 ? Decoration::Dot_Column | ||
1934 | : Decoration::Dot_Intersection) | | ||
1935 | (toErase & 0xffff)); | ||
1936 | } else if (get_symbol_type(toErase) == Decoration::Poly) { | ||
1937 | int symbol = 0; // Make a random shape to cancel | ||
1938 | while (symbol == 0) { | ||
1939 | std::set<Point> area = _gridpos; | ||
1940 | int shapeSize; | ||
1941 | if ((toErase & Decoration::Negative) || hasFlag(Config::SmallShapes)) | ||
1942 | shapeSize = Random::rand() % 3 + 1; | ||
1943 | else { | ||
1944 | shapeSize = Random::rand() % 5 + 1; | ||
1945 | if (shapeSize < 3) shapeSize += Random::rand() % 3; | ||
1946 | } | ||
1947 | Shape shape = generate_shape(area, pick_random(area), shapeSize); | ||
1948 | if (shape.size() == region.size()) | ||
1949 | continue; // Don't allow the shape to match the region, to guarantee | ||
1950 | // it will be wrong | ||
1951 | symbol = make_shape_symbol(shape, toErase & Decoration::Can_Rotate, | ||
1952 | toErase & Decoration::Negative); | ||
1953 | } | ||
1954 | set(pos, symbol | (toErase & 0xf)); | ||
1955 | } else if (get_symbol_type(toErase) == Decoration::Triangle) { | ||
1956 | if (hasFlag(Config::TreehouseLayout) /*|| | ||
1957 | _panel->id == 0x289E7*/) { // If the block is adjacent to a start or | ||
1958 | // exit, don't place a triangle there | ||
1959 | bool found = false; | ||
1960 | for (Point dir : _DIRECTIONS1) { | ||
1961 | if (_starts.count(pos + dir) || _exits.count(pos + dir)) { | ||
1962 | found = true; | ||
1963 | break; | ||
1964 | } | ||
1965 | } | ||
1966 | if (found) continue; | ||
1967 | } | ||
1968 | int count = count_sides(pos); | ||
1969 | if (count == 0) | ||
1970 | count = Random::rand() % 3 + 1; | ||
1971 | else | ||
1972 | count = (count + (Random::rand() & 1)) % 3 + 1; | ||
1973 | set(pos, toErase | (count << 16)); | ||
1974 | } | ||
1975 | |||
1976 | if (!(toErase & Decoration::Dot)) { | ||
1977 | _openpos.erase(pos); | ||
1978 | open2.erase(pos); | ||
1979 | } | ||
1980 | // Place the eraser at a random open point | ||
1981 | if (_splitPoints.size() == 0) | ||
1982 | pos = pick_random(open2); | ||
1983 | else | ||
1984 | for (Point p : _splitPoints) | ||
1985 | if (region.count(p)) { | ||
1986 | pos = p; | ||
1987 | break; | ||
1988 | } | ||
1989 | /*if (_panel->id == 0x288FC && hasFlag(Generate::Config::DisableWrite)) { | ||
1990 | if (hasSymbolOrPath(5, 5)) return false; | ||
1991 | pos = {5, 5}; // For the puzzle in the cave with a pillar in middle | ||
1992 | }*/ | ||
1993 | set(pos, Decoration::Eraser | color); | ||
1994 | _openpos.erase(pos); | ||
1995 | amount--; | ||
1996 | } | ||
1997 | return true; | ||
1998 | } | ||
1999 | |||
2000 | // For the mountain floor puzzle on hard mode. Combine two tetris shapes into | ||
2001 | // one | ||
2002 | bool Generate::combine_shapes(std::vector<Shape>& shapes) { | ||
2003 | for (int i = 0; i < shapes.size(); i++) { | ||
2004 | for (int j = 0; j < shapes.size(); j++) { | ||
2005 | if (i == j) continue; | ||
2006 | if (shapes[i].size() + shapes[j].size() <= 5) continue; | ||
2007 | if (shapes[i].size() > 5 || shapes[j].size() > 5) continue; | ||
2008 | // Look for adjacent points | ||
2009 | for (Point p1 : shapes[i]) { | ||
2010 | for (Point p2 : shapes[j]) { | ||
2011 | for (Point dir : _DIRECTIONS2) { | ||
2012 | if (p1 + dir == p2) { | ||
2013 | // Combine shapes | ||
2014 | for (Point p : shapes[i]) shapes[j].insert(p); | ||
2015 | // Make sure there are no holes | ||
2016 | std::set<Point> area = _gridpos; | ||
2017 | for (Point p : shapes[j]) area.erase(p); | ||
2018 | while (area.size() > 0) { | ||
2019 | std::set<Point> region; | ||
2020 | std::vector<Point> check; | ||
2021 | check.push_back(*area.begin()); | ||
2022 | region.insert(*area.begin()); | ||
2023 | while (check.size() > 0) { | ||
2024 | Point p = check[check.size() - 1]; | ||
2025 | check.pop_back(); | ||
2026 | for (Point dir : _DIRECTIONS1) { | ||
2027 | Point p2 = p + dir * 2; | ||
2028 | if (area.count(p2) && region.insert(p2).second) { | ||
2029 | check.push_back(p2); | ||
2030 | } | ||
2031 | } | ||
2032 | } | ||
2033 | bool connected = false; | ||
2034 | for (Point p : region) { | ||
2035 | if (p.first == 1 || p.second == 1 || | ||
2036 | p.first == _panel->width() - 2 || | ||
2037 | p.second == _panel->height() - 2) { | ||
2038 | connected = true; | ||
2039 | break; | ||
2040 | } | ||
2041 | } | ||
2042 | if (!connected) return false; | ||
2043 | for (Point p : region) area.erase(p); | ||
2044 | } | ||
2045 | shapes.erase(shapes.begin() + i); | ||
2046 | return true; | ||
2047 | } | ||
2048 | } | ||
2049 | } | ||
2050 | } | ||
2051 | } | ||
2052 | } | ||
2053 | return false; | ||
2054 | } | ||
2055 | |||
2056 | bool Generate::hasSymbolOrPath(int x, int y) { return get(x, y) != 0; } | ||
diff --git a/ext/wittle_generator/Generate.h b/ext/wittle_generator/Generate.h new file mode 100644 index 0000000..c28a47d --- /dev/null +++ b/ext/wittle_generator/Generate.h | |||
@@ -0,0 +1,248 @@ | |||
1 | #ifndef GENERATE_H_0582FCEA | ||
2 | #define GENERATE_H_0582FCEA | ||
3 | |||
4 | #include <stdlib.h> | ||
5 | #include <time.h> | ||
6 | |||
7 | #include <algorithm> | ||
8 | #include <memory> | ||
9 | #include <set> | ||
10 | #include <string> | ||
11 | |||
12 | #include "Panel.h" | ||
13 | #include "PuzzleSymbols.h" | ||
14 | #include "Random.h" | ||
15 | |||
16 | typedef std::set<Point> Shape; | ||
17 | |||
18 | // The main class for generating puzzles. | ||
19 | class Generate { | ||
20 | public: | ||
21 | Generate() { | ||
22 | _width = _height = 0; | ||
23 | _areaTotal = _genTotal = _totalPuzzles = _areaPuzzles = _stoneTypes = 0; | ||
24 | _fullGaps = _bisect = _allowNonMatch = false; | ||
25 | _panel = NULL; | ||
26 | _parity = -1; | ||
27 | colorblind = false; | ||
28 | _seed = Random::rand(); | ||
29 | arrowColor = backgroundColor = successColor = {0, 0, 0, 0}; | ||
30 | resetConfig(); | ||
31 | } | ||
32 | enum Config { // See configinfo.txt for explanations of config flags. | ||
33 | None = 0, | ||
34 | FullGaps = 0x1, | ||
35 | StartEdgeOnly = 0x2, | ||
36 | DisableWrite = 0x4, | ||
37 | PreserveStructure = 0x8, | ||
38 | MakeStonesUnsolvable = 0x10, | ||
39 | SmallShapes = 0x20, | ||
40 | DisconnectShapes = 0x40, | ||
41 | ResetColors = 0x80, | ||
42 | DisableCancelShapes = 0x100, | ||
43 | RequireCancelShapes = 0x200, | ||
44 | BigShapes = 0x400, | ||
45 | SplitShapes = 0x800, | ||
46 | RequireCombineShapes = 0x1000, | ||
47 | TreehouseLayout = 0x2000, | ||
48 | TreehouseColors = 0x4000, | ||
49 | AlternateColors = 0x8000, | ||
50 | WriteColors = 0x10000, | ||
51 | Write2Color = 0x20000, | ||
52 | FixBackground = 0x40000, | ||
53 | CombineErasers = 0x80000, | ||
54 | LongPath = 0x100000, | ||
55 | ShortPath = 0x200000, | ||
56 | EnableFlash = 0x400000, | ||
57 | DecorationsOnly = 0x800000, | ||
58 | FalseParity = 0x1000000, | ||
59 | DisableDotIntersection = 0x2000000, | ||
60 | WriteDotColor = 0x4000000, | ||
61 | WriteDotColor2 = 0x8000000, | ||
62 | LongestPath = 0x10000000, | ||
63 | WriteInvisible = 0x20000000, | ||
64 | DisableReset = 0x40000000, | ||
65 | MountainFloorH = 0x80000000 | ||
66 | }; | ||
67 | |||
68 | void generate(int width, int height, PuzzleSymbols symbols); | ||
69 | |||
70 | /*void generateMaze(int id); | ||
71 | void generateMaze(int id, int numStarts, int numExits);*/ | ||
72 | void initPanel(); | ||
73 | void setPath(const std::set<Point>& path) { | ||
74 | customPath = path; | ||
75 | for (Point p : path) setSymbol(IntersectionFlags::PATH, p.first, p.second); | ||
76 | } | ||
77 | void setObstructions(const std::vector<Point>& walls) { | ||
78 | _obstructions = {walls}; | ||
79 | } | ||
80 | void setObstructions(const std::vector<std::vector<Point>>& walls) { | ||
81 | _obstructions = walls; | ||
82 | } | ||
83 | void setSymbol(Decoration::Shape symbol, int x, int y); | ||
84 | void setSymbol(IntersectionFlags symbol, int x, int y) { | ||
85 | setSymbol(static_cast<Decoration::Shape>(symbol), x, y); | ||
86 | } | ||
87 | void setGridSize(int width, int height); | ||
88 | void setSymmetry(Panel::Symmetry symmetry); | ||
89 | void write(int id); | ||
90 | void setFlag(Config option) { _config |= option; }; | ||
91 | void setFlagOnce(Config option) { | ||
92 | _config |= option; | ||
93 | _oneTimeAdd |= option; | ||
94 | }; | ||
95 | bool hasFlag(Config option) { return _config & option; }; | ||
96 | void removeFlag(Config option) { _config &= ~option; }; | ||
97 | void removeFlagOnce(Config option) { | ||
98 | _config &= ~option; | ||
99 | _oneTimeRemove |= option; | ||
100 | }; | ||
101 | void resetConfig(); | ||
102 | void seed(long seed) { | ||
103 | Random::seed(seed); | ||
104 | _seed = Random::rand(); | ||
105 | } | ||
106 | |||
107 | float pathWidth; // Controls how thick the line is on the puzzle | ||
108 | std::vector<Point> hitPoints; // The generated path will be forced to hit | ||
109 | // these points in order | ||
110 | std::set<Point> | ||
111 | openPos; // Custom set of points that can have symbols placed on | ||
112 | std::set<Point> blockPos; // Point that must be left open | ||
113 | std::set<Point> customPath; | ||
114 | Color arrowColor, backgroundColor, successColor; // For the arrow puzzles | ||
115 | |||
116 | private: | ||
117 | int get_symbol_type(int flags) { return flags & 0x700; } | ||
118 | void set_path(Point pos); | ||
119 | Point get_sym_point(Point pos) { return _panel->get_sym_point(pos); } | ||
120 | int get_parity(Point pos) { return (pos.first / 2 + pos.second / 2) % 2; } | ||
121 | void clear(); | ||
122 | void resetVars(); | ||
123 | void init_treehouse_layout(); | ||
124 | template <class T> | ||
125 | T pick_random(const std::vector<T>& vec) { | ||
126 | return vec[Random::rand() % vec.size()]; | ||
127 | } | ||
128 | template <class T> | ||
129 | T pick_random(const std::set<T>& set) { | ||
130 | auto it = set.begin(); | ||
131 | std::advance(it, Random::rand() % set.size()); | ||
132 | return *it; | ||
133 | } | ||
134 | template <class T> | ||
135 | T pop_random(const std::vector<T>& vec) { | ||
136 | int i = Random::rand() % vec.size(); | ||
137 | T item = vec[i]; | ||
138 | vec.erase(vec.begin() + i); | ||
139 | return item; | ||
140 | } | ||
141 | template <class T> | ||
142 | T pop_random(const std::set<T>& set) { | ||
143 | T item = pick_random(set); | ||
144 | set.erase(item); | ||
145 | return item; | ||
146 | } | ||
147 | bool on_edge(Point p) { | ||
148 | return (p.first == 0 || p.first + 1 == _panel->width() || p.second == 0 || | ||
149 | p.second + 1 == _panel->height()); | ||
150 | } | ||
151 | bool off_edge(Point p) { | ||
152 | return (p.first < 0 || p.first >= _panel->width() || p.second < 0 || | ||
153 | p.second >= _panel->height()); | ||
154 | } | ||
155 | static std::vector<Point> _DIRECTIONS1, _8DIRECTIONS1, _DIRECTIONS2, | ||
156 | _8DIRECTIONS2, _SHAPEDIRECTIONS, _DISCONNECT; | ||
157 | // bool generate_maze(int id, int numStarts, int numExits); | ||
158 | bool generateInternal(int width, int height, PuzzleSymbols symbols); | ||
159 | bool place_all_symbols(PuzzleSymbols& symbols); | ||
160 | bool generate_path(PuzzleSymbols& symbols); | ||
161 | bool generate_path_length(int minLength, int maxLength); | ||
162 | bool generate_path_length(int minLength) { | ||
163 | return generate_path_length(minLength, 10000); | ||
164 | }; | ||
165 | bool generate_path_regions(int minRegions); | ||
166 | bool generate_longest_path(); | ||
167 | bool generate_special_path(); | ||
168 | void erase_path(); | ||
169 | Point adjust_point(Point pos); | ||
170 | std::set<Point> get_region(Point pos); | ||
171 | std::vector<int> get_symbols_in_region(Point pos); | ||
172 | std::vector<int> get_symbols_in_region(const std::set<Point>& region); | ||
173 | bool place_start(int amount); | ||
174 | bool place_exit(int amount); | ||
175 | bool can_place_gap(Point pos); | ||
176 | bool place_gaps(int amount); | ||
177 | bool can_place_dot(Point pos, bool intersectionOnly); | ||
178 | bool place_dots(int amount, Decoration::Color color, bool intersectionOnly); | ||
179 | bool can_place_stone(const std::set<Point>& region, int color); | ||
180 | bool place_stones(int color, int amount); | ||
181 | Shape generate_shape(std::set<Point>& region, std::set<Point>& bufferRegion, | ||
182 | Point pos, int maxSize); | ||
183 | Shape generate_shape(std::set<Point>& region, Point pos, int maxSize) { | ||
184 | std::set<Point> buffer; | ||
185 | return generate_shape(region, buffer, pos, maxSize); | ||
186 | } | ||
187 | int make_shape_symbol(Shape shape, bool rotated, bool negative, int rotation, | ||
188 | int depth); | ||
189 | int make_shape_symbol(const Shape& shape, bool rotated, bool negative) { | ||
190 | return make_shape_symbol(shape, rotated, negative, -1, 0); | ||
191 | } | ||
192 | bool place_shapes(const std::vector<int>& colors, | ||
193 | const std::vector<int>& negativeColors, int amount, | ||
194 | int numRotated, int numNegative); | ||
195 | int count_color(const std::set<Point>& region, int color); | ||
196 | bool place_stars(int color, int amount); | ||
197 | bool has_star(const std::set<Point>& region, int color); | ||
198 | bool checkStarZigzag(); | ||
199 | bool place_triangles(int color, int amount, int targetCount); | ||
200 | int count_sides(Point pos); | ||
201 | bool place_arrows(int color, int amount, int targetCount); | ||
202 | int count_crossings(Point pos, Point dir); | ||
203 | bool place_erasers(const std::vector<int>& colors, | ||
204 | const std::vector<int>& eraseSymbols); | ||
205 | bool combine_shapes(std::vector<Shape>& shapes); | ||
206 | |||
207 | bool hasSymbolOrPath(const Point& pos) { | ||
208 | return hasSymbolOrPath(pos.first, pos.second); | ||
209 | } | ||
210 | bool hasSymbolOrPath(int x, int y); | ||
211 | |||
212 | std::unique_ptr<Panel> _panel; | ||
213 | std::vector<std::vector<int>> _custom_grid; | ||
214 | int _width, _height; | ||
215 | Panel::Symmetry _symmetry; | ||
216 | std::set<Point> _starts, _exits; | ||
217 | std::set<Point> _gridpos, _openpos; | ||
218 | std::set<Point> _path, _path1, _path2; | ||
219 | bool _fullGaps, _bisect; | ||
220 | int _stoneTypes; | ||
221 | int _config; | ||
222 | int _oneTimeAdd, _oneTimeRemove; | ||
223 | long _seed; | ||
224 | std::vector<Point> _splitPoints; | ||
225 | bool _allowNonMatch; // Used for multi-generator | ||
226 | int _parity; | ||
227 | std::vector<std::vector<Point>> _obstructions; | ||
228 | bool colorblind; | ||
229 | |||
230 | int _areaTotal, _genTotal, _areaPuzzles, _totalPuzzles; | ||
231 | std::wstring _areaName; | ||
232 | |||
233 | void set(int x, int y, int val) { _panel->set(x, y, val); } | ||
234 | void set(const Point& pos, int val) { | ||
235 | _panel->set(pos.first, pos.second, val); | ||
236 | } | ||
237 | |||
238 | int get(int x, int y) { return _panel->get(x, y); } | ||
239 | int get(const Point& pos) { return _panel->get(pos.first, pos.second); } | ||
240 | |||
241 | // std::vector<std::vector<int>> _panelData; | ||
242 | // std::vector<std::vector<bool>> _drawnPath; | ||
243 | |||
244 | friend class PuzzleList; | ||
245 | friend class Special; | ||
246 | }; | ||
247 | |||
248 | #endif /* end of include guard: GENERATE_H_0582FCEA */ | ||
diff --git a/ext/wittle_generator/Panel.cpp b/ext/wittle_generator/Panel.cpp new file mode 100644 index 0000000..a005467 --- /dev/null +++ b/ext/wittle_generator/Panel.cpp | |||
@@ -0,0 +1,116 @@ | |||
1 | #include "Panel.h" | ||
2 | |||
3 | #include <fstream> | ||
4 | #include <sstream> | ||
5 | |||
6 | template <class T> | ||
7 | int find(const std::vector<T>& data, T search, size_t startIndex = 0) { | ||
8 | for (size_t i = startIndex; i < data.size(); i++) { | ||
9 | if (data[i] == search) return static_cast<int>(i); | ||
10 | } | ||
11 | return -1; | ||
12 | } | ||
13 | |||
14 | void Panel::SetSymbol(int x, int y, Decoration::Shape symbol, | ||
15 | Decoration::Color color) { | ||
16 | int gridx = x * 2 + (symbol & IntersectionFlags::COLUMN ? 0 : 1); | ||
17 | int gridy = y * 2 + (symbol & IntersectionFlags::ROW ? 0 : 1); | ||
18 | if (symbol & IntersectionFlags::DOT) { | ||
19 | if (color == Decoration::Color::Blue || color == Decoration::Color::Cyan) | ||
20 | color = static_cast<Decoration::Color>(IntersectionFlags::DOT_IS_BLUE); | ||
21 | else if (color == Decoration::Color::Orange || | ||
22 | color == Decoration::Color::Yellow) | ||
23 | color = static_cast<Decoration::Color>(IntersectionFlags::DOT_IS_ORANGE); | ||
24 | else | ||
25 | color = Decoration::Color::None; | ||
26 | if (symmetry) { | ||
27 | Point sp = get_sym_point(gridx, gridy); | ||
28 | SetGridSymbol(sp.first, sp.second, | ||
29 | static_cast<Decoration::Shape>(symbol & ~Decoration::Dot), | ||
30 | Decoration::Color::None); | ||
31 | } | ||
32 | } else if (symbol & IntersectionFlags::ROW || | ||
33 | symbol & IntersectionFlags::COLUMN) | ||
34 | color = Decoration::Color::None; | ||
35 | SetGridSymbol(gridx, gridy, symbol, color); | ||
36 | } | ||
37 | |||
38 | void Panel::SetShape(int x, int y, int shape, bool rotate, bool negative, | ||
39 | Decoration::Color color) { | ||
40 | if (!shape) return; | ||
41 | int symbol = Decoration::Shape::Poly; | ||
42 | while (!(shape & 0xf)) shape >>= 4; | ||
43 | while (!(shape & 0x1111)) shape >>= 1; | ||
44 | shape <<= 16; | ||
45 | if (rotate) | ||
46 | shape |= Decoration::Shape::Can_Rotate; | ||
47 | else | ||
48 | shape &= ~Decoration::Shape::Can_Rotate; | ||
49 | if (negative) | ||
50 | shape |= Decoration::Shape::Negative; | ||
51 | else | ||
52 | shape &= ~Decoration::Shape::Negative; | ||
53 | _grid[x * 2 + 1][y * 2 + 1] = symbol | shape | color; | ||
54 | } | ||
55 | |||
56 | void Panel::ClearSymbol(int x, int y) { ClearGridSymbol(x * 2 + 1, y * 2 + 1); } | ||
57 | |||
58 | void Panel::SetGridSymbol(int x, int y, Decoration::Shape symbol, | ||
59 | Decoration::Color color) { | ||
60 | if (symbol == Decoration::Start) _startpoints.push_back({x, y}); | ||
61 | if (symbol == Decoration::Exit) { | ||
62 | Endpoint::Direction dir; | ||
63 | if (y == 0) | ||
64 | dir = Endpoint::Direction::UP; | ||
65 | else if (y == _height - 1) | ||
66 | dir = Endpoint::Direction::DOWN; | ||
67 | else if (x == 0) | ||
68 | dir = Endpoint::Direction::LEFT; | ||
69 | else | ||
70 | dir = Endpoint::Direction::RIGHT; | ||
71 | /*if (id == 0x033D4 || id == 0x0A3B5) { | ||
72 | if (x == 0) | ||
73 | dir = Endpoint::Direction::LEFT; | ||
74 | else | ||
75 | dir = Endpoint::Direction::RIGHT; | ||
76 | }*/ | ||
77 | if (symmetry == Symmetry::ParallelH || | ||
78 | symmetry == Symmetry::ParallelHFlip) { | ||
79 | if (x == 0) dir = Endpoint::Direction::LEFT; | ||
80 | if (x == _width - 1) dir = Endpoint::Direction::RIGHT; | ||
81 | } | ||
82 | _endpoints.emplace_back(Endpoint( | ||
83 | x, y, dir, | ||
84 | IntersectionFlags::ENDPOINT | | ||
85 | (dir == Endpoint::Direction::UP || dir == Endpoint::Direction::DOWN | ||
86 | ? IntersectionFlags::COLUMN | ||
87 | : IntersectionFlags::ROW))); | ||
88 | } else | ||
89 | _grid[x][y] = symbol | color; | ||
90 | } | ||
91 | |||
92 | void Panel::ClearGridSymbol(int x, int y) { _grid[x][y] = 0; } | ||
93 | |||
94 | void Panel::Resize(int width, int height) { | ||
95 | for (Point& s : _startpoints) { | ||
96 | if (s.first == _width - 1) s.first = width - 1; | ||
97 | if (s.second == _height - 1) s.second = height - 1; | ||
98 | } | ||
99 | for (Endpoint& e : _endpoints) { | ||
100 | if (e.GetX() == _width - 1) e.SetX(width - 1); | ||
101 | if (e.GetY() == _height - 1) e.SetY(height - 1); | ||
102 | } | ||
103 | if (_width != _height || width != height) { | ||
104 | float maxDim = std::max(maxx - minx, maxy - miny); | ||
105 | float unitSize = maxDim / std::max(width - 1, height - 1); | ||
106 | minx = 0.5f - unitSize * (width - 1) / 2; | ||
107 | maxx = 0.5f + unitSize * (width - 1) / 2; | ||
108 | miny = 0.5f - unitSize * (height - 1) / 2; | ||
109 | maxy = 0.5f + unitSize * (height - 1) / 2; | ||
110 | } | ||
111 | _width = width; | ||
112 | _height = height; | ||
113 | _grid.resize(width); | ||
114 | for (auto& row : _grid) row.resize(height); | ||
115 | _resized = true; | ||
116 | } | ||
diff --git a/ext/wittle_generator/Panel.h b/ext/wittle_generator/Panel.h new file mode 100644 index 0000000..7444a9a --- /dev/null +++ b/ext/wittle_generator/Panel.h | |||
@@ -0,0 +1,448 @@ | |||
1 | #ifndef PANEL_H_FC471D68 | ||
2 | #define PANEL_H_FC471D68 | ||
3 | |||
4 | #include <stdint.h> | ||
5 | |||
6 | #include <tuple> | ||
7 | #include <vector> | ||
8 | |||
9 | struct Point { | ||
10 | int first; | ||
11 | int second; | ||
12 | Point() { | ||
13 | first = 0; | ||
14 | second = 0; | ||
15 | }; | ||
16 | Point(int x, int y) { | ||
17 | first = x; | ||
18 | second = y; | ||
19 | } | ||
20 | Point operator+(const Point& p) { | ||
21 | return {first + p.first, second + p.second}; | ||
22 | } | ||
23 | Point operator*(int d) { return {first * d, second * d}; } | ||
24 | Point operator/(int d) { return {first / d, second / d}; } | ||
25 | bool operator==(const Point& p) const { | ||
26 | return first == p.first && second == p.second; | ||
27 | }; | ||
28 | bool operator!=(const Point& p) const { | ||
29 | return first != p.first || second != p.second; | ||
30 | }; | ||
31 | friend bool operator<(const Point& p1, const Point& p2) { | ||
32 | if (p1.first == p2.first) return p1.second < p2.second; | ||
33 | return p1.first < p2.first; | ||
34 | }; | ||
35 | }; | ||
36 | |||
37 | class Decoration { | ||
38 | public: | ||
39 | enum Shape : int { | ||
40 | Exit = 0x600001, | ||
41 | Start = 0x600002, | ||
42 | Stone = 0x100, | ||
43 | Star = 0x200, | ||
44 | Poly = 0x400, | ||
45 | Eraser = 0x500, | ||
46 | Triangle = 0x600, | ||
47 | Triangle1 = 0x10600, | ||
48 | Triangle2 = 0x20600, | ||
49 | Triangle3 = 0x30600, | ||
50 | Triangle4 = 0x40600, | ||
51 | Arrow = 0x700, | ||
52 | Arrow1 = 0x1700, | ||
53 | Arrow2 = 0x2700, | ||
54 | Arrow3 = 0x3700, | ||
55 | Can_Rotate = 0x1000, | ||
56 | Negative = 0x2000, | ||
57 | Gap = 0x100000, | ||
58 | Gap_Row = 0x300000, | ||
59 | Gap_Column = 0x500000, | ||
60 | Dot = 0x20, | ||
61 | Dot_Row = 0x240020, | ||
62 | Dot_Column = 0x440020, | ||
63 | Dot_Intersection = 0x600020, | ||
64 | Empty = 0xA00, | ||
65 | }; | ||
66 | enum Color : int { | ||
67 | None = 0, | ||
68 | Black = 0x1, | ||
69 | White = 0x2, | ||
70 | Red = 0x3, // Doesn't work sadly | ||
71 | Purple = 0x4, | ||
72 | Green = 0x5, | ||
73 | Cyan = 0x6, | ||
74 | Magenta = 0x7, | ||
75 | Yellow = 0x8, | ||
76 | Blue = 0x9, | ||
77 | Orange = 0xA, | ||
78 | X = 0xF, | ||
79 | }; | ||
80 | }; | ||
81 | |||
82 | enum IntersectionFlags : int { | ||
83 | ROW = 0x200000, | ||
84 | COLUMN = 0x400000, | ||
85 | INTERSECTION = 0x600000, | ||
86 | ENDPOINT = 0x1, | ||
87 | STARTPOINT = 0x2, | ||
88 | OPEN = 0x3, // Puzzle loader flag - not to be written out | ||
89 | PATH = 0x4, // Generator use only | ||
90 | NO_POINT = 0x8, // Points that nothing connects to | ||
91 | GAP = 0x100000, | ||
92 | DOT = 0x20, | ||
93 | DOT_IS_BLUE = 0x100, | ||
94 | DOT_IS_ORANGE = 0x200, | ||
95 | DOT_IS_INVISIBLE = 0x1000, | ||
96 | DOT_SMALL = 0x2000, | ||
97 | DOT_MEDIUM = 0x4000, | ||
98 | DOT_LARGE = 0x8000, | ||
99 | }; | ||
100 | |||
101 | class Endpoint { | ||
102 | public: | ||
103 | enum Direction { | ||
104 | NONE = 0, | ||
105 | LEFT = 1, | ||
106 | RIGHT = 2, | ||
107 | UP = 4, | ||
108 | DOWN = 8, | ||
109 | UP_LEFT = 5, | ||
110 | UP_RIGHT = 6, | ||
111 | DOWN_LEFT = 9, | ||
112 | DOWN_RIGHT = 10 | ||
113 | }; | ||
114 | |||
115 | Endpoint(int x, int y, Direction dir, int flags) { | ||
116 | _x = x; | ||
117 | _y = y; | ||
118 | _dir = dir; | ||
119 | _flags = flags; | ||
120 | } | ||
121 | |||
122 | int GetX() { return _x; } | ||
123 | void SetX(int x) { _x = x; } | ||
124 | int GetY() { return _y; } | ||
125 | void SetY(int y) { _y = y; } | ||
126 | Direction GetDir() { return _dir; } | ||
127 | int GetFlags() { return _flags; } | ||
128 | void SetDir(Direction dir) { _dir = dir; } | ||
129 | |||
130 | private: | ||
131 | int _x, _y, _flags; | ||
132 | Direction _dir; | ||
133 | }; | ||
134 | |||
135 | struct Color { | ||
136 | float r; | ||
137 | float g; | ||
138 | float b; | ||
139 | float a; | ||
140 | friend bool operator<(const Color& lhs, const Color& rhs) { | ||
141 | return lhs.r * 8 + lhs.g * 4 + lhs.b * 2 + lhs.a > | ||
142 | rhs.r * 8 + rhs.g * 4 + rhs.b * 2 + rhs.a; | ||
143 | } | ||
144 | }; | ||
145 | |||
146 | struct SolutionPoint { | ||
147 | int pointA, pointB, pointC, pointD; | ||
148 | float f1x, f1y, f2x, f2y, f3x, f3y, f4x, f4y; | ||
149 | int endnum; | ||
150 | }; | ||
151 | |||
152 | class Panel { | ||
153 | public: | ||
154 | void SetSymbol(int x, int y, Decoration::Shape symbol, | ||
155 | Decoration::Color color); | ||
156 | void SetShape(int x, int y, int shape, bool rotate, bool negative, | ||
157 | Decoration::Color color); | ||
158 | void ClearSymbol(int x, int y); | ||
159 | void SetGridSymbol(int x, int y, Decoration::Shape symbol, | ||
160 | Decoration::Color color = Decoration::Color::None); | ||
161 | void ClearGridSymbol(int x, int y); | ||
162 | void Resize(int width, int height); | ||
163 | |||
164 | int width() const { return _width; } | ||
165 | int height() const { return _height; } | ||
166 | |||
167 | int get(int x, int y) { return _grid[x][y]; } | ||
168 | void set(int x, int y, int val) { _grid[x][y] = val; } | ||
169 | |||
170 | enum Style { | ||
171 | SYMMETRICAL = 0x2, // Not on the town symmetry puzzles? IDK why. | ||
172 | NO_BLINK = 0x4, | ||
173 | HAS_DOTS = 0x8, | ||
174 | IS_2COLOR = 0x10, | ||
175 | HAS_STARS = 0x40, | ||
176 | HAS_TRIANGLES = 0x80, | ||
177 | HAS_STONES = 0x100, | ||
178 | HAS_ERASERS = 0x1000, | ||
179 | HAS_SHAPERS = 0x2000, | ||
180 | IS_PIVOTABLE = 0x8000, | ||
181 | }; | ||
182 | |||
183 | enum Symmetry { // NOTE - Not all of these are valid symmetries for certain | ||
184 | // puzzles | ||
185 | None, | ||
186 | Horizontal, | ||
187 | Vertical, | ||
188 | Rotational, | ||
189 | RotateLeft, | ||
190 | RotateRight, | ||
191 | FlipXY, | ||
192 | FlipNegXY, | ||
193 | ParallelH, | ||
194 | ParallelV, | ||
195 | ParallelHFlip, | ||
196 | ParallelVFlip, | ||
197 | PillarParallel, | ||
198 | PillarHorizontal, | ||
199 | PillarVertical, | ||
200 | PillarRotational | ||
201 | }; | ||
202 | Symmetry symmetry; | ||
203 | |||
204 | float pathWidth; | ||
205 | enum ColorMode { | ||
206 | Default, | ||
207 | Reset, | ||
208 | Alternate, | ||
209 | WriteColors, | ||
210 | Treehouse, | ||
211 | TreehouseAlternate | ||
212 | }; | ||
213 | ColorMode colorMode; | ||
214 | bool decorationsOnly; | ||
215 | bool enableFlash; | ||
216 | |||
217 | Point get_sym_point(int x, int y, Symmetry symmetry) { | ||
218 | switch (symmetry) { | ||
219 | case None: | ||
220 | return Point(x, y); | ||
221 | case Symmetry::Horizontal: | ||
222 | return Point(x, _height - 1 - y); | ||
223 | case Symmetry::Vertical: | ||
224 | return Point(_width - 1 - x, y); | ||
225 | case Symmetry::Rotational: | ||
226 | return Point(_width - 1 - x, _height - 1 - y); | ||
227 | case Symmetry::RotateLeft: | ||
228 | return Point(y, _width - 1 - x); | ||
229 | case Symmetry::RotateRight: | ||
230 | return Point(_height - 1 - y, x); | ||
231 | case Symmetry::FlipXY: | ||
232 | return Point(y, x); | ||
233 | case Symmetry::FlipNegXY: | ||
234 | return Point(_height - 1 - y, _width - 1 - x); | ||
235 | case Symmetry::ParallelH: | ||
236 | return Point(x, y == _height / 2 | ||
237 | ? _height / 2 | ||
238 | : (y + (_height + 1) / 2) % (_height + 1)); | ||
239 | case Symmetry::ParallelV: | ||
240 | return Point(x == _width / 2 ? _width / 2 | ||
241 | : (x + (_width + 1) / 2) % (_width + 1), | ||
242 | y); | ||
243 | case Symmetry::ParallelHFlip: | ||
244 | return Point(_width - 1 - x, | ||
245 | y == _height / 2 | ||
246 | ? _height / 2 | ||
247 | : (y + (_height + 1) / 2) % (_height + 1)); | ||
248 | case Symmetry::ParallelVFlip: | ||
249 | return Point(x == _width / 2 ? _width / 2 | ||
250 | : (x + (_width + 1) / 2) % (_width + 1), | ||
251 | _height - 1 - y); | ||
252 | case Symmetry::PillarParallel: | ||
253 | return Point(x + _width / 2, y); | ||
254 | case Symmetry::PillarHorizontal: | ||
255 | return Point(x + _width / 2, _height - 1 - y); | ||
256 | case Symmetry::PillarVertical: | ||
257 | return Point(_width / 2 - x, y); | ||
258 | case Symmetry::PillarRotational: | ||
259 | return Point(_width / 2 - x, _height - 1 - y); | ||
260 | } | ||
261 | return Point(x, y); | ||
262 | } | ||
263 | |||
264 | Point get_sym_point(int x, int y) { return get_sym_point(x, y, symmetry); } | ||
265 | Point get_sym_point(Point p) { | ||
266 | return get_sym_point(p.first, p.second, symmetry); | ||
267 | } | ||
268 | Point get_sym_point(Point p, Symmetry symmetry) { | ||
269 | return get_sym_point(p.first, p.second, symmetry); | ||
270 | } | ||
271 | Endpoint::Direction get_sym_dir(Endpoint::Direction direction, | ||
272 | Symmetry symmetry) { | ||
273 | int dirIndex; | ||
274 | if (direction == Endpoint::Direction::LEFT) dirIndex = 0; | ||
275 | if (direction == Endpoint::Direction::RIGHT) dirIndex = 1; | ||
276 | if (direction == Endpoint::Direction::UP) dirIndex = 2; | ||
277 | if (direction == Endpoint::Direction::DOWN) dirIndex = 3; | ||
278 | std::vector<Endpoint::Direction> mapping; | ||
279 | switch (symmetry) { | ||
280 | case Symmetry::Horizontal: | ||
281 | mapping = {Endpoint::Direction::LEFT, Endpoint::Direction::RIGHT, | ||
282 | Endpoint::Direction::DOWN, Endpoint::Direction::UP}; | ||
283 | break; | ||
284 | case Symmetry::Vertical: | ||
285 | mapping = {Endpoint::Direction::RIGHT, Endpoint::Direction::LEFT, | ||
286 | Endpoint::Direction::UP, Endpoint::Direction::DOWN}; | ||
287 | break; | ||
288 | case Symmetry::Rotational: | ||
289 | mapping = {Endpoint::Direction::RIGHT, Endpoint::Direction::LEFT, | ||
290 | Endpoint::Direction::DOWN, Endpoint::Direction::UP}; | ||
291 | break; | ||
292 | case Symmetry::RotateLeft: | ||
293 | mapping = {Endpoint::Direction::DOWN, Endpoint::Direction::UP, | ||
294 | Endpoint::Direction::LEFT, Endpoint::Direction::RIGHT}; | ||
295 | break; | ||
296 | case Symmetry::RotateRight: | ||
297 | mapping = {Endpoint::Direction::UP, Endpoint::Direction::DOWN, | ||
298 | Endpoint::Direction::RIGHT, Endpoint::Direction::LEFT}; | ||
299 | break; | ||
300 | case Symmetry::FlipXY: | ||
301 | mapping = {Endpoint::Direction::UP, Endpoint::Direction::DOWN, | ||
302 | Endpoint::Direction::LEFT, Endpoint::Direction::RIGHT}; | ||
303 | break; | ||
304 | case Symmetry::FlipNegXY: | ||
305 | mapping = {Endpoint::Direction::DOWN, Endpoint::Direction::UP, | ||
306 | Endpoint::Direction::RIGHT, Endpoint::Direction::LEFT}; | ||
307 | break; | ||
308 | case Symmetry::ParallelH: | ||
309 | mapping = {Endpoint::Direction::LEFT, Endpoint::Direction::RIGHT, | ||
310 | Endpoint::Direction::UP, Endpoint::Direction::DOWN}; | ||
311 | break; | ||
312 | case Symmetry::ParallelV: | ||
313 | mapping = {Endpoint::Direction::LEFT, Endpoint::Direction::RIGHT, | ||
314 | Endpoint::Direction::UP, Endpoint::Direction::DOWN}; | ||
315 | break; | ||
316 | case Symmetry::ParallelHFlip: | ||
317 | mapping = {Endpoint::Direction::RIGHT, Endpoint::Direction::LEFT, | ||
318 | Endpoint::Direction::UP, Endpoint::Direction::DOWN}; | ||
319 | break; | ||
320 | case Symmetry::ParallelVFlip: | ||
321 | mapping = {Endpoint::Direction::LEFT, Endpoint::Direction::RIGHT, | ||
322 | Endpoint::Direction::DOWN, Endpoint::Direction::UP}; | ||
323 | break; | ||
324 | default: | ||
325 | mapping = {Endpoint::Direction::LEFT, Endpoint::Direction::RIGHT, | ||
326 | Endpoint::Direction::UP, Endpoint::Direction::DOWN}; | ||
327 | break; | ||
328 | } | ||
329 | return mapping[dirIndex]; | ||
330 | } | ||
331 | |||
332 | int get_num_grid_points() { return ((_width + 1) / 2) * ((_height + 1) / 2); } | ||
333 | int get_num_grid_blocks() { return (_width / 2) * (_height / 2); } | ||
334 | int get_parity() { return (get_num_grid_points() + 1) % 2; } | ||
335 | |||
336 | private: | ||
337 | std::pair<int, int> loc_to_xy(int location) { | ||
338 | int height2 = (_height - 1) / 2; | ||
339 | int width2 = (_width + 1) / 2; | ||
340 | |||
341 | int x = 2 * (location % width2); | ||
342 | int y = 2 * (height2 - location / width2); | ||
343 | return {x, y}; | ||
344 | } | ||
345 | |||
346 | int xy_to_loc(int x, int y) { | ||
347 | int height2 = (_height - 1) / 2; | ||
348 | int width2 = (_width + 1) / 2; | ||
349 | |||
350 | int rowsFromBottom = height2 - y / 2; | ||
351 | return rowsFromBottom * width2 + x / 2; | ||
352 | } | ||
353 | |||
354 | std::pair<int, int> dloc_to_xy(int location) { | ||
355 | int height2 = (_height - 3) / 2; | ||
356 | int width2 = _width / 2; | ||
357 | |||
358 | int x = 2 * (location % width2) + 1; | ||
359 | int y = 2 * (height2 - location / width2) + 1; | ||
360 | return {x, y}; | ||
361 | } | ||
362 | |||
363 | int xy_to_dloc(int x, int y) { | ||
364 | int height2 = (_height - 3) / 2; | ||
365 | int width2 = _width / 2; | ||
366 | |||
367 | int rowsFromBottom = height2 - (y - 1) / 2; | ||
368 | return rowsFromBottom * width2 + (x - 1) / 2; | ||
369 | } | ||
370 | |||
371 | int locate_segment(int x, int y, std::vector<int>& connections_a, | ||
372 | std::vector<int>& connections_b) { | ||
373 | for (int i = 0; i < connections_a.size(); i++) { | ||
374 | std::pair<int, int> coord1 = loc_to_xy(connections_a[i]); | ||
375 | std::pair<int, int> coord2 = loc_to_xy(connections_b[i]); | ||
376 | int x1 = coord1.first, y1 = coord1.second, x2 = coord2.first, | ||
377 | y2 = coord2.second; | ||
378 | if ((x1 == x - 1 && x2 == x + 1 && y1 == y && y2 == y) || | ||
379 | (y1 == y - 1 && y2 == y + 1 && x1 == x && x2 == x)) { | ||
380 | return i; | ||
381 | } | ||
382 | } | ||
383 | return -1; | ||
384 | } | ||
385 | |||
386 | bool break_segment(int x, int y, std::vector<int>& connections_a, | ||
387 | std::vector<int>& connections_b, | ||
388 | std::vector<float>& intersections, | ||
389 | std::vector<int>& intersectionFlags) { | ||
390 | int i = locate_segment(x, y, connections_a, connections_b); | ||
391 | if (i == -1) { | ||
392 | return false; | ||
393 | } | ||
394 | int other_connection = connections_b[i]; | ||
395 | connections_b[i] = static_cast<int>(intersectionFlags.size()); | ||
396 | connections_a.push_back(static_cast<int>(intersectionFlags.size())); | ||
397 | connections_b.push_back(other_connection); | ||
398 | intersections.push_back(static_cast<float>(minx + x * unitWidth)); | ||
399 | intersections.push_back( | ||
400 | static_cast<float>(miny + (_height - 1 - y) * unitHeight)); | ||
401 | intersectionFlags.push_back(_grid[x][y]); | ||
402 | return true; | ||
403 | } | ||
404 | |||
405 | bool break_segment_gap(int x, int y, std::vector<int>& connections_a, | ||
406 | std::vector<int>& connections_b, | ||
407 | std::vector<float>& intersections, | ||
408 | std::vector<int>& intersectionFlags) { | ||
409 | int i = locate_segment(x, y, connections_a, connections_b); | ||
410 | if (i == -1) { | ||
411 | return false; | ||
412 | } | ||
413 | int other_connection = connections_b[i]; | ||
414 | connections_b[i] = static_cast<int>(intersectionFlags.size() + 1); | ||
415 | connections_a.push_back(other_connection); | ||
416 | connections_b.push_back(static_cast<int>(intersectionFlags.size())); | ||
417 | if (!(_grid[x][y] & IntersectionFlags::GAP)) { | ||
418 | _grid[x][y] |= | ||
419 | (x % 2 == 0 ? IntersectionFlags::COLUMN : IntersectionFlags::ROW); | ||
420 | connections_a.push_back(static_cast<int>(intersectionFlags.size())); | ||
421 | connections_b.push_back(static_cast<int>(intersectionFlags.size() + 1)); | ||
422 | } | ||
423 | double xOffset = _grid[x][y] & IntersectionFlags::ROW ? 0.5 : 0; | ||
424 | double yOffset = _grid[x][y] & IntersectionFlags::COLUMN ? 0.5 : 0; | ||
425 | intersections.push_back( | ||
426 | static_cast<float>(minx + (x + xOffset) * unitWidth)); | ||
427 | intersections.push_back( | ||
428 | static_cast<float>(miny + (_height - 1 - y - yOffset) * unitHeight)); | ||
429 | intersections.push_back( | ||
430 | static_cast<float>(minx + (x - xOffset) * unitWidth)); | ||
431 | intersections.push_back( | ||
432 | static_cast<float>(miny + (_height - 1 - y + yOffset) * unitHeight)); | ||
433 | intersectionFlags.push_back(_grid[x][y]); | ||
434 | intersectionFlags.push_back(_grid[x][y]); | ||
435 | return true; | ||
436 | } | ||
437 | |||
438 | int _width, _height; | ||
439 | |||
440 | std::vector<std::vector<int>> _grid; | ||
441 | std::vector<Point> _startpoints; | ||
442 | std::vector<Endpoint> _endpoints; | ||
443 | float minx, miny, maxx, maxy, unitWidth, unitHeight; | ||
444 | int _style; | ||
445 | bool _resized; | ||
446 | }; | ||
447 | |||
448 | #endif /* end of include guard: PANEL_H_FC471D68 */ | ||
diff --git a/ext/wittle_generator/PuzzleSymbols.h b/ext/wittle_generator/PuzzleSymbols.h new file mode 100644 index 0000000..dedf61a --- /dev/null +++ b/ext/wittle_generator/PuzzleSymbols.h | |||
@@ -0,0 +1,59 @@ | |||
1 | #ifndef PUZZLESYMBOLS_H_9DD95900 | ||
2 | #define PUZZLESYMBOLS_H_9DD95900 | ||
3 | |||
4 | #include <map> | ||
5 | #include <vector> | ||
6 | |||
7 | #include "Panel.h" | ||
8 | #include "Random.h" | ||
9 | |||
10 | struct PuzzleSymbols { | ||
11 | std::map<int, std::vector<std::pair<int, int>>> symbols; | ||
12 | std::vector<std::pair<int, int>>& operator[](int symbolType) { | ||
13 | return symbols[symbolType]; | ||
14 | } | ||
15 | int style; | ||
16 | int getNum(int symbolType) { | ||
17 | int total = 0; | ||
18 | for (auto& pair : symbols[symbolType]) total += pair.second; | ||
19 | return total; | ||
20 | } | ||
21 | bool any(int symbolType) { return symbols[symbolType].size() > 0; } | ||
22 | int popRandomSymbol() { | ||
23 | std::vector<int> types; | ||
24 | for (auto& pair : symbols) | ||
25 | if (pair.second.size() > 0 && pair.first != Decoration::Start && | ||
26 | pair.first != Decoration::Exit && pair.first != Decoration::Gap && | ||
27 | pair.first != Decoration::Eraser) | ||
28 | types.push_back(pair.first); | ||
29 | int randType = types[Random::rand() % types.size()]; | ||
30 | int randIndex = Random::rand() % symbols[randType].size(); | ||
31 | while (symbols[randType][randIndex].second == 0 || | ||
32 | symbols[randType][randIndex].second >= 25) { | ||
33 | randType = types[Random::rand() % types.size()]; | ||
34 | randIndex = Random::rand() % symbols[randType].size(); | ||
35 | } | ||
36 | symbols[randType][randIndex].second--; | ||
37 | return symbols[randType][randIndex].first; | ||
38 | } | ||
39 | PuzzleSymbols(std::vector<std::pair<int, int>> symbolVec) { | ||
40 | for (std::pair<int, int> s : symbolVec) { | ||
41 | if (s.first == Decoration::Gap || s.first == Decoration::Start || | ||
42 | s.first == Decoration::Exit) | ||
43 | symbols[s.first].push_back(s); | ||
44 | else if (s.first & Decoration::Dot) | ||
45 | symbols[Decoration::Dot].push_back(s); | ||
46 | else | ||
47 | symbols[s.first & 0x700].push_back(s); | ||
48 | } | ||
49 | style = 0; | ||
50 | if (any(Decoration::Dot)) style |= Panel::Style::HAS_DOTS; | ||
51 | if (any(Decoration::Stone)) style |= Panel::Style::HAS_STONES; | ||
52 | if (any(Decoration::Star)) style |= Panel::Style::HAS_STARS; | ||
53 | if (any(Decoration::Poly)) style |= Panel::Style::HAS_SHAPERS; | ||
54 | if (any(Decoration::Triangle)) style |= Panel::Style::HAS_TRIANGLES; | ||
55 | if (any(Decoration::Arrow)) style |= Panel::Style::HAS_TRIANGLES; | ||
56 | } | ||
57 | }; | ||
58 | |||
59 | #endif /* end of include guard: PUZZLESYMBOLS_H_9DD95900 */ | ||
diff --git a/ext/wittle_generator/Random.cpp b/ext/wittle_generator/Random.cpp new file mode 100644 index 0000000..2248947 --- /dev/null +++ b/ext/wittle_generator/Random.cpp | |||
@@ -0,0 +1,5 @@ | |||
1 | #include "Random.h" | ||
2 | |||
3 | #include <time.h> | ||
4 | |||
5 | std::mt19937 Random::gen = std::mt19937((int)time(0)); | ||
diff --git a/ext/wittle_generator/Random.h b/ext/wittle_generator/Random.h new file mode 100644 index 0000000..115c62d --- /dev/null +++ b/ext/wittle_generator/Random.h | |||
@@ -0,0 +1,16 @@ | |||
1 | #ifndef RANDOM_H_F3423EE7 | ||
2 | #define RANDOM_H_F3423EE7 | ||
3 | |||
4 | #include <stdlib.h> | ||
5 | |||
6 | #include <random> | ||
7 | |||
8 | struct Random { | ||
9 | static std::mt19937 gen; | ||
10 | |||
11 | static void seed(int val) { gen = std::mt19937(val); } | ||
12 | |||
13 | static int rand() { return abs((int)gen()); } | ||
14 | }; | ||
15 | |||
16 | #endif /* end of include guard: RANDOM_H_F3423EE7 */ | ||
diff --git a/ext/wittle_generator/Test.cpp b/ext/wittle_generator/Test.cpp new file mode 100644 index 0000000..6139215 --- /dev/null +++ b/ext/wittle_generator/Test.cpp | |||
@@ -0,0 +1,10 @@ | |||
1 | #include "Generate.h" | ||
2 | |||
3 | int main(int, char**) { | ||
4 | Generate generator; | ||
5 | generator.setSymbol(Decoration::Start, 0, 4 * 2); | ||
6 | generator.setSymbol(Decoration::Exit, 4 * 2, 0); | ||
7 | generator.generate(4 * 2 + 1, 4 * 2 + 1, {{{Decoration::Triangle, 6}}}); | ||
8 | |||
9 | return 0; | ||
10 | } | ||
diff --git a/lib/keep b/lib/keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/keep | |||