You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
5.8 KiB
220 lines
5.8 KiB
8 years ago
|
/*
|
||
|
OneLoneCoder.com - Recursive Backtracker Maze Algorithm
|
||
|
"Get lost..." - @Javidx9
|
||
|
|
||
|
Disclaimer
|
||
|
~~~~~~~~~~
|
||
|
I don't care what you use this for. It's intended to be educational, and perhaps
|
||
|
to the oddly minded - a little bit of fun. Please hack this, change it and use it
|
||
|
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
|
||
|
bad that happens as a result of your actions. However, if good stuff happens, I
|
||
|
would appreciate a shout out, or at least give the blog some publicity for me.
|
||
|
Cheers!
|
||
|
|
||
|
Background
|
||
|
~~~~~~~~~~
|
||
|
I really like perfect algorithms. This one shows how to generate a maze that guarantees
|
||
|
all cells can reach all other cells, it just may take some time to get there. I introduce
|
||
|
stacks, and show how the algorithm generates the maze visually.
|
||
|
|
||
|
Author
|
||
|
~~~~~~
|
||
|
Twitter: @javidx9
|
||
|
Blog: www.onelonecoder.com
|
||
|
|
||
|
Video:
|
||
|
~~~~~~
|
||
|
https://youtu.be/Y37-gB83HKE
|
||
|
|
||
|
Last Updated: 10/07/2017
|
||
|
*/
|
||
|
|
||
|
#include <iostream>
|
||
|
#include <stack>
|
||
|
using namespace std;
|
||
|
|
||
|
#include "olcConsoleGameEngine.h"
|
||
|
|
||
|
class OneLoneCoder_Maze : public olcConsoleGameEngine
|
||
|
{
|
||
|
public:
|
||
|
OneLoneCoder_Maze()
|
||
|
{
|
||
|
m_sAppName = L"MAZE";
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
int m_nMazeWidth;
|
||
|
int m_nMazeHeight;
|
||
|
int *m_maze;
|
||
|
|
||
|
|
||
|
// Some bit fields for convenience
|
||
|
enum
|
||
|
{
|
||
|
CELL_PATH_N = 0x01,
|
||
|
CELL_PATH_E = 0x02,
|
||
|
CELL_PATH_S = 0x04,
|
||
|
CELL_PATH_W = 0x08,
|
||
|
CELL_VISITED = 0x10,
|
||
|
};
|
||
|
|
||
|
|
||
|
// Algorithm variables
|
||
|
int m_nVisitedCells;
|
||
|
stack<pair<int, int>> m_stack; // (x, y) coordinate pairs
|
||
|
int m_nPathWidth;
|
||
|
|
||
|
|
||
|
protected:
|
||
|
// Called by olcConsoleGameEngine
|
||
|
virtual bool OnUserCreate()
|
||
|
{
|
||
|
// Maze parameters
|
||
|
m_nMazeWidth = 40;
|
||
|
m_nMazeHeight = 25;
|
||
|
m_maze = new int[m_nMazeWidth * m_nMazeHeight];
|
||
|
memset(m_maze, 0x00, m_nMazeWidth * m_nMazeHeight * sizeof(int));
|
||
|
m_nPathWidth = 3;
|
||
|
|
||
|
// Choose a starting cell
|
||
|
int x = rand() % m_nMazeWidth;
|
||
|
int y = rand() % m_nMazeHeight;
|
||
|
m_stack.push(make_pair(x, y));
|
||
|
m_maze[y * m_nMazeWidth + x] = CELL_VISITED;
|
||
|
m_nVisitedCells = 1;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Called by olcConsoleGameEngine
|
||
|
virtual bool OnUserUpdate(float fElapsedTime)
|
||
|
{
|
||
|
// Slow down for animation
|
||
|
this_thread::sleep_for(10ms);
|
||
|
|
||
|
// Little lambda function to calculate index in a readable way
|
||
|
auto offset = [&](int x, int y)
|
||
|
{
|
||
|
return (m_stack.top().second + y) * m_nMazeWidth + (m_stack.top().first + x);
|
||
|
};
|
||
|
|
||
|
// Do Maze Algorithm
|
||
|
if (m_nVisitedCells < m_nMazeWidth * m_nMazeHeight)
|
||
|
{
|
||
|
// Create a set of unvisted neighbours
|
||
|
vector<int> neighbours;
|
||
|
|
||
|
// North neighbour
|
||
|
if (m_stack.top().second > 0 && (m_maze[offset(0, -1)] & CELL_VISITED) == 0)
|
||
|
neighbours.push_back(0);
|
||
|
// East neighbour
|
||
|
if (m_stack.top().first < m_nMazeWidth - 1 && (m_maze[offset(1, 0)] & CELL_VISITED) == 0)
|
||
|
neighbours.push_back(1);
|
||
|
// South neighbour
|
||
|
if (m_stack.top().second < m_nMazeHeight - 1 && (m_maze[offset(0, 1)] & CELL_VISITED) == 0)
|
||
|
neighbours.push_back(2);
|
||
|
// West neighbour
|
||
|
if (m_stack.top().first > 0 && (m_maze[offset(-1, 0)] & CELL_VISITED) == 0)
|
||
|
neighbours.push_back(3);
|
||
|
|
||
|
// Are there any neighbours available?
|
||
|
if (!neighbours.empty())
|
||
|
{
|
||
|
// Choose one available neighbour at random
|
||
|
int next_cell_dir = neighbours[rand() % neighbours.size()];
|
||
|
|
||
|
// Create a path between the neighbour and the current cell
|
||
|
switch (next_cell_dir)
|
||
|
{
|
||
|
case 0: // North
|
||
|
m_maze[offset(0, -1)] |= CELL_VISITED | CELL_PATH_S;
|
||
|
m_maze[offset(0, 0)] |= CELL_PATH_N;
|
||
|
m_stack.push(make_pair((m_stack.top().first + 0), (m_stack.top().second - 1)));
|
||
|
break;
|
||
|
|
||
|
case 1: // East
|
||
|
m_maze[offset(+1, 0)] |= CELL_VISITED | CELL_PATH_W;
|
||
|
m_maze[offset( 0, 0)] |= CELL_PATH_E;
|
||
|
m_stack.push(make_pair((m_stack.top().first + 1), (m_stack.top().second + 0)));
|
||
|
break;
|
||
|
|
||
|
case 2: // South
|
||
|
m_maze[offset(0, +1)] |= CELL_VISITED | CELL_PATH_N;
|
||
|
m_maze[offset(0, 0)] |= CELL_PATH_S;
|
||
|
m_stack.push(make_pair((m_stack.top().first + 0), (m_stack.top().second + 1)));
|
||
|
break;
|
||
|
|
||
|
case 3: // West
|
||
|
m_maze[offset(-1, 0)] |= CELL_VISITED | CELL_PATH_E;
|
||
|
m_maze[offset( 0, 0)] |= CELL_PATH_W;
|
||
|
m_stack.push(make_pair((m_stack.top().first - 1), (m_stack.top().second + 0)));
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
m_nVisitedCells++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No available neighbours so backtrack!
|
||
|
m_stack.pop();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// === DRAWING STUFF ===
|
||
|
|
||
|
// Clear Screen by drawing 'spaces' everywhere
|
||
|
Fill(0, 0, ScreenWidth(), ScreenHeight(), L' ');
|
||
|
|
||
|
// Draw Maze
|
||
|
for (int x = 0; x < m_nMazeWidth; x++)
|
||
|
{
|
||
|
for (int y = 0; y < m_nMazeHeight; y++)
|
||
|
{
|
||
|
// Each cell is inflated by m_nPathWidth, so fill it in
|
||
|
for (int py = 0; py < m_nPathWidth; py++)
|
||
|
for (int px = 0; px < m_nPathWidth; px++)
|
||
|
{
|
||
|
if (m_maze[y * m_nMazeWidth + x] & CELL_VISITED)
|
||
|
Draw(x * (m_nPathWidth + 1) + px, y * (m_nPathWidth + 1) + py, PIXEL_SOLID, FG_WHITE); // Draw Cell
|
||
|
else
|
||
|
Draw(x * (m_nPathWidth + 1) + px, y * (m_nPathWidth + 1) + py, PIXEL_SOLID, FG_BLUE); // Draw Cell
|
||
|
}
|
||
|
|
||
|
// Draw passageways between cells
|
||
|
for (int p = 0; p < m_nPathWidth; p++)
|
||
|
{
|
||
|
if (m_maze[y * m_nMazeWidth + x] & CELL_PATH_S)
|
||
|
Draw(x * (m_nPathWidth + 1) + p, y * (m_nPathWidth + 1) + m_nPathWidth); // Draw South Passage
|
||
|
|
||
|
if (m_maze[y * m_nMazeWidth + x] & CELL_PATH_E)
|
||
|
Draw(x * (m_nPathWidth + 1) + m_nPathWidth, y * (m_nPathWidth + 1) + p); // Draw East Passage
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Draw Unit - the top of the stack
|
||
|
for (int py = 0; py < m_nPathWidth; py++)
|
||
|
for (int px = 0; px < m_nPathWidth; px++)
|
||
|
Draw(m_stack.top().first * (m_nPathWidth + 1) + px, m_stack.top().second * (m_nPathWidth + 1) + py, 0x2588, FG_GREEN); // Draw Cell
|
||
|
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
int main()
|
||
|
{
|
||
|
// Seed random number generator
|
||
|
srand(clock());
|
||
|
|
||
|
// Use olcConsoleGameEngine derived app
|
||
|
OneLoneCoder_Maze game;
|
||
|
game.ConstructConsole(160, 100, 8, 8);
|
||
|
game.Start();
|
||
|
|
||
|
return 0;
|
||
|
}
|