Trees maze generator
About the code
The rules
Maze representation
Drawing the grid
#define MAZE_WIDTH 30
#define MAZE_HEIGHT 20
Each tile will be a square of size of 20*20 pixels.
#define TILE_SIZE 20
And the walls will have a width of 2.
#define WALL_WIDTH 2
The screen size will be defined by these values.
#define SCREEN_WIDTH (MAZE_WIDTH * TILE_SIZE + WALL_WIDTH)
#define SCREEN_HEIGHT (MAZE_HEIGHT * TILE_SIZE + WALL_WIDTH)
Now we will write a simple routine that draws a wall in the tile (x, y).
void drawWall(int x, int y, bool isHorizontal, Color c)
{
int sx = x * TILE_SIZE;
int sy = y * TILE_SIZE;
if (isHorizontal == true)
{
int ex = sx + TILE_SIZE - 1;
int ey = sy + WALL_WIDTH - 1;
for (int yy = sy; yy <= ey; yy++)
for (int xx = sx; xx <= ex; xx++)
gfx.setPixel(xx, yy, c);
}
else
{
int ex = sx + WALL_WIDTH - 1;
int ey = sy + TILE_SIZE + WALL_WIDTH - 1;
for (int yy = sy; yy <= ey; yy++)
for (int xx = sx; xx <= ex; xx++)
gfx.setPixel(xx, yy, c);
}
}
To test this function we will draw all the walls of our maze.
// draw the maze
for (int y = 0; y < MAZE_HEIGHT; y++)
for (int x = 0; x < MAZE_WIDTH; x++)
{
drawWall(x, y, false, Color(255, 255, 255));
drawWall(x, y, true, Color(255, 255, 255));
}
And as we said we have to draw the right and bottom borders of the whole
for (int y = 0; y < MAZE_HEIGHT; y++)
drawWall(MAZE_WIDTH, y, false, Color(255, 255, 255));
for (int x = 0; x < MAZE_WIDTH; x++)
drawWall(x, MAZE_HEIGHT, true, Color(255, 255, 255));
Download source code
Download executable for Windows
And we get the desired result
The maze datas
struct STile
{
bool topIsActive;
bool leftIsActive;
Color topColor;
Color leftColor;
};
The maze will be an array of these tiles.
STile maze[MAZE_WIDTH + 1][MAZE_HEIGHT + 1];
We will initialize this array by clearing all the walls' flags.
// init the maze
for (int y = 0; y <= MAZE_HEIGHT; y++)
for (int x = 0; x <= MAZE_WIDTH; x++)
{
maze[x][y].topIsActive = false;
maze[x][y].leftIsActive = false;
}
Now in this part we will try to draw only the borders of our maze.
// init left and right borders
for (int y = 0; y < MAZE_HEIGHT; y++)
{
maze[0][y].leftIsActive = true;
maze[0][y].leftColor = Color(255, 255, 255);
maze[MAZE_WIDTH][y].leftIsActive = true;
maze[MAZE_WIDTH][y].leftColor = Color(255, 255, 255);
}
// init top and bottom borders
for (int x = 0; x < MAZE_WIDTH; x++)
{
maze[x][0].topIsActive = true;
maze[x][0].topColor = Color(255, 255, 255);
maze[x][MAZE_HEIGHT].topIsActive = true;
maze[x][MAZE_HEIGHT].topColor = Color(255, 255, 255);
}
And the function to draw the maze will simply call drawWall() for each active wall.
// draw the maze
for (int y = 0; y <= MAZE_HEIGHT; y++)
for (int x = 0; x <= MAZE_WIDTH; x++)
{
if (maze[x][y].leftIsActive == true)
drawWall(x, y, false, maze[x][y].leftColor);
if (maze[x][y].topIsActive == true)
drawWall(x, y, true, maze[x][y].topColor);
}
Download source code
Download executable for Windows
The result may not be easy to see, but we really get a white rectangle that shows the boundaries of our grid.
The maze class
#ifndef MAZE_H
#define MAZE_H
#include "Graphics.h"
#define MAZE_WIDTH 30
#define MAZE_HEIGHT 20
#define TILE_SIZE 20
#define WALL_WIDTH 2
struct STile
{
bool topIsActive;
bool leftIsActive;
Color topColor;
Color leftColor;
};
class CMaze
{
public:
CMaze();
void draw();
private:
void drawWall(int x, int y, bool isHorizontal, Color c);
STile tiles[MAZE_WIDTH + 1][MAZE_HEIGHT + 1];
};
#endif // MAZE_H
And "maze.cpp"
#include "maze.h"
void CMaze::drawWall(int x, int y, bool isHorizontal, Color c)
{
[...]
}
CMaze::CMaze()
{
// init the maze
for (int y = 0; y <= MAZE_HEIGHT; y++)
for (int x = 0; x <= MAZE_WIDTH; x++)
{
tiles[x][y].topIsActive = false;
[...]
}
// init left and right borders
for (int y = 0; y < MAZE_HEIGHT; y++)
{
tiles[0][y].leftIsActive = true;
[...]
}
// init top and bottom borders
for (int x = 0; x < MAZE_WIDTH; x++)
{
tiles[x][0].topIsActive = true;
[...]
}
}
void CMaze::draw()
{
// draw the maze
for (int y = 0; y <= MAZE_HEIGHT; y++)
for (int x = 0; x <= MAZE_WIDTH; x++)
{
if (tiles[x][y].leftIsActive == true)
drawWall(x, y, false, tiles[x][y].leftColor);
if (tiles[x][y].topIsActive == true)
drawWall(x, y, true, tiles[x][y].topColor);
}
}
Now in "main.cpp" we just need to make an instance of this class and to call its draw method.
CMaze maze;
[...]
maze.draw();
Download source code
The inner walls
// choose a random wall inside maze
STile* chooseWall(bool& isHorizontal)
{
int x, y;
isHorizontal = (rand() & 1 ? false : true);
if (isHorizontal == true)
{
x = 1 + (rand() % (MAZE_WIDTH - 2));
y = 1 + (rand() % (MAZE_HEIGHT - 1));
}
else
{
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = 1 + (rand() % (MAZE_HEIGHT - 2));
}
return maze.getTile(x, y);
}
Then in the main loop, we will call this function and draw this wall.
bool isHorizontal;
STile* tile = chooseWall(isHorizontal);
if (isHorizontal == true)
{
tile->topIsActive = true;
tile->topColor = Color(255, 0, 0);
}
else
{
tile->leftIsActive = true;
tile->leftColor = Color(0, 255, 0);
}
maze.draw();
Download source code
Download executable for Windows
This draws all the walls we want.
Planting the trunks
// choose a random place to put a trunk
STile* CMaze::chooseTrunk(bool& isHorizontal)
{
int x, y;
int dir = rand() % 4;
if (dir == 0)
{
isHorizontal = true;
x = 0;
y = 1 + (rand() % (MAZE_HEIGHT - 1));
}
else if (dir == 1)
{
isHorizontal = true;
x = MAZE_WIDTH - 1;
y = 1 + (rand() % (MAZE_HEIGHT - 1));
}
else if (dir == 2)
{
isHorizontal = false;
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = 0;
}
else
{
isHorizontal = false;
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = MAZE_HEIGHT - 1;
}
return getTile(x, y);
}
Then we will call it in the Maze constructor where we initialize the flags of the walls:
// init the trunks
for (int i = 0; i < NB_TRUNKS; ++i)
{
bool isHorizontal;
Color color(32 + rand() % 224,
32 + rand() % 224,
32 + rand() % 224);
while (true)
{
STile* tile = chooseTrunk(isHorizontal);
if (isHorizontal == true)
{
if (tile->topIsActive == false)
{
tile->topIsActive = true;
tile->topColor = color;
break;
}
}
else
{
if (tile->leftIsActive == false)
{
tile->leftIsActive = true;
tile->leftColor = color;
break;
}
}
}
}
Download source code
Download executable for Windows
This give us the same kind of image that we saw at the beginning.
Checking collisions
bool CMaze::hasWallUp(int x, int y)
{
return getTile(x, y - 1)->leftIsActive;
}
bool CMaze::hasWallDown(int x, int y)
{
return getTile(x, y)->leftIsActive;
}
bool CMaze::hasWallLeft(int x, int y)
{
return getTile(x - 1, y)->topIsActive;
}
bool CMaze::hasWallRight(int x, int y)
{
return getTile(x, y)->topIsActive;
}
bool CMaze::checkIntersection(int x, int y)
{
if (hasWallUp(x, y) == true)
return true;
if (hasWallDown(x, y) == true)
return true;
if (hasWallLeft(x, y) == true)
return true;
if (hasWallRight(x, y) == true)
return true;
return false;
}
Then we can use this function when we choose a trunk to test if its end touches another wall:
// choose a random place to put a trunk
STile* CMaze::chooseTrunk(bool& isHorizontal)
{
int x, y;
STile* tile;
while (true)
{
int dir = rand() % 4;
if (dir == 0)
{
isHorizontal = true;
x = 0;
y = 1 + (rand() % (MAZE_HEIGHT - 1));
if (checkIntersection(x + 1, y) == false)
break;
}
else if (dir == 1)
{
isHorizontal = true;
x = MAZE_WIDTH - 1;
y = 1 + (rand() % (MAZE_HEIGHT - 1));
if (checkIntersection(x, y) == false)
break;
}
else if (dir == 2)
{
isHorizontal = false;
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = 0;
if (checkIntersection(x, y + 1) == false)
break;
}
else
{
isHorizontal = false;
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = MAZE_HEIGHT - 1;
if (checkIntersection(x, y) == false)
break;
}
}
return getTile(x, y);
}
Download source code
Drawing the maze
// choose a random wall inside maze
void chooseWall()
{
int x, y;
Color c;
bool isHorizontal;
while(true)
{
isHorizontal = (rand() & 1 ? false : true);
if (isHorizontal == true)
{
x = 1 + (rand() % (MAZE_WIDTH - 2));
y = 1 + (rand() % (MAZE_HEIGHT - 1));
if (checkWall(x, y, isHorizontal, c) == false)
{
maze.getTile(x, y)->topIsActive = true;
maze.getTile(x, y)->topColor = c;
break;
}
}
else
{
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = 1 + (rand() % (MAZE_HEIGHT - 2));
if (checkWall(x, y, isHorizontal, c) == false)
{
maze.getTile(x, y)->leftIsActive = true;
maze.getTile(x, y)->leftColor = c;
break;
}
}
}
}
This function will call checkIntersection() at both end of the wall we want ot add.
bool checkWall(int x, int y, bool& isHorizontal, Color& color)
{
Color c1, c2;
bool inter1, inter2;
inter1 = maze.checkIntersection(x, y, c1);
if (isHorizontal == true)
inter2 = maze.checkIntersection(x + 1, y, c2);
else
inter2 = maze.checkIntersection(x, y + 1, c2);
We want to connect this wall to another one but to avoid connecting 2 trees together.
if (inter1 == true && inter2 == false)
{
color = c1;
return false;
}
else if (inter1 == false && inter2 == true)
{
color = c2;
return false;
}
return true;
}
Download source code
Download executable for Windows
Note that as the checkIntersection() checks all the directions, it will also check if there is already a wall at
Detecting the end
struct STile
{
bool topIsActive;
bool leftIsActive;
Color topColor;
Color leftColor;
bool topVisited;
bool leftVisited;
};
Of course these flags are initialized to false in the Maze class constructor.
bool checkWall(int x, int y, bool& isHorizontal, Color& color)
{
[...]
if (inter1 == true && inter2 == false)
{
if (isHorizontal == true)
maze.getTile(x, y)->topVisited = true;
else
maze.getTile(x, y)->leftVisited = true;
color = c1;
return false;
}
else if (inter1 == false && inter2 == true)
{
if (isHorizontal == true)
maze.getTile(x, y)->topVisited = true;
else
maze.getTile(x, y)->leftVisited = true;
color = c2;
return false;
}
else if (inter1 == true && inter2 == true)
{
if (isHorizontal == true)
maze.getTile(x, y)->topVisited = true;
else
maze.getTile(x, y)->leftVisited = true;
}
return true;
}
Now at the end we should have marked all the positions of the inner walls where we actually drew a wall or where
bool checkFinished()
{
int nbVisits = 0;
for (int x = 0; x < MAZE_WIDTH; x++)
for (int y = 0; y < MAZE_HEIGHT; y++)
{
if (maze.getTile(x, y)->topVisited == true)
nbVisits++;
if (maze.getTile(x, y)->leftVisited == true)
nbVisits++;
}
return (nbVisits == (MAZE_WIDTH - 1) * (MAZE_HEIGHT - 2) + (MAZE_WIDTH - 2) * (MAZE_HEIGHT - 1));
}
We will use this function at the beginning of chooseWall() to avoid entering in an endless loop.
void chooseWall()
{
int x, y;
Color c;
bool isHorizontal;
while(true)
{
if (checkFinished() == true)
break;
Download source code
Download executable for Windows
Now this program should end without any error.
The islands
// choose a random place to put an island
STile* CMaze::chooseIsland(bool& isHorizontal)
{
int x, y;
Color c;
while (true)
{
isHorizontal = (rand() & 1 ? false : true);
if (isHorizontal == true)
{
x = 1 + (rand() % (MAZE_WIDTH - 2));
y = 1 + (rand() % (MAZE_HEIGHT - 1));
if (checkIntersection(x, y, c) == false &&
checkIntersection(x + 1, y, c) == false)
break;
}
else
{
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = 1 + (rand() % (MAZE_HEIGHT - 2));
if (checkIntersection(x, y, c) == false &&
checkIntersection(x, y + 1, c) == false)
break;
}
}
return getTile(x, y);
}
CMaze::CMaze()
{
srand(time(NULL));
// init the maze
[...]
// init left and right borders
[...]
// init top and bottom borders
[...]
// init the trunks
[...]
// init the islands
for (int i = 0; i < NB_ISLANDS; ++i)
{
bool isHorizontal;
Color color(32 + rand() % 224,
32 + rand() % 224,
32 + rand() % 224);
STile* tile = chooseIsland(isHorizontal);
if (isHorizontal == true)
{
tile->topIsActive = true;
tile->topColor = color;
}
else
{
tile->leftIsActive = true;
tile->leftColor = color;
}
}
}
Download source code
Download executable for Windows
Here is an exemple of a maze with islands:
Qt version
Download source code
Download executable for Windows
Improving the borders
void chooseWall()
{
int x, y;
Color c;
bool isHorizontal;
while(true)
{
if (checkFinished() == true)
break;
isHorizontal = (rand() & 1 ? false : true);
if (isHorizontal == true)
{
x = rand() % MAZE_WIDTH;
y = 1 + (rand() % (MAZE_HEIGHT - 1));
if (x == 0)
{
if (maze.checkIntersection(1, y, c) == false)
{
maze.getTile(x, y)->topIsActive = true;
maze.getTile(x, y)->topColor = Color(32 + rand() % 224,
32 + rand() % 224,
32 + rand() % 224);
break;
}
}
else if (x == MAZE_WIDTH - 1)
{
if (maze.checkIntersection(x, y, c) == false)
{
maze.getTile(x, y)->topIsActive = true;
maze.getTile(x, y)->topColor = Color(32 + rand() % 224,
32 + rand() % 224,
32 + rand() % 224);
break;
}
}
else if (checkWall(x, y, isHorizontal, c) == false)
{
maze.getTile(x, y)->topIsActive = true;
maze.getTile(x, y)->topColor = c;
break;
}
}
else
{
x = 1 + (rand() % (MAZE_WIDTH - 1));
y = rand() % MAZE_HEIGHT;
if (y == 0)
{
if (maze.checkIntersection(x, 1, c) == false)
{
maze.getTile(x, y)->leftIsActive = true;
maze.getTile(x, y)->leftColor = Color(32 + rand() % 224,
32 + rand() % 224,
32 + rand() % 224);
break;
}
}
else if (y == MAZE_HEIGHT - 1)
{
if (maze.checkIntersection(x, y, c) == false)
{
maze.getTile(x, y)->leftIsActive = true;
maze.getTile(x, y)->leftColor = Color(32 + rand() % 224,
32 + rand() % 224,
32 + rand() % 224);
break;
}
}
else if (checkWall(x, y, isHorizontal, c) == false)
{
maze.getTile(x, y)->leftIsActive = true;
maze.getTile(x, y)->leftColor = c;
break;
}
}
}
}
Download source code
Download executable for Windows
And this is a maze produced by this code: