1 /***********************************************************************
2 *
3 * Copyright (C) 2007, 2008, 2012, 2014 Graeme Gott <graeme@gottcode.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***********************************************************************/
19
20 #include "maze.h"
21
22 #include <QDataStream>
23 #include <QSettings>
24
25 // ============================================================================
26
27 // Hunt and Kill algorithm
28 // ============================================================================
29
generate()30 void HuntAndKillMaze::generate()
31 {
32 m_visited = QVector< QVector<bool> >(columns(), QVector<bool>(rows()));
33 m_unvisited = columns() * rows();
34
35 QPoint current(0, randomInt(rows()));
36 m_visited[current.x()][current.y()] = true;
37 m_unvisited--;
38
39 QPoint neighbor;
40 while (m_unvisited) {
41 neighbor = randomNeighbor(m_visited, current);
42 if (neighbor.x() != -1) {
43 mergeCells(current, neighbor);
44 current = neighbor;
45 m_unvisited--;
46 } else {
47 current = hunt();
48 }
49 }
50
51 m_visited.clear();
52 }
53
54 // ============================================================================
55
hunt()56 QPoint HuntAndKillMaze::hunt()
57 {
58 static QPoint direction[4] = {
59 QPoint(1, 0),
60 QPoint(0, 1),
61 QPoint(-1, 0),
62 QPoint(0, -1)
63 };
64
65 QPoint cell, next;
66 for (int c = 0; c < columns(); ++c) {
67 cell.setX(c);
68 for (int r = 0; r < rows(); ++r) {
69 cell.setY(r);
70 if (m_visited.at(c).at(r)) {
71 continue;
72 }
73 for (int d = 0; d < 4; ++d) {
74 next = cell + direction[d];
75 if (next.x() < 0 ||
76 next.x() >= columns() ||
77 next.y() < 0 ||
78 next.y() >= rows()) {
79 continue;
80 }
81 if (m_visited.at(next.x()).at(next.y())) {
82 mergeCells(cell, next);
83 m_visited[c][r] = true;
84 m_unvisited--;
85 return cell;
86 }
87 }
88 }
89 }
90 return QPoint(-1, -1);
91 }
92
93 // ============================================================================
94
95 // Kruskal's algorithm
96 // ============================================================================
97
generate()98 void KruskalMaze::generate()
99 {
100 // Generate sets
101 m_set_ids = QVector< QVector<Set*> >(columns(), QVector<Set*>(rows()));
102 for (int c = 0; c < columns(); ++c) {
103 for (int r = 0; r < rows(); ++r) {
104 m_sets.append(QList<QPoint>() << QPoint(c, r));
105 m_set_ids[c][r] = &m_sets.last();
106 }
107 }
108
109 while (m_sets.size() > 1) {
110 Set* set1 = &m_sets.first();
111
112 // Find random cell
113 const QPoint& cell = set1->at(randomInt(set1->size()));
114
115 // Find random neighbor of cell
116 QPoint cell2(cell);
117 if (randomInt(2)) {
118 cell2.rx()++;
119 } else {
120 cell2.ry()++;
121 }
122 if (cell2.x() >= columns() || cell2.y() >= rows()) {
123 continue;
124 }
125
126 // Find set containing second cell
127 Set* set2 = m_set_ids.at(cell2.x()).at(cell2.y());
128
129 // Merge sets if they are different
130 if (set1 != set2) {
131 mergeCells(cell, cell2);
132 int size = set1->size();
133 for (int i = 0; i < size; ++i) {
134 const QPoint& cell3 = set1->at(i);
135 m_set_ids[cell3.x()][cell3.y()] = set2;
136 }
137 *set2 += *set1;
138 m_sets.removeFirst();
139 }
140 }
141
142 m_sets.clear();
143 m_set_ids.clear();
144 }
145
146 // ============================================================================
147
148 // Prim's algorithm
149 // ============================================================================
150
generate()151 void PrimMaze::generate()
152 {
153 // Generate cell lists
154 m_regions = QVector< QVector<int> >(columns(), QVector<int>(rows(), 0));
155
156 // Move first cell
157 QPoint cell(0, randomInt(columns()));
158 m_regions[0][cell.y()] = 2;
159 moveNeighbors(cell);
160
161 // Move remaining cells
162 while (!m_frontier.isEmpty()) {
163 cell = m_frontier.takeAt(randomInt(m_frontier.size()));
164 mergeRandomNeighbor(cell);
165 m_regions[cell.x()][cell.y()] = 2;
166 moveNeighbors(cell);
167 }
168
169 m_regions.clear();
170 }
171
172 // ============================================================================
173
moveNeighbors(const QPoint & cell)174 void PrimMaze::moveNeighbors(const QPoint& cell)
175 {
176 QList<QPoint> n = neighbors(cell);
177 for (int i = 0; i < n.size(); ++i) {
178 const QPoint& current = n.at(i);
179 int& ref = m_regions[current.x()][current.y()];
180 if (ref == 0) {
181 ref = 1;
182 m_frontier.append(current);
183 }
184 }
185 }
186
187 // ============================================================================
188
mergeRandomNeighbor(const QPoint & cell)189 void PrimMaze::mergeRandomNeighbor(const QPoint& cell)
190 {
191 QList<QPoint> cells;
192
193 QList<QPoint> n = neighbors(cell);
194 for (int i = 0; i < n.size(); ++i) {
195 const QPoint& current = n.at(i);
196 if (m_regions.at(current.x()).at(current.y()) == 2) {
197 cells.append(current);
198 }
199 }
200
201 mergeCells( cell, cells.at(randomInt(cells.size())) );
202 }
203
204 // ============================================================================
205
neighbors(const QPoint & cell)206 QList<QPoint> PrimMaze::neighbors(const QPoint& cell)
207 {
208 QList<QPoint> n;
209 if (cell.x() > 0) {
210 n.append(cell + QPoint(-1, 0));
211 }
212 if (cell.y() > 0) {
213 n.append(cell + QPoint(0, -1));
214 }
215 if (cell.y() < rows() - 1) {
216 n.append(cell + QPoint(0, 1));
217 }
218 if (cell.x() < columns() - 1) {
219 n.append(cell + QPoint(1, 0));
220 }
221 return n;
222 }
223
224 // ============================================================================
225
226 // Recursive Backtracker algorithm
227 // ============================================================================
228
generate()229 void RecursiveBacktrackerMaze::generate()
230 {
231 m_visited = QVector< QVector<bool> >(columns(), QVector<bool>(rows()));
232
233 QPoint start(0, randomInt(rows()));
234 m_visited[start.x()][start.y()] = true;
235 makePath(start);
236
237 m_visited.clear();
238 }
239
240 // ============================================================================
241
makePath(const QPoint & current)242 void RecursiveBacktrackerMaze::makePath(const QPoint& current)
243 {
244 QPoint neighbor;
245 while ( (neighbor = randomNeighbor(m_visited, current)).x() != -1 ) {
246 mergeCells(current, neighbor);
247 makePath(neighbor);
248 }
249 }
250
251 // ============================================================================
252
253 // Stack algorithm
254 // ============================================================================
255
generate()256 void StackMaze::generate()
257 {
258 // Generate cell lists
259 m_visited = QVector< QVector<bool> >(columns(), QVector<bool>(rows()));
260 QList<QPoint> active;
261
262 // Start maze
263 QPoint start(0, randomInt(rows()));
264 m_visited[start.x()][start.y()] = true;
265 active.append(start);
266
267 // Loop through active list
268 QPoint cell, neighbor;
269 int pos;
270 while (!active.isEmpty()) {
271 pos = nextActive(active.size());
272 cell = active.at(pos);
273 neighbor = randomNeighbor(m_visited, cell);
274 if (neighbor.x() != -1) {
275 mergeCells(cell, neighbor);
276 active.append(neighbor);
277 } else {
278 active.takeAt(pos);
279 }
280 }
281
282 m_visited.clear();
283 }
284
285 // ============================================================================
286
nextActive(int size)287 int StackMaze::nextActive(int size)
288 {
289 return size - 1;
290 }
291
292 // ============================================================================
293
nextActive(int size)294 int Stack2Maze::nextActive(int size)
295 {
296 if (randomInt(2) != 0) {
297 return size - 1;
298 } else {
299 return randomInt(size);
300 }
301 }
302
303 // ============================================================================
304
nextActive(int size)305 int Stack3Maze::nextActive(int size)
306 {
307 return randomInt(size);
308 }
309
310 // ============================================================================
311
nextActive(int size)312 int Stack4Maze::nextActive(int size)
313 {
314 int recent = 3 < size ? 3 : size;
315 return size - (randomInt(recent)) - 1;
316 }
317
318 // ============================================================================
319
nextActive(int size)320 int Stack5Maze::nextActive(int size)
321 {
322 switch (randomInt(3)) {
323 case 0:
324 return 0;
325 case 1:
326 return randomInt(size);
327 case 2:
328 default:
329 return size - 1;
330 }
331 }
332
333 // ============================================================================
334
335 // Maze class
336 // ============================================================================
337
generate(int columns,int rows,std::mt19937 & random)338 void Maze::generate(int columns, int rows, std::mt19937& random)
339 {
340 m_random = random;
341 m_columns = columns;
342 m_rows = rows;
343 m_cells = QVector< QVector<Cell> >(m_columns, QVector<Cell>(m_rows));
344 generate();
345 random = m_random;
346 }
347
348 // ============================================================================
349
load()350 bool Maze::load()
351 {
352 // Read data from disk
353 QByteArray data = QSettings().value("Current/Progress").toByteArray();
354 if (data.isEmpty()) {
355 return false;
356 }
357
358 // Decompress data
359 data = qUncompress(data);
360 if (data.isEmpty()) {
361 return false;
362 }
363
364 // Deserialize data
365 QDataStream stream(&data, QIODevice::ReadOnly);
366 stream.setVersion(QDataStream::Qt_4_3);
367 for (int c = 0; c < m_columns; ++c) {
368 for (int r = 0; r < m_rows; ++r) {
369 stream >> m_cells[c][r];
370 if (stream.status() != QDataStream::Ok) {
371 return false;
372 }
373 }
374 }
375
376 if (!stream.atEnd() || stream.status() != QDataStream::Ok) {
377 return false;
378 }
379
380 return true;
381 }
382
383 // ============================================================================
384
save() const385 void Maze::save() const
386 {
387 // Serialize data
388 QByteArray data;
389 QDataStream stream(&data, QIODevice::WriteOnly);
390 stream.setVersion(QDataStream::Qt_4_3);
391 for (int c = 0; c < m_columns; ++c) {
392 for (int r = 0; r < m_rows; ++r) {
393 stream << m_cells[c][r];
394 }
395 }
396
397 // Compress data
398 data = qCompress(data, 9);
399
400 // Write data to disk
401 QSettings().setValue("Current/Progress", data);
402 }
403
404 // ============================================================================
405
mergeCells(const QPoint & cell1,const QPoint & cell2)406 void Maze::mergeCells(const QPoint& cell1, const QPoint& cell2)
407 {
408 if (cell1.y() == cell2.y()) {
409 if (cell2.x() > cell1.x()) {
410 m_cells[cell1.x()][cell1.y()].removeRightWall();
411 m_cells[cell2.x()][cell2.y()].removeLeftWall();
412 } else if (cell2.x() < cell1.x()) {
413 m_cells[cell1.x()][cell1.y()].removeLeftWall();
414 m_cells[cell2.x()][cell2.y()].removeRightWall();
415 }
416 } else if (cell1.x() == cell2.x()) {
417 if (cell2.y() > cell1.y()) {
418 m_cells[cell1.x()][cell1.y()].removeBottomWall();
419 m_cells[cell2.x()][cell2.y()].removeTopWall();
420 } else if (cell2.y() < cell1.y()) {
421 m_cells[cell1.x()][cell1.y()].removeTopWall();
422 m_cells[cell2.x()][cell2.y()].removeBottomWall();
423 }
424 }
425 }
426
427 // ============================================================================
428
randomNeighbor(QVector<QVector<bool>> & visited,const QPoint & cell)429 QPoint Maze::randomNeighbor(QVector<QVector<bool>>& visited, const QPoint& cell)
430 {
431 // Find unvisited neighbors
432 QPoint neighbors[4];
433 int found = 0;
434 if (cell.x() > 0) {
435 QPoint n(cell.x() - 1, cell.y());
436 if (visited.at(n.x()).at(n.y()) == false) {
437 neighbors[found] = n;
438 found++;
439 }
440 }
441 if (cell.y() > 0) {
442 QPoint n(cell.x(), cell.y() - 1);
443 if (visited.at(n.x()).at(n.y()) == false) {
444 neighbors[found] = n;
445 found++;
446 }
447 }
448 if (cell.y() < visited.at(cell.x()).size() - 1) {
449 QPoint n(cell.x(), cell.y() + 1);
450 if (visited.at(n.x()).at(n.y()) == false) {
451 neighbors[found] = n;
452 found++;
453 }
454 }
455 if (cell.x() < visited.size() - 1) {
456 QPoint n(cell.x() + 1, cell.y());
457 if (visited.at(n.x()).at(n.y()) == false) {
458 neighbors[found] = n;
459 found++;
460 }
461 }
462
463 // Return random neighbor
464 if (found) {
465 const QPoint& n = neighbors[randomInt(found)];
466 visited[n.x()][n.y()] = true;
467 return n;
468 } else {
469 return QPoint(-1,-1);
470 }
471 }
472
473 // ============================================================================
474