about summary refs log tree commit diff stats
path: root/ext/wittle_generator/Generate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wittle_generator/Generate.cpp')
-rw-r--r--ext/wittle_generator/Generate.cpp2056
1 files changed, 2056 insertions, 0 deletions
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; }