I've never finished a game of my own (though there was that one abortion in 2006), yet I still enjoy fiddling around with OHRRPGCE, and I like to sprite and come up with game concepts in my spare time. Since I don't like the crap I produce to go to waste, I thought I'd share it here instead. In particular, I'll be messing with OHRRPGCE and HSS, seeing what fun things I can do with them. I don't have any big concepts in mind just yet, but hey, maybe we'll go with the flow and see what happens?
Today I went about writing a script that randomly generates mazes, also known as "Baby's First Algorithm". Not only am I a greenhorn when it comes to creating complex scripts, I'm also terribly rusty with HSS. Therefore this has been quite an adventure. But somehow I got a working product, which I'm about to show off like a kindergartener with a macaroni-and-glue portrait.
My goal was to create a simple maze: one that covers the entire grid, with where you're able to get from any point in the maze to any other point (i.e. there are no sections completely blocked off from one another). I'll get to script in a minute, but first here's the premade map that I'm working with for this demo:
This is the blank slate that the maze will be chiseled into, complete with hideous placeholder graphics. Right now orange tiles represent solid walls, while black tiles (of which there are none at the moment) represent "excavated" passages. To make this thing a little easier to read:
The green tiles here represent "cells," as I will be calling them. Every single cell will be excavated, and every cell will be connected to every other cell in the end. As you can see, this simple demo map is 6x6 cells. Yellow tiles are the walls that lie between cells. Some of them will be excavated to create paths between cells, while others will remain solid walls. Orange tiles will always be solid. The orange tiles with the black x's designate the border of the map. All the fun stuff will happen in the middle.
That blank map is starting to bug me, so let's get started making a maze.
Code:
variable (CurrentCellEReady)
variable (CurrentCellNReady)
variable (CurrentCellSReady)
variable (CurrentCellWReady)
variable (CurrentCellX)
variable (CurrentCellY)
variable (ExtraPathNumber)
variable (ExtraPaths)
variable (GoodToGo)
variable (MapSize)
variable (RandomNumberGod)
variable (TotalCells)
variable (VisitedCells)
variable (WallCleared)
variable (CurrentCellNReady)
variable (CurrentCellSReady)
variable (CurrentCellWReady)
variable (CurrentCellX)
variable (CurrentCellY)
variable (ExtraPathNumber)
variable (ExtraPaths)
variable (GoodToGo)
variable (MapSize)
variable (RandomNumberGod)
variable (TotalCells)
variable (VisitedCells)
variable (WallCleared)
We start off by naming all the lovely variables that will be put to use.
Code:
MapSize := 6
TotalCells := MapSize*MapSize
TotalCells := MapSize*MapSize
MapSize determines the length (in cells) of the map that we'll be making. This variable can be changed to whatever number you want, though you need a premade map to support it, like my 6x6 map shown above. This script produces square-shaped mazes (since they're all I really need), though you could probably easily alter it to allow for rectangular mazes.
TotalCells gives the volume of the grid (in cells). Since it's a square map, the math is easy. In the case of my size-6 map, the total number of cells is 6 times 6, or 36.
Code:
CurrentCellX := random(1,MapSize)*2
CurrentCellY := random(1,MapSize)*2
CurrentCellY := random(1,MapSize)*2
Here, the script picks a random cell to start out from. Note that since cells are always located on even-numbered map coordinates, the random output is multiplied by two. The upper-left corner of the map will always be at 2,2 on the map. In the case of our 6x6 maze, the bottom-right corner corresponds to 12,12.
Code:
while (VisitedCells < TotalCells) do (
if ((read map block (CurrentCellX,CurrentCellY--2)) == 2) then (CurrentCellNReady := 1) else (CurrentCellNReady := 0)
if ((read map block (CurrentCellX,CurrentCellY+2)) == 2) then (CurrentCellSReady := 1) else (CurrentCellSReady := 0)
if ((read map block (CurrentCellX--2,CurrentCellY)) == 2) then (CurrentCellWReady := 1) else (CurrentCellWReady := 0)
if ((read map block (CurrentCellX+2,CurrentCellY)) == 2) then (CurrentCellEReady := 1) else (CurrentCellEReady := 0)
#Checks to see which neighboring cells are excavated and which aren't.
if (CurrentCellNReady == 1 || CurrentCellSReady == 1 || CurrentCellWReady == 1 || CurrentCellEReady == 1) then (
#If at least one of the neighboring cells is excavated...
while (WallCleared == 0) do (
RandomNumberGod := random(1,4)
if (RandomNumberGod == 1) then (
if (CurrentCellNReady == 1) then (
write map block (CurrentCellX,CurrentCellY--1,0)
write map block (CurrentCellX,CurrentCellY--2,0)
CurrentCellY -= 2
VisitedCells += 1
WallCleared := 1
)
)
if (RandomNumberGod == 2) then (
if (CurrentCellSReady == 1) then (
write map block (CurrentCellX,CurrentCellY+1,0)
write map block (CurrentCellX,CurrentCellY+2,0)
CurrentCellY += 2
VisitedCells += 1
WallCleared := 1
)
)
if (RandomNumberGod == 3) then (
if (CurrentCellWReady == 1) then (
write map block (CurrentCellX--1,CurrentCellY,0)
write map block (CurrentCellX--2,CurrentCellY,0)
CurrentCellX -= 2
VisitedCells += 1
WallCleared := 1
)
)
if (RandomNumberGod == 4) then (
if (CurrentCellEReady == 1) then (
write map block (CurrentCellX+1,CurrentCellY,0)
write map block (CurrentCellX+2,CurrentCellY,0)
CurrentCellX += 2
VisitedCells += 1
WallCleared := 1
)
)
)
#Randomly chooses a neighboring, unexcavated cell and clears out a path to it.
WallCleared := 0
) else (
#If none of the neighboring cells are excavated...
while (GoodToGo == 0) do (
CurrentCellX := random(1,MapSize)*2
CurrentCellY := random(1,MapSize)*2
if ((read map block (CurrentCellX,CurrentCellY)) == 0) then (GoodToGo := 1)
)
#Selects a new, excavated cell to work from.
GoodToGo := 0
)
)
if ((read map block (CurrentCellX,CurrentCellY--2)) == 2) then (CurrentCellNReady := 1) else (CurrentCellNReady := 0)
if ((read map block (CurrentCellX,CurrentCellY+2)) == 2) then (CurrentCellSReady := 1) else (CurrentCellSReady := 0)
if ((read map block (CurrentCellX--2,CurrentCellY)) == 2) then (CurrentCellWReady := 1) else (CurrentCellWReady := 0)
if ((read map block (CurrentCellX+2,CurrentCellY)) == 2) then (CurrentCellEReady := 1) else (CurrentCellEReady := 0)
#Checks to see which neighboring cells are excavated and which aren't.
if (CurrentCellNReady == 1 || CurrentCellSReady == 1 || CurrentCellWReady == 1 || CurrentCellEReady == 1) then (
#If at least one of the neighboring cells is excavated...
while (WallCleared == 0) do (
RandomNumberGod := random(1,4)
if (RandomNumberGod == 1) then (
if (CurrentCellNReady == 1) then (
write map block (CurrentCellX,CurrentCellY--1,0)
write map block (CurrentCellX,CurrentCellY--2,0)
CurrentCellY -= 2
VisitedCells += 1
WallCleared := 1
)
)
if (RandomNumberGod == 2) then (
if (CurrentCellSReady == 1) then (
write map block (CurrentCellX,CurrentCellY+1,0)
write map block (CurrentCellX,CurrentCellY+2,0)
CurrentCellY += 2
VisitedCells += 1
WallCleared := 1
)
)
if (RandomNumberGod == 3) then (
if (CurrentCellWReady == 1) then (
write map block (CurrentCellX--1,CurrentCellY,0)
write map block (CurrentCellX--2,CurrentCellY,0)
CurrentCellX -= 2
VisitedCells += 1
WallCleared := 1
)
)
if (RandomNumberGod == 4) then (
if (CurrentCellEReady == 1) then (
write map block (CurrentCellX+1,CurrentCellY,0)
write map block (CurrentCellX+2,CurrentCellY,0)
CurrentCellX += 2
VisitedCells += 1
WallCleared := 1
)
)
)
#Randomly chooses a neighboring, unexcavated cell and clears out a path to it.
WallCleared := 0
) else (
#If none of the neighboring cells are excavated...
while (GoodToGo == 0) do (
CurrentCellX := random(1,MapSize)*2
CurrentCellY := random(1,MapSize)*2
if ((read map block (CurrentCellX,CurrentCellY)) == 0) then (GoodToGo := 1)
)
#Selects a new, excavated cell to work from.
GoodToGo := 0
)
)
Oh boy, here comes the big part! First, the script checks to see which neighboring cells have been excavated and which haven't. (When the maze generation begins, obviously none of them have, but later on this is important.) If one or more neighboring cells are unexcavated, then one of those unexcavated cells will be chosen at random. That cell along with the wall between it and the current cell will be excavated. Then the new cell becomes the current cell, and the script loops back to checking which neighboring cells have or haven't been excavated.
(When reading map blocks, 0 is the excavated black tile, 1 is the X-orange border tile, and 2 is the plain orange wall tile. The script will never attempt to excavate a border tile.)
If the script finds that all neighboring cells have been excavated, then it will randomly select another excavated cell and try from there. The script continues until every cell on the grid has been visited.
Code:
ExtraPathNumber := random(1,TotalCells/3)
while (ExtraPaths < ExtraPathNumber) do (
CurrentCellX := random(1,MapSize)*2
CurrentCellY := random(1,MapSize)*2
#Selects a new cell to work from.
RandomNumberGod := random(1,4)
if (RandomNumberGod == 1) then (
if ((read map block (CurrentCellX,CurrentCellY--2)) == 0) then (
write map block (CurrentCellX,CurrentCellY--1,0)
)
)
if (RandomNumberGod == 2) then (
if ((read map block (CurrentCellX,CurrentCellY+2)) == 0) then (
write map block (CurrentCellX,CurrentCellY+1,0)
)
)
if (RandomNumberGod == 3) then (
if ((read map block (CurrentCellX--2,CurrentCellY)) == 0) then (
write map block (CurrentCellX--1,CurrentCellY,0)
)
)
if (RandomNumberGod == 4) then (
if ((read map block (CurrentCellX+2,CurrentCellY)) == 0) then (
write map block (CurrentCellX+1,CurrentCellY,0)
)
)
#Attempts to break down a wall.
ExtraPaths += 1
)
while (ExtraPaths < ExtraPathNumber) do (
CurrentCellX := random(1,MapSize)*2
CurrentCellY := random(1,MapSize)*2
#Selects a new cell to work from.
RandomNumberGod := random(1,4)
if (RandomNumberGod == 1) then (
if ((read map block (CurrentCellX,CurrentCellY--2)) == 0) then (
write map block (CurrentCellX,CurrentCellY--1,0)
)
)
if (RandomNumberGod == 2) then (
if ((read map block (CurrentCellX,CurrentCellY+2)) == 0) then (
write map block (CurrentCellX,CurrentCellY+1,0)
)
)
if (RandomNumberGod == 3) then (
if ((read map block (CurrentCellX--2,CurrentCellY)) == 0) then (
write map block (CurrentCellX--1,CurrentCellY,0)
)
)
if (RandomNumberGod == 4) then (
if ((read map block (CurrentCellX+2,CurrentCellY)) == 0) then (
write map block (CurrentCellX+1,CurrentCellY,0)
)
)
#Attempts to break down a wall.
ExtraPaths += 1
)
This little bit of script here is just to make the maze a bit more interesting. It simple goes around knocking down a few extra walls, adding circular paths to the maze. Again, you can mess around with the numbers a little. I have the number of attempts the script will make set to a third of the TotalCells variable. Higher than that and there will be tons of loops and circular paths in the maze; lower than that and it'll be closer to a "perfect maze."
That's all there is to the script (for now). When you run it, you'll get something like these:
Pretty nifty, huh? And just for one, here's what a 64x64 randomly-generated maze looks like:
The script works very quickly. Even that 64x64 maze I just posted was generated instantaneously. Still, I'm sure my script is less efficient than it would be had it been written by someone more experience than I; feel free to share criticism.
That's all I have for now. Maybe next time I'll add more features to the generation... perhaps start and finish spots, rooms, different wall types, randomly-placed objects and other features. Share any comments or suggestions you have. Peace out.




)