about summary refs log tree commit diff stats
path: root/ext/wittle_generator
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wittle_generator')
-rw-r--r--ext/wittle_generator/.clang-format2
-rw-r--r--ext/wittle_generator/CMakeLists.txt6
-rw-r--r--ext/wittle_generator/Generate.cpp2056
-rw-r--r--ext/wittle_generator/Generate.h248
-rw-r--r--ext/wittle_generator/Panel.cpp116
-rw-r--r--ext/wittle_generator/Panel.h448
-rw-r--r--ext/wittle_generator/PuzzleSymbols.h59
-rw-r--r--ext/wittle_generator/Random.cpp5
-rw-r--r--ext/wittle_generator/Random.h16
-rw-r--r--ext/wittle_generator/Test.cpp10
10 files changed, 2966 insertions, 0 deletions
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 @@
1cmake_minimum_required (VERSION 3.1)
2project (wittle_generator)
3
4add_executable(wittle_generator Generate.cpp Panel.cpp Random.cpp Test.cpp)
5set_property(TARGET wittle_generator PROPERTY CXX_STANDARD 17)
6set_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
5std::vector<Point> Generate::_DIRECTIONS1 = {Point(0, 1), Point(0, -1),
6 Point(1, 0), Point(-1, 0)};
7std::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)};
10std::vector<Point> Generate::_DIRECTIONS2 = {Point(0, 2), Point(0, -2),
11 Point(2, 0), Point(-2, 0)};
12std::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)};
15std::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};
24std::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.
38void 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
45void 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
94void 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.
119void 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.
132void 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
153void 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.
181void 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.
198void 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.
213void 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)
239void 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
252void 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.
266bool 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
431void 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.
442bool 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.
511bool 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.
579bool 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.
645bool 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.
672bool 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.
703bool 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
794bool 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
844void 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
857Point 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)
875std::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)
898std::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
903std::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
913bool 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
959bool 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.
1009bool 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
1059bool 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.
1084bool 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
1126bool 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.
1206bool 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
1215bool 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
1289Shape 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
1315int 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
1378bool 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)
1650int 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
1661bool 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
1693bool 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
1700bool 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
1718bool 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)
1773int 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
1787bool 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)
1821int 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
1833bool 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
2002bool 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
2056bool 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
16typedef std::set<Point> Shape;
17
18// The main class for generating puzzles.
19class 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
6template <class T>
7int 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
14void 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
38void 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
56void Panel::ClearSymbol(int x, int y) { ClearGridSymbol(x * 2 + 1, y * 2 + 1); }
57
58void 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
92void Panel::ClearGridSymbol(int x, int y) { _grid[x][y] = 0; }
93
94void 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
9struct 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
37class 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
82enum 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
101class 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
135struct 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
146struct SolutionPoint {
147 int pointA, pointB, pointC, pointD;
148 float f1x, f1y, f2x, f2y, f3x, f3y, f4x, f4y;
149 int endnum;
150};
151
152class 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
10struct 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
5std::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
8struct 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
3int 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}