#include "pch.c"
#pragma hdrstop
/*
Maze generation based on Kruskal's algorithm
Consider all possible paths as sets of connected cells. Initially
all cells are unconnected and all walls are present. Pick a wall
at random and determine if the cells on either side of the wall are
connected by any possible path. If they are, try again. If not,
knock out the wall and union the two path sets together.
*/
#include <stdlib.h>
#include <memory.h>
#include "genmaze.h"
typedef struct _MazeCell
{
/* Pointer to the head of the set of cells reachable from this one.
Easily identifies an entire set for comparison */
struct _MazeCell *set;
/* Pointer to the next cell in this connected set */
struct _MazeCell *next;
} MazeCell;
typedef struct _MazeWall
{
BYTE wall;
int x, y;
} MazeWall;
/*
Union two connection sets together by setting all elements in one
to the set of the other and then appending it to the end of the
set list
*/
static void ConnectSets(MazeCell *set, MazeCell *add)
{
/* Locate end of set */
while (set->next != NULL)
{
set = set->next;
}
/* Change to point to head of set */
add = add->set;
/* Append new cells */
set->next = add;
/* Change new cells' set identity */
while (add != NULL)
{
add->set = set->set;
add = add->next;
}
}
/*
Determine whether the two cells are already connected. With the
existing data structures this is a trivial comparison of set
identities
*/
#define SetsAreConnected(a, b) \
((a)->set == (b)->set)
/* Locate a cell in a known array */
#define CellAt(x, y) (cells+(x)+(y)*width)
/* Locate a cell in the output array */
#define MazeAt(x, y) (maze+(x)+(y)*(width+1))
/*
Generate a maze by deleting walls at random
Width and height are the counts of cells
Result is stored in the walls array, formatted as (width+1)*(height+1)
cells with MAZE_WALL_HORZ | MAZE_WALL_VERT set appropriately.
X coordinates are one byte apart, so maze is treated like
maze[height+1][width+1]
*/
BOOL GenerateMaze(int width, int height, BYTE *maze)
{
MazeCell *cells;
MazeWall *walls;
int ncells;
int nwalls;
int i, x, y;
MazeCell *ca, *cb;
MazeWall *w, wt;
ncells = width*height;
cells = (MazeCell *)malloc(sizeof(MazeCell)*ncells);
if (cells == NULL)
{
return FALSE;
}
nwalls = (width-1)*height+(height-1)*width;
walls = (MazeWall *)malloc(sizeof(MazeWall)*nwalls);
if (walls == NULL)
{
free(cells);
return FALSE;
}
/* Initialize all cells to be unique sets */
ca = cells;
for (i = 0; i < ncells; i++)
{
ca->set = ca;
ca->next = NULL;
ca++;
}
/* Add all internal horizontal and vertical walls.
All edge walls will be present in the final maze so
they aren't considered here */
w = walls;
for (x = 1; x < width; x++)
{
for (y = 0; y < height; y++)
{
w->wall = MAZE_WALL_VERT;
w->x = x;
w->y = y;
w++;
}
}
for (y = 1; y < height; y++)
{
for (x = 0; x < width; x++)
{
w->wall = MAZE_WALL_HORZ;
w->x = x;
w->y = y;
w++;
}
}
/* Randomize the wall array */
for (i = nwalls-1; i > 0; i--)
{
w = walls+(rand() % i);
wt = *w;
*w = walls[i];
walls[i] = wt;
}
/* Now walk the random list of walls, knocking walls out to
join cells together */
w = walls;
for (i = 0; i < nwalls; i++)
{
/* Determine the two cells separated by the current wall */
ca = CellAt(w->x, w->y);
if (w->wall == MAZE_WALL_HORZ)
{
cb = CellAt(w->x, w->y-1);
}
else
{
cb = CellAt(w->x-1, w->y);
}
/* If the two cells aren't connected, connect them by knocking
out the wall */
if (!SetsAreConnected(ca, cb))
{
ConnectSets(ca, cb);
w->wall = 0;
}
w++;
}
/* Initialize output to empty */
memset(maze, 0, sizeof(BYTE)*(width+1)*(height+1));
/* Set all edge walls in the output */
for (x = 0; x < width; x++)
{
*MazeAt(x, 0) |= MAZE_WALL_HORZ;
*MazeAt(x, height) |= MAZE_WALL_HORZ;
}
for (y = 0; y < height; y++)
{
*MazeAt(0, y) |= MAZE_WALL_VERT;
*MazeAt(width, y) |= MAZE_WALL_VERT;
}
/* Copy remaining walls into the output array */
w = walls;
for (i = 0; i < nwalls; i++)
{
*MazeAt(w->x, w->y) |= w->wall;
w++;
}
free(cells);
free(walls);
return TRUE;
}