Game Development Community

dev|Pro Game Development Curriculum

Depth-First Search Maze Generation

by TechLord · 10/19/2004 (6:56 pm) · 17 comments

//Depth-First Search Maze Generation TorqueScript
//by Frankie L. Taylor 2004

//Based on DFS Algorithm found http://www.mazeworks.com/mazegen/mazetut/

//DFS Algorithm works like this:
//	1) Start at a random cell in the grid.
//	2) Look for a random neighbor cell you haven't been to yet.
//	3) If you find one, move there, knocking down the wall between the cells. 
//	   If you don't find one, back up to the previous cell.
//	4) Repeat steps 2 and 3 until you've been to every cell in the grid.

//When the while loop terminates, the algorithm is completed. Every cell has been 
//visited and thus no cell is inaccessible. Also, since we test each possible move 
//to see if we've already been there, the algorithm prevents the creation of any 
//open areas, or paths that circle back on themselves.

//We can put the start and end points wherever we want. This is another advantage
//of a perfect maze. Since, by definition, one and only one path will exist between 
//any two points in the maze, we know that given a start/end pair, a unique solution
// to the maze must exist.

//Depth-First Search is the most common algorithm used in maze generation programs: 
//it's simple to implement, works quickly, and generates very pretty mazes. 
//The algorithm can also be used to solve mazes. This is how MazeGen generates 
//solutions for all mazes, no matter which algorithm was used to create them.

//-------------------------------------------------------------------------------------

function mazeGenerate(%fileName,%cellMaxX,%cellMaxY){
	 
	//Pararmeters:	%filename	Name of maze file. The *.maz and *.maz.2 are added.
	//			%cellMaxX	Maximum number of horizontal cells.
	//			%cellMaxY	Maximum number or vertical cells.

	//Return:		nothing

	//Description:	Generates maze data in two formats. Data used to assemble random
	//			maze from independent DIF Interiors, DTS Shapes, and Images.

	%filename = "starter.fps/data/missions/" @ %filename @ ".maz"; //change directory path as needed.

	%cellBaseData =	"0000 1000 0100 0010 0001 1100 0110 0011 1001 1110 0111 1011 1101 1010 0101 1111";
	//cellBaseID:	 0    1    2    3    4    5    6    7    8    9    10   11   12   13   14   15
	%cellBaseDataPTR = 0;

	%cellNeighborData = "0 -1 -1 0 0 1 1 0";
	%cellNeighborDataPTR = 0;

	%cellBaseWallKnockoutData = "0 2 1 3 2 0 3 1"; //current,neighbor
	%cellBaseWallKnockoutDataPTR = 0;

	//create a CellStack (LIFO) to hold a list of cell locations
	%cellStackX[0] = 0;
	%cellStackY[0] = 0;
	%cellStackPTR = 0;

	//set TotalCells = number of cells in grid
	%cellTotal = %cellMaxX*%cellMaxY;

	//choose a cell at random and call it CurrentCell
	%cellCurrentX = getRandom(%cellMaxX);
	%cellCurrentY = getRandom(%cellMaxY);

	//create cells: cellBaseWall, cellBaseWallVariance (future use), cellBase, cellBaseVariance(future use)
	//Note: cellbase describes the the cell pattern (See Diagram 1). A cellbase contains upto 4 walls.
	//Each wall can be independent in shape/texture (variance).
	for (%cellX = 0;%cellX < %cellMaxX;%cellX++){
		for(%cellY = 0;%cellY < %cellMaxY;%cellY++){
			%cellBase[%cellX,%cellY] = 0;
			%cellBaseVariance[%cellX,%cellY] = getRandom(15);
			for(%loop = 0;%loop < 4;%loop++){
				%cellBaseWall[%cellX,%cellY,%loop] = 1; //cellwalls = 0:top, 1:left, 2:bottom, 3:right
				//make walls border by variance
				if ( %cellX == 0 || %cellX == %cellMaxX-1 || %cellY == 0 || %cellY == %cellMaxY-1){//borders
					%cellBaseWallVariance[%cellX,%cellY,%loop] = getRandom(16,24); //Borders are BaseWallVariety 16-24
				}
				else{//standard walls
					%cellBaseWallVariance[%cellX,%cellY,%loop] = getRandom(15); //BaseWallVariety 0-15
				}
			}
		}
	}

	//set VisitedCells = 1
	%cellVisited = 1;

	//while VisitedCells < TotalCells
	while(%cellVisited < %cellTotal){

		//find all neighbors of CurrentCell with all walls intact
		%cellNeighborDataPTR = 0;//restore cellNeighborData
		%cellNeighborCount = 0;//reset neighborCount (list)
		for(%loop = 0;%loop<4;%loop++){
			//calculate neighbors
			%cellX = getWord(%cellNeighborData,%cellNeighborDataPTR);
			%cellY = getWord(%cellNeighborData,%cellNeighborDataPTR+1);
			%cellNeighborDataPTR += 2;//increment cellNeighborData

			%cellX += %cellCurrentX;
			%cellY += %cellCurrentY;

			//check if base is within borders
			//if so, check if walls are intact add to neighbor list
			if( %cellX >= 0 && %cellX < %cellMaxX && %cellY >= 0 && %cellY < %cellMaxY ){
				%cellBaseWallsIntact = %cellBaseWall[%cellX,%cellY,0] + %cellBaseWall[%cellX,%cellY,1] + %cellBaseWall[%cellX,%cellY,2] + %cellBaseWall[%cellX,%cellY,3];
				if (%cellBaseWallsIntact == 4){
					//add to neighbor list
					%cellNeighborX[%cellNeighborCount] = %cellX;
					%cellNeighborY[%cellNeighborCount] = %cellY;
					%cellNeighborPos[%cellNeighborCount] = %loop;
					%cellNeighborCount++;
				}
			}
		}

		//if one or more neighbor found
		if (%cellNeighborCount > 0){

			//choose one of the neighbors at random
			%cellNeighborRandom = getRandom(%cellNeighborCount-1);

			//knock down the wall between it and CurrentCell
			%cellBaseWallKnockoutDataPTR = 0;//restore cellBaseWallKnockoutData
			for(%loop = 0;%loop<4;%loop++){//check walls
				%cellBaseWallKnockoutCurrent = getWord(%cellBaseWallKnockoutData,%cellBaseWallKnockoutDataPTR);
				%cellBaseWallKnockoutNeighbor = getWord(%cellBaseWallKnockoutData,%cellBaseWallKnockoutDataPTR+1);
				%cellBaseWallKnockoutDataPTR += 2;//increment cellBaseWallKnockoutData
				if(%cellNeighborPos[%cellNeighborRandom] == %loop){
					%cellBaseWall[%cellCurrentX,%cellCurrentY, %cellBaseWallKnockoutCurrent] = 0;
					%cellBaseWall[%cellNeighborX[%cellNeighborRandom],%cellNeighborY[%cellNeighborRandom],%cellBaseWallKnockoutNeighbor] = 0;
					%loop = 4;//break for loop
				}
			}

			//push CurrentCell location on the CellStack
			%cellStackX[%cellStackPTR] = %cellCurrentX;
			%cellStackY[%cellStackPTR] = %cellCurrentY;
			%cellStackPTR++;

			//make the new cell CurrentCell
			%cellCurrentX = %cellNeighborX[%cellNeighborRandom];
			%cellCurrentY = %cellNeighborY[%cellNeighborRandom];

			//add 1 to VisitedCells
			%cellVisited++;
		}
		else{
			//pop the most recent cell entry off the CellStack and make it CurrentCell
			%cellStackPTR--;
			%cellCurrentX = %cellStackX[%cellStackPTR];
			%cellCurrentY = %cellStackY[%cellStackPTR];
		}//endIf
	} //endWhile

	//maze completed, id cellbases
	for (%cellX = 0;%cellX < %cellMaxX;%cellX++){
		for(%cellY = 0;%cellY < %cellMaxY;%cellY++){
			%cellBaseWallSet1 = %cellBaseWall[%cellX,%cellY,0] @ %cellBaseWall[%cellX,%cellY,1] @ %cellBaseWall[%cellX,%cellY,2] @ %cellBaseWall[%cellX,%cellY,3];
			%cellBaseDataPTR = 0;//restore cellBaseData
			for(%loop = 0;%loop < 16;%loop++){
				%cellBaseWallSet2 = getWord(%cellBaseData,%cellBaseDataPTR);
				%cellBaseDataPTR++;//increment cellBaseData
				if(%cellBaseWallSet2 == %cellBaseWallSet1){
					%cellBase[%cellX,%cellY] = %loop;
                                        %loop = 16;//break
				}

			}
		}
	}

	//write maze data format I
	//Note: if variance is not required omit the lines.
	%file = new fileObject();
	%file.openforWrite(%fileName);
	for (%cellX = 0;%cellX < %cellMaxX;%cellX++){
		for(%cellY = 0;%cellY < %cellMaxY;%cellY++){
			%file.writeLine(%cellBase[%cellX,%cellY]);
			%file.writeLine(%cellBaseVariance[%cellX,%cellY]);
			for(%loop = 0;%loop < 4;%loop++){
				%file.writeLine(%cellBaseWall[%cellX,%cellY,%loop]);
				%file.writeLine(%cellBaseWallVariance[%cellX,%cellY,%loop]);
			}
		}
	}
	%file.close();
	%file.delete();

	//write maze data format II (Optional)
	%file = new fileObject();
	%file.openforWrite(%fileName@".2");
	for(%cellY = 0;%cellY < %cellMaxY;%cellY++){
		%writetext="";
		for (%cellX = 0;%cellX < %cellMaxX;%cellX++){
			%writetext = %writetext @ %cellBase[%cellX,%cellY] @ "\t";
		}
		%file.writeLine(%writetext);
	}
	%file.close();
	%file.delete();	

}

softbullets.no-ip.com/images/maze1.gifDiagram 1. Cellbaseid ( in green ), Format II.


softbullets.no-ip.com/images/maze2.gif

softbullets.no-ip.com/images/maze.gifThe Result: A Perfect Maze

About the author

thegamedevstore.com

Recent Blogs

• Plan for Frankie Taylor

#1
10/20/2004 (12:13 am)
Excellent resource! I definately need that for my next game! Thanks a lot for posting this one!

Best,
Martin
indies@work
#2
10/20/2004 (5:23 am)
looks cool, but i dont get it, you dont give much of an explanation, what are the uses for this, it would be really cool if it worked for the terrain in torque
#3
10/22/2004 (4:21 pm)
This is slick brutha. Good job. Very useful info. Thank you.
#4
10/23/2004 (6:54 pm)
Hey, This is pretty sweet!
filebox.vt.edu/users/ehartman/maze1.jpg
filebox.vt.edu/users/ehartman/maze2.jpg


Although, I am having a problem where sometimes a cell will have an id of 15, which I dont think is supposed to happen.
#5
10/24/2004 (5:03 am)
ok, now i see, that is cool :)
#6
10/26/2004 (5:31 pm)
Eric,

filebox.vt.edu/users/ehartman/maze1.jpgAWESOME WORK!!! The code is not to supposed to generate a cell id of 15.Well, at least it didnt in my test. I reckoned it is possible.
#7
10/29/2004 (8:24 am)
That would be well cool in an FPS shooter game!
#8
10/29/2004 (9:59 am)
we HAVE to use this in the next Game In a Day.

I only have one question...just exactly how awesome is this? eh?
#9
10/30/2004 (4:21 am)
Anyone willing to post their dif or dts loading script? I'm trying to utilize this and accomplish the result of the screenshots, but its not quite working. Help?
#10
11/03/2004 (6:25 pm)
i have gotten all the errors except this one, any idea?
%cellVisited++;
}
else{
//pop the most recent cell entry off the CellStack and make it CurrentCell

starter.fps/server/scripts/maze1.cs Line: 150 - Syntax error.
>>> Advanced script error report. Line 299.
>>> Some error context, with ## on sides of error halt:
%cellCurrentY = %cellNeighborY[%cellNeighborRandom];



//add 1 to VisitedCells

%cellVisited++;

}

else{##
##
//pop the most recent cell entry off the CellStack and make it CurrentCell

Thanks
#11
11/04/2004 (1:39 am)
Your error is probably above that, most likely a missing bracket. The code had no errors, other than fixing up the word wrap.
#12
11/04/2004 (10:03 am)
Here is the code above that
//push CurrentCell location on the CellStack
%cellStackX[%cellStackPTR] = %cellCurrentX;
%cellStackY[%cellStackPTR] = %cellCurrentY;
%cellStackPTR++;

//make the new cell CurrentCell
%cellCurrentX = %cellNeighborX[%cellNeighborRandom];
%cellCurrentY = %cellNeighborY[%cellNeighborRandom];

//add 1 to VisitedCells
%cellVisited++;
}
else
{
#13
11/04/2004 (11:13 am)
@Wayne Emailing you a compilable file....

I'm still looking for the method of loading dif/dts with this. Anyone? :)
#14
11/04/2004 (3:57 pm)
Thanks man Erik
Yea it would be great to have the dif/dts shape to try this out with.
#15
03/07/2005 (4:25 pm)
Can someone pls post pics and examples of uses for this resource? I think it could be used to generate dungeons with a little work, I'm just not exactly sure how.
#16
07/11/2005 (6:27 pm)
I would be interested in doing more work in this area for 3d maze generation if I could get more info on what was used in the dif/dts shapes. I tried google on site and found very little information.

Any resources appreciated.
#17
07/24/2009 (9:53 am)
Anyone, know how to load them?