The official distribution of olcConsoleGameEngine, a tool used in javidx9's YouTube videos and projects
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.
videos/OneLoneCoder_Mazes.cpp

220 lines
5.8 KiB

/*
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;
}