Simple Maze Generator

Miscellaneous Forums/Blitz Showcase/Simple Maze Generator



This is a simple maze generator.

I've got it making mazes decently fast.

blitzmax language
'///////////////////////////////////////////////////////////////
' ***maze generator***
' Ryan Burnside 2008 (Pixel_Outlaw)
'///////////////////////////////////////////////////////////////

'///////////////////////////////////////////////////////////////
' algorithem overview
' seed an array with cells that have 4 walls
' (a) choose 1 cell do a random walk untill a dead end is reached (knocking down walls between this cell and the former)
' do a check for cells that border already visited cells adding them to a waiting list
' pick a new cell from the waiting list and knock down a wall to a visited cell
' go to (a)
'///////////////////////////////////////////////////////////////

Strict
Framework brl.GLMax2D
Import brl.Random
AppTitle = "Random Walk Maze by Ryan Burnside 2008 B=Binary maze, L=line maze, D=dungeon"

SeedRnd(MilliSecs()) 
Global length:Int = 30   ' maze length |<--------->|
Global height:Int = 30 ' maze height
Global cell_width:Float = 4


Type cell
Field n:Byte, e:Byte, s:Byte, w:Byte, u:Byte
EndType

Type position
	Field x:Int, y:Int
End Type

' create our array of cells
Global array:cell[length, height] 
 
'holds cells waiting to be worked with
Global waiting_list:TList = New TList

'seed the array with cells
For Local x:Int = 0 To length - 1
	For Local y:Int = 0 To height - 1
	Local c:cell = New cell
	c.n = 1
	c.s = 1
	c.e = 1
	c.w = 1
	c.u = 1
	array[x, y] = c
	Next
Next

'function to take a random walk
Function rand_walk() 
' find all possible border cells

Local list_length:Int = CountList(waiting_list) 
While list_length > 0

Local chosen:position = position(waiting_list.ValueAtIndex(Rand(0, list_length - 1))) 
 
' starting square
Local current_x:Int = chosen.x
Local current_y:Int = chosen.y
attach_former(current_x, current_y) 
While 1
	'1 find next cell from current x and y randomly if possible else exit
	'2 mark current x and y as visited
	'3 knock down wall between
	array[current_x, current_y].u = 0
	
	Local a:Int[] = select_next(current_x, current_y) 
	' end prematurly if no passage is found
	If a[0] = current_x And a[1] = current_y
		Return Null
	Else
	current_x = a[0] 
	current_y = a[1]
	End If
	
			
Wend
wend
EndFunction

'function to select the next cell to walk from
Function select_next:Int[] (current_x:Int, current_y:Int) 
' store our x and y point into a returnable array
Local return_array:Int[2] 
Local choices:String = ""

' check cell to north, is it unvisited?
If current_y - 1 >= 0
	If array[current_x, current_y - 1].u
		choices:+"n"
	EndIf
End If
' check to east
If current_x + 1 <= length - 1
	If array[current_x + 1, current_y].u
		choices:+"e"
	EndIf
End If
'check to the south
If current_y + 1 <= height - 1
	If array[current_x, current_y + 1].u
		choices:+"s"
	EndIf
End If
'check to the west
If current_x - 1 >= 0
	If array[current_x - 1, current_y].u
		choices:+"w"
	EndIf
End If


' now select a random position from the alloted choices
If choices.length > 0

Local selection:String = Chr(choices[Rand(0, choices.length - 1)] ) 
	 
	Select selection
		Case "n"
 			return_array[0] = current_x
			return_array[1] = current_y - 1
			array[current_x, current_y - 1].s = 0
			array[current_x, current_y].n = 0
			Return return_array
		Case "e"
			return_array[0] = current_x + 1
			return_array[1] = current_y
			array[current_x + 1, current_y].w = 0
			array[current_x, current_y].e = 0
			Return return_array
		Case "s"
			return_array[0] = current_x
			return_array[1] = current_y + 1
			array[current_x, current_y + 1].n = 0
			array[current_x, current_y].s = 0
			Return return_array
		Case "w"
			return_array[0] = current_x - 1
			return_array[1] = current_y
			array[current_x - 1, current_y].e = 0
			array[current_x, current_y].w = 0
			Return return_array
		
	End Select
Else
	return_array[0] = current_x
	return_array[1] = current_y
	Return return_array
EndIf

End Function

Function add_waiting_best() 
' executed before each random walk (not during) 
' scan through the entire array add all unvisited cells that have one visited neighbour to the waiting list

' clear the list
waiting_list.Clear() 

'Why is this better than add_waiting?
' 1 pick a random start position in the array
' 2 loop untill a position is found that is unvisited and has a neighbour
' 3 return one position- no list making and clearing, no extra computations 

Local x:Int = Rand(0, length - 1) 
Local y:Int = Rand(0, height - 1) 
Local max_turns:Int = length * height
Local visited:Int = 0
While 1
visited:+1
		' if this cell is unvisited (condition 1) and has a visited neighbour (condition 2)
		If array[x, y].u

			If y - 1 >= 0
				If Not array[x, y - 1].u
					Local p:position = New position
					p.x = x
					p.y = y
					ListAddLast(waiting_list, p) 
					Return Null
				EndIf
			End If

			' check to east
			If x + 1 <= length - 1
				If Not array[x + 1, y].u
					Local p:position = New position
					p.x = x
					p.y = y
					ListAddLast(waiting_list, p) 
					Return Null
				EndIf
			End If

			'check to the south
			If y + 1 <= height - 1
				If Not array[x, y + 1].u
					Local p:position = New position
					p.x = x
					p.y = y
					ListAddLast(waiting_list, p) 
					Return Null
				EndIf
			End If

			'check to the west
			If x - 1 >= 0
				If Not array[x - 1, y].u
					Local p:position = New position
					p.x = x
					p.y = y
					ListAddLast(waiting_list, p) 
					Return Null
				EndIf
			End If
		EndIf
x:+1
If x > length - 1
	x = 0
	y:+1
	If y > height - 1
		y = 0
	End If
End If

If x > length - 1
	x = 0
End If
If y > height - 1
	y = 0
End If
If visited > max_turns
Return Null
EndIf
Wend
End Function

Function attach_former(x:Int, y:Int) 
Local order:String = ""

'check to north
If y - 1 >= 0
	If Not array[x, y - 1].u
		order:+"n"
	EndIf
End If

' check to east
If x + 1 <= length - 1
	If Not array[x + 1, y].u
		order:+"e"
	EndIf
End If

'check to the south
If y + 1 <= height - 1
	If Not array[x, y + 1].u
		order:+"s"
	EndIf
End If

'check to the west
If x - 1 >= 0
	If Not array[x - 1, y].u
		order:+"w"
	EndIf
End If

' select random neighbor
If order.length > 0
order = Chr(order[Rand(0, order.length - 1)] ) 

Select order
	Case "n"
		array[x, y - 1].s = 0
		array[x, y].n = 0
		Return
	Case "e"
		array[x + 1, y].w = 0
		array[x, y].e = 0
		Return
	Case "s"
		array[x, y + 1].n = 0
		array[x, y].s = 0
		Return
	Case "w"
		array[x - 1, y].e = 0
		array[x, y].w = 0
		Return
End Select

EndIf

End Function

Function draw() 
SetClsColor(255, 255, 255) 
Cls
SetColor(0, 0, 0) 
For Local x:Int = 0 To length - 1
	For Local y:Int = 0 To height - 1
		'draw here
		Local c:cell = array[x, y] 
		Local a:Float = x * cell_width + 3
		Local b:Float = y * cell_width + 3

			If c.n
		 		DrawLine(a, b, a + cell_width, b) 
		 	End If
			If c.e
		 		DrawLine(a + cell_width, b, a + cell_width, b + cell_width) 
		 	End If
		 	If c.s
		 		DrawLine(a, b + cell_width, a + cell_width, b + cell_width) 
			End If
		 	If c.w
		 		DrawLine(a, b, a, b + cell_width) 
		 	End If
	Next
Next
EndFunction

' dungeon generating functions
Function remove_dead_ends() 

' remove percent dead ends
For Local i = 0 To Floor((length / 2.0) + (height / 2.0)) 
	Local removal_list:TList = New TList
	For Local x:Int = 0 To length - 1
		For Local y:Int = 0 To height - 1
		Local walls:Int = 0
			'start counting cells remove if 3 or more and not start or end
				'If (x <> start_x And y <> start_y) Or(x <> end_x And y <> end_y) 
					If array[x, y].n
						walls:+1
					End If
					If array[x, y].e
						walls:+1
					End If
					If array[x, y].s
						walls:+1
					End If
					If array[x, y].w
						walls:+1
					End If
					' find the side without the wall
					If walls = 3
						Local P:position = New position
						p.x = x
						p.y = y
						ListAddLast(removal_list, p) 
					End If
				'EndIf
		Next
	Next
	For Local p:position = EachIn(removal_list) 
	Local empty:String = ""
	Local x:Int = p.x
	Local y:Int = p.y
		If Not array[x, y].n
						empty = "n"
						End If
		If Not array[x, y].e
						empty = "e"
						End If
		If Not array[x, y].s
						empty = "s"
						End If
		If Not array[x, y].w
						empty = "w"
						End If
		' put a wall up making a new culdesac
						
		Select empty
							Case "n"
							array[x, y - 1].s = 1
							Case "e"
							array[x + 1, y].w = 1
							Case "s"
							array[x, y + 1].n = 1
							Case "w"
							array[x - 1, y].e = 1
						End Select
						
		array[x, y].n = 0
		array[x, y].e = 0
		array[x, y].s = 0
		array[x, y].w = 0
	Next
Next
End Function

Function convert_to_blocks:Byte[,] () 
'make a block maze from the cell maze
'cells are either open or closed (booliean)
Local new_length = (2 * length) + 1
Local new_height = (2 * height) + 1
Local return_array:Byte[new_length, new_height] 
' fill the array with walls
For Local x = 0 To new_length - 1
	For Local y = 0 To new_height - 1
		return_array[x, y] = 1
	Next
Next
'now fill the new array
For Local x = 0 To length - 1
	For Local y = 0 To height - 1
		' this is the location in the new and bigger array
		Local location_x:Int = x * 2 + 1
		Local location_y:Int = y * 2 + 1
		Local sides:Byte = 0
		'check cells in the old small array
		return_array[location_x, location_y] = 0
		If array[x, y].n
			return_array[location_x, location_y - 1] = 1
			Else
			return_array[location_x, location_y - 1] = 0
		End If
		
		If array[x, y].e
			return_array[location_x + 1, location_y] = 1
			Else
			return_array[location_x + 1, location_y] = 0
		End If
		
		If array[x, y].s
			return_array[location_x, location_y + 1] = 1
			Else
			return_array[location_x, location_y + 1] = 0
		End If
		
		If array[x, y].w
			return_array[location_x - 1, location_y] = 1
			Else
			return_array[location_x - 1, location_y] = 0
		End If
		

	Next
Next
Return return_array
End Function

Function draw_blocks(a:Byte[,] ) 
	For Local x = 0 To length * 2
		For Local y = 0 To height * 2
			If a[x, y] = 0
				SetColor(200, 200, 200) 
				DrawRect(x * cell_width, y * cell_width, cell_width, cell_width) 
				Else
				SetColor(128, 128, 128) 
				DrawRect(x * cell_width, y * cell_width, cell_width, cell_width)  
			EndIf
			
		Next
	Next
End Function

Function remove_wall_ends:Byte[,] (a:Byte[,] , n:Int) 
	'finds a wall tip and removes it x many times
	' if the position is a wall tip ( has only 1 wall neighbor)
	' create a potsition save to a list
	' remove the positions after each iteration	
	Local positions:TList = New TList
	For Local c:Int = 0 To n - 1
		For Local x:Int = 1 To a.dimensions()[0] - 2
			For Local y = 1 To a.dimensions()[1] - 2
				If a[x, y] = 1
				Local count = 0
					If a[x, y - 1] = 0
					count:+1
					EndIf
					If a[x + 1, y] = 0
					count:+1
					EndIf
					If a[x, y + 1] = 0
					count:+1
					EndIf
					If a[x - 1, y] = 0
					count:+1
					EndIf
					If count = 3
						Local p:position = New position
						p.x = x
						p.y = y
						ListAddLast(positions, p) 
					End If
				End If
			Next
		Next
		' remove all walls from positions list
		For Local p:position = EachIn(positions) 
			a[p.x, p.y] = 0
		Next
	Next
	Return a
End Function

Function remove_ends:Byte[,] (a:Byte[,] , n:Int) 
	'finds a wall tip and removes it x many times
	' if the position is a wall tip ( has only 1 wall neighbor)
	' create a potsition save to a list
	' remove the positions after each iteration	
	Local positions:TList = New TList
	For Local c:Int = 0 To n - 1
		For Local x:Int = 1 To a.dimensions()[0] - 2
			For Local y = 1 To a.dimensions()[1] - 2
				If a[x, y] = 0
				Local count = 0
					If a[x, y - 1] = 1
					count:+1
					EndIf
					If a[x + 1, y] = 1
					count:+1
					EndIf
					If a[x, y + 1] = 1
					count:+1
					EndIf
					If a[x - 1, y] = 1
					count:+1
					EndIf
					If count = 3
						Local p:position = New position
						p.x = x
						p.y = y
						ListAddLast(positions, p) 
					End If
				End If
			Next
		Next
		' remove all walls from positions list
		For Local p:position = EachIn(positions) 
			a[p.x, p.y] = 1
		Next
	Next
	Return a
End Function


array[length / 2, height / 2].u = 0
add_waiting_best() 
 
Local C:Float = MilliSecs() ' start a timer for creation 

While CountList(waiting_list) > 0
add_waiting_best() 
rand_walk() 
Wend

Notify("Genarated in: " + String((MilliSecs() - c) / 1000) + " seconds.") 

Graphics 1024, 800
SetClsColor(255, 255, 255) 
Cls() 
Flip

' make a binary tile maze vesion of the orginal
Global g:Byte[,] = convert_to_blocks() 

' make a binary tile dungeon of the initial
Global d:Byte[,] = convert_to_blocks() 
d = remove_ends(d, 4) 
d = remove_wall_ends(d, 4) 
d = remove_ends(d, 20) 
d = remove_wall_ends(d, 8) 
d = remove_ends(d, 20)  
' kill off the list and free memory
DrawText("B=Binary Version", 0, 0) 
DrawText("D=Dungeon", 0, 24) 
DrawText("L=Line Version", 0, 12) 
Flip
While Not KeyDown(KEY_ESCAPE) 
	'draw binary collision maze if b is pressed
	If KeyHit(KEY_B) 
		Cls
		SetColor(0, 0, 0) 
		DrawText("This is a binary maze version. Usefull For tile based collisions. No cell objects just a binary array of 0 and 1.", 0, cell_width * (height + 2) * 2) 
		draw_blocks(g) 
		Flip
	End If
	
	'draw binary dungeon if d is pressed
	If KeyHit(KEY_D) 
		Cls
		SetColor(0, 0, 0) 
		DrawText("This is a binary Dungeon. Usefull For tile based collisions. No cell objects just a binary array of 0 and 1.", 0, cell_width * (height + 2) * 2) 
		draw_blocks(d) 
		Flip
	End If
	
	' draw line maze if l is pressed
	If KeyHit(KEY_L) 
		Cls
		SetColor(0, 0, 0) 
		DrawText("This is a line maze version. Good for paper and pencil fun. Overly complex cell objects and large memory size.", 0, cell_width * (height + 2) * 2) 
		draw
		Flip
	End If

	
WEnd


' as of 4-13-08 a 120x120 grid takes around 16-19 seconds to construct (add_waiting method)
' as of 4-13-08 a 120x120 grid takes around .30-.50 seconds to draw (native blitz drawing commands)

' as of 4-13-08 9:00 pm a 120x120 grid takes about 1-2 seconds to construst with new(add_waiting_best method)
' as of 4-16-08 10:00 pm a 120x120 grid takes about 1-4 seconds to construct with improved and random add_waiting best method

' as of 4-22-08 1:pm a 120 x120 grid takes about .2 to .5 seconds to construct with improved random search





.exe for those without blitzmax

http://myfreefilehosting.com/f/d604cf3874_0.1MB