Dungeon Generator

This was a one-week project where I created a Dungeon level generator inspired by the likes of roguelikes such as Angbad. This was done using Lua in the IDE Zerobrane. Both of which I have 0 previous experience with.

While working on this project the first day just consisted of me hammering down Lua and Zero brane basics.


print("Hello, World!")						
								

The Gridsystem

The next couple days I created a grid that contained nothing but walls. From there a room was generated, then a bunch of rooms at random positions, then the rooms hade spacing for the corridors to move between them, then a start and end room was made, and finally the rooms got doors. The biggest challenge in this part was the fact that tables, which is the Lua equitant of the all so common array, start at 1 by default.


function GenerateRoomsOnGrid()
  currentAmountOfRooms = 0;
  failedRoommTries = 0;

  while(currentAmountOfRooms < roomAmount and failedRoommTries < roomRetry) do
    
    newRoomY = math.random(1 , #grid )
    newRoomX = math.random(1 , #grid )
    thisRoomWidth = math.random(roomMin,roomMax)
    thisRoomHeigth = math.random(roomMin,roomMax)
    
    canMakeRoom = true;
    if grid[newRoomY][newRoomX] ~= floor then
      
      for y = newRoomY - roomSpaceing, newRoomY + thisRoomHeigth + roomSpaceing do
        for x = newRoomX - roomSpaceing, newRoomX + thisRoomWidth + roomSpaceing do
          if y < 1 or y > #grid or x < 1 or x > #grid or grid[y][x] == floor then
            canMakeRoom = false
            failedRoommTries = failedRoommTries + 1
            break
          end
        end
        
        if canMakeRoom == false then
          break
        end
      end
      
      if canMakeRoom then
        failedRoommTries = 0
        
        for y = newRoomY, newRoomY + thisRoomHeigth do
          for x = newRoomX, newRoomX + thisRoomWidth do
              grid[y][x] = floor
          end
        end
        
        randomizer = math.random(0,1)
        if randomizer > 0.5  then
          doorPointVerticalWall = math.random(newRoomY, newRoomY + thisRoomHeigth)
          grid[doorPointVerticalWall][newRoomX-1] = door
        else
          doorPointVerticalWall = math.random(newRoomY, newRoomY + thisRoomHeigth)
          grid[doorPointVerticalWall][newRoomX+thisRoomWidth+1] = door
        end
        
        randomizer = math.random(0,1)
        if randomizer > 0.5  then
          doorPointHorizontalWall = math.random(newRoomX, newRoomX + thisRoomWidth)
          grid[newRoomY-1][doorPointHorizontalWall] = door
        else
          doorPointHorizontalWall = math.random(newRoomX, newRoomX + thisRoomWidth)
          grid[newRoomY+thisRoomHeigth+1][doorPointHorizontalWall] = door
        end
        currentAmountOfRooms = currentAmountOfRooms+1;
        
      end
    end
  end
end						
								

Corridors

The last 4 days got spent on figuring out the corridors as well as cleaning up the code. When it came to make the corridors work, I ended up with a solution that uses dijkstra's algorithm from the start room to find every other door. This creates a kind of snaking effect where the corridors move between all the rooms becoming one big corridor leading to all the rooms. Looking like this:

One of the especially hard things for me to figure out was how to use the Lua tables that function as hash maps, lists and arrays at the same time to make the, frontier, and came from lists of the dijkstra's algorithm work properly


function GenerateCorridorsFromStart()
  
  frontier[1] = AddToList(doorList[1].Y , doorList[1].X, nil)
  table.insert(cameFrom, frontier[1])
  while frontier[1] ~= nil do
    currentIndex = 0
    for i = 1, #cameFrom do
      if cameFrom[i].X == frontier[1].X and cameFrom[i].Y == frontier[1].Y then
        currentIndex = i
        break
      end
    end
    table.remove(frontier,1)
    current = cameFrom[currentIndex]
    CheckGridPositionOntoTables(current.Y-1,current.X, currentIndex)
    CheckGridPositionOntoTables(current.Y+1,current.X, currentIndex)
    CheckGridPositionOntoTables(current.Y,current.X-1, currentIndex)
    CheckGridPositionOntoTables(current.Y,current.X+1, currentIndex)
  end
  
  for i = 2, #doorList do
    aDoorPos = nil
    for j = 1, #cameFrom do
      if doorList[i].X == cameFrom[j].X and doorList[i].Y == cameFrom[j].Y then
        aDoorPos = cameFrom[j]
        break
      end
    end
    
    local l = cameFrom[aDoorPos.Previous]
    while l.Previous do
      grid[l.Y][l.X] = floor
      l = cameFrom[l.Previous]
    end
  end
end						
								


function CheckGridPositionOnTables(y, x, index)
  
  if IsAPossiblePositon(x, y) then
    table.insert(frontier, AddToList(y, x, index))
    table.insert(cameFrom, AddToList(y, x, index))
    if grid[y][x] == door then
      table.insert(doorList, AddToList(y,x,index)) 
    end
  end
end					
								


function IsAPossiblePositon(x,y)

  for i = 1, #cameFrom do
    
    if cameFrom[i].X == x and cameFrom[i].Y == y then
      return false
    end
  end
  
  if grid[y][x] == door then return true end

  for Y = -1, 1 do
    for X = -1, 1 do
      if x +X > #grid or x+X < 1 or y+Y > #grid or y+Y < 1 then 
        return false 
      elseif grid[y + Y][x + X] == floor then 
        return false 
      end
    end
  end
  return true
end						
								

The Aftermath

In the end I would like to improve how the corridors are generated by doing them in one batch instead of one by one as it is the slowest process of the script. I would also improve the code cleanliness by adding a bit more spacing and removing redundant comments to dampen the moments where you hit a brick wall of code. But as a pretty novice programmer this project was a ton of fun. Mainly because I didn’t follow any online tutorials or guides about anything and I got to use a couple of things that I hadn't ever really used before but always known how to operate like, 2d arrays, and linked lists. But I also got to work with completely new algorithms which I had to make proper research for like Dijkstra’s.