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