Code archives/Algorithms/Maze Generation Using Recursive Backtracker
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
This maze generator creates rectangular mazes using the recursive backtracker algorithm. When you run it, it will ask for a place and filename to save the resulting png image. | |||||
SuperStrict 'This is the TCell class. It describes one cell in the grid. Type TCell 'these fields point to neighboring cells. Field North:TCell, South:TCell, East:TCell, West:TCell 'These fields point to the row and column in the grid which contains this cell Field Row:Int, Column:Int 'This list conatins all cells that this one links to, i.e. there is a path connecting the two Field Links:TList = CreateList() 'Constructor function for creating the cell Function Create:TCell(Row:Int, Column:Int) Local Cell:TCell = New TCell Cell.Row = Row Cell.Column = Column Return Cell End Function 'This method will link this cell with another one, i.e. create a path between them ' It will call the same method for the linked cell with bidi = true so the link will ' be bi-directional Method Link(Cell:TCell, bidi:Int = True) Links.AddLast(Cell) If bidi Then Cell.Link(Self,False) 'pass false to prevent endless loop End Method 'This methid will check if there is a link between two cells Method isLinked:Int(Cell:TCell) For Local Test:TCell = EachIn Links If Test = Cell Then Return True Next Return False End Method 'This method will return a list of neighbors which do not currently link to any other cell Method Unvisited:TList() Local Neighbors:TList = CreateList() If North And North.Links.isEmpty() Then Neighbors.Addlast(North) If East And East.Links.isEmpty() Then Neighbors.AddLast(East) If South And South.Links.isEmpty() Then Neighbors.AddLast(South) If West And West.Links.isEmpty() Then Neighbors.AddLast(West) Return Neighbors End Method End Type 'The grid class holds a grid of cells representing the entire maze Type TGrid 'This array of arrays holds the grid of cells. I chose an array of arrays instead of a 2 dimentional ' as some types of mazes, such as circular ones, might have different number of columns per row. Even ' though this example only uses rectangular mazes, I am using array of arrays so there will be minimal ' modifications to the class when porting to other types of mazes Field Cells:TCell[][] 'number of rows and columns in the grid Field Rows:Int, Columns:Int 'The constructor function Function Create:TGrid(Rows:Int, Columns:Int) Local Grid:TGrid = New TGrid Grid.Rows = Rows Grid.Columns = Columns Grid.Init() Return Grid End Function 'Initializes the grid, creates the cells and defines the cells' neighbors Method Init() 'create the grid and populate with cells Cells = New TCell[][Rows] For Local Row:Int = 0 Until Rows Cells[Row] = New TCell[Columns] For Local Column:Int = 0 Until Columns Cells[Row][Column] = TCell.Create(Row,Column) Next Next 'Go through the cells and point to the neighbors For Local Row:Int = 0 Until Rows For Local Column:Int = 0 Until Columns Local Cell:TCell = getCell(Row,Column) Cell.North = getCell(Row-1,Column) Cell.East = getCell(Row,Column+1) Cell.South = getCell(Row+1,Column) Cell.West = getCell(Row,Column-1) Next Next End Method 'Gets a cell from the grid. Does bounds checking and returns Null if out of bounds Method getCell:TCell(row:Int,column:Int) If row < 0 Or row >= rows Or column < 0 Or column >= columns Then Return Null Return Cells[row][column] End Method 'return a random cell from the grid Method getRandom:TCell() Local Cell:TCell Repeat Local Row:Int = Rand(0,Rows-1) Local Column:Int = Rand(0,Cells[Row].Length-1) Cell = Cells[Row][Column] Until Cell Return Cell End Method 'this method will create a pixmap representation of the maze. background and wall are in ARGB order Method ToPixmap:TPixmap(background:Int, wall:Int, size:Int = 10) Local imgWidth:Int = size*Columns+1 Local imgHeight:Int = size*rows+1 'create the pixmap and clear to the background color Local img:TPixmap = CreatePixmap(imgWidth, imgHeight, PF_RGBA8888) img.ClearPixels(background) For Local row:Int = 0 Until rows For Local column:Int = 0 Until columns Local Cell:TCell = getCell(row,column) 'calculate the four corners of the cell Local x1:Int = column*size Local x2:Int = x1+size Local y1:Int = row*size Local y2:Int = y1 + size 'Check if on north or west border, draw a wall if so If Not Cell.North Then PixmapLine(img,x1,y1,x2,y1,Wall) If Not Cell.West Then PixmapLine(img,x1,y1,x1,y2,Wall) 'check if there is a wall on the East and south If Not Cell.isLinked(Cell.East) Then PixmapLine(img,x2,y1,x2,y2,Wall) If Not Cell.isLinked(Cell.South) Then PixmapLine(img,x1,y2,x2,y2,Wall) Next Next Return img End Method 'Our grid type is filled with TCells all containing circular references. BlitzMAX garbage collector ' does not do well with circular references, so when we are through with the type, we need to make ' sure they are cleaned up. Not so important on this small example, but can be a source of a memory ' leak on bigger projects. Method Clean() For Local row:Int = 0 Until rows For Local column:Int = 0 Until columns Local Cell:TCell = getCell(row,column) Cell.North = Null Cell.East = Null Cell.South = Null Cell.West = Null Cell.Links.Clear() Cells = Null Next Next End Method 'This method is called when the type is GCed. It will in turn call Clean if it hasn't been cleaned already Method Delete() If Cells Then Clean End Method End Type 'this function will draw lines on the pixmap using besenehams algorithm. This has been modified from the ' code archives submitted by ImaginaryHuman [https://www.blitzbasic.com/codearcs/codearcs.php?code=1465] Function PixmapLine(pixmap:TPixmap,x1:Int,y1:Int,x2:Int,y2:Int,color:Int) 'convert the color to proper endiness to speed up rendering a bit 'Draws a line of individual pixels from X1,Y1 to X2,Y2 at any angle Local Steep:Int=Abs(Y2-Y1) > Abs(X2-X1) 'Boolean If Steep Local Temp:Int=X1; X1=Y1; Y1=Temp 'Swap X1,Y1 Temp=X2; X2=Y2; Y2=Temp 'Swap X2,Y2 EndIf Local DeltaX:Int=Abs(X2-X1) 'X Difference Local DeltaY:Int=Abs(Y2-Y1) 'Y Difference Local Error:Int=0 'Overflow counter Local DeltaError:Int=DeltaY 'Counter adder Local X:Int=X1 'Start at X1,Y1 Local Y:Int=Y1 Local XStep:Int Local YStep:Int If X1<X2 Then XStep=1 Else XStep=-1 'Direction If Y1<Y2 Then YStep=1 Else YStep=-1 'Direction If Steep Then WritePixel(pixmap,Y,X,color) Else WritePixel(pixmap,X,Y,color) 'Draw While X<>X2 X:+XStep 'Move in X Error:+DeltaError 'Add to counter If (Error Shl 1)>DeltaX 'Would it overflow? Y:+YStep 'Move in Y Error=Error-DeltaX 'Overflow/wrap the counter EndIf If Steep Then WritePixel(pixmap,Y,X,color) Else WritePixel(pixmap,X,Y,color) 'Draw Wend End Function 'This function will connect paths into a grid using Recursive Backtracker algorythm Function RecursiveBacktracker(Grid:TGrid) Local Queue:TList = CreateList() 'queue of cells to be tested Queue.AddLast(Grid.getRandom()) 'push a random cell onto the queue While Not Queue.isEmpty() 'the maze is done when there are no more cells on the queue Local Cell:TCell = TCell(Queue.Last()) 'get the last cell on the queue Local Neighbors:TList = Cell.Unvisited() 'get a list of unvisited cells If Neighbors.isEmpty() 'there are no unvisited cells Queue.RemoveLast() 'remove this cell from the queue Else Local Neighbor:TCell = TCell(Neighbors.ValueAtIndex(Rand(0,Neighbors.Count()-1))) 'pick a random neighbor Cell.Link(Neighbor) 'link te two cells together Queue.AddLast(Neighbor) 'add the neighbor to the queue End If Wend 'The grid now contains the completed maze End Function 'select the filename to which we will save our maze Local Filename:String = RequestFile("Save maze as...","Portable Netwrok Graphic(png):png",True) If Not Filename Then End SeedRnd(MilliSecs()) Local Grid:TGrid = TGrid.Create(20,20) 'Create the grid RecursiveBacktracker(Grid) 'Create the maze Local img:TPixmap = Grid.ToPixmap($FFFFFFFF,$FF000000,20) 'Render the maze to a pixmap SavePixmapPNG(img,Filename) |
Comments
| ||
That book "mazes for programmers" really tought me how a lot of maze generation algorithms work. I like the one where you keep dividing the map in two and leave a passage spot. Only a few lines needed for additional room area's. Recursive division it is called. |
Code Archives Forum