Ok, I took a stab at this.
Paste the attached at the end into a script entity called Mazebuilder.
Basic maze creation
The steps are pretty much 1) create maze instance, 2) initialize maze dimensions/location, 3) set the map layout.
Here is an example of a 4x3 maze, on level 1, at (5, 5):
Code: Select all
maze = MazeBuilder:Create()
MazeBuilder.Initalize(maze, 1, 5, 5, 4, 3)
MazeBuilder.SetMap(maze, [[
+-+-+-+-+
| | |
+ +-+ + +
| | |
+-+ +-+ +
| | |
+-+-+-+-+
]])
Keep that maze variable around! You can call MazeBuilder.SetMap over and over, with different maps, and the doors will automatically change to match the new map. If you specify a truthy third parameter, like: MazeBuilder.SetMap(maze, map, true), the doors will animate into their new state. otherwise it will happen without animation.
By default, it uses dungeon secret doors/pillars. You can change this after creation but before intialization:
Code: Select all
maze = MazeBuilder:Create()
maze.doorTypes = {"temple_secret_door"}
maze.pillarTypes = {"temple_piller}
maze.doorTypes and maze.pillarTypes are tables, so you can throw multiple values in there. if there is more than one value, the MazeBuilder will pick one at random. This way, we can add some random flavor to the maze.
Speaking of random flavor, why do we need to even set a map?
Code: Select all
maze = MazeBuilder:Create()
MazeBuilder.Initialize(maze, 1, 5, 5, 12, 12)
MazeBuilder.RandomizeMap(maze, true/false)
Here, the MazeBuilder will take care of making a random maze for you. It will fill up the entire area provided (see
http://en.wikipedia.org/wiki/Maze_gener ... rst_search)
The call to RandomizeMap will return the generated map string, so you can hang on to it an re-create the same maze again at a later time. You could even put it in a scroll text or something.
Code: Select all
-- Maze Builder
-- Creates mazes out of secret doors and pillars.
--
-- Usage:
-- 1. Copy this entire script into a Script entity anywhere in your dungeon. Name it MazeBuilder
-- 2. Create another script, here is where you will configure your maze
-- 3. Create a new instance of the the MazeBuilder:
-- maze = MazeBuilder:Create()
-- 4. Carve out the area for your maze in the dungeon level. Initialize the maze. Pass the
-- dungeon level, the x,y of the upper left of your maze, and the width/height of your maze.
-- MazeBuilder.Initialize(maze, 1, 5, 5, 10, 10) -- 10x10 maze located at (5,5)
-- 5. By default, all doors will be open. Set the map to a preset one or a random one:
-- MazeBuilder.SetMap(maze, animate)
-- MazeBuilder.RandomizeMap(maze, animate)
-- mapString is a string made with +, -, and | describing the walls. See example below.
-- Each of the functions above have an optional animate parameter, if set to true the walls
-- will animate into their new positions.
--
-- Here is an example of a 4x3 maze string:
-- +-+-+-+-+
-- | | |
-- + +-+ + +
-- | | |
-- +-+ +-+ +
-- | | |
-- +-+-+-+-+
function Create()
local maze = {}
maze.map = ""
maze.doorTypes = {"dungeon_secret_door"}
maze.pillarTypes = {"dungeon_pillar"}
maze.eastDoors = {}
maze.southDoors = {}
maze.initialized = false
return maze
end
-- Preps a map by generating doors and pillars.
-- level : the level the maze is on
-- x : the x coordinate of the upper left cell in the maze
-- y : the y coordinate of the upper left cell in the maze\
-- w : the width of the maze, in cells
-- h : the height of the maze, in cells
function Initialize(maze, level, x, y, w, h)
if not (level and x and y and w and h) then
print("Please specify level, x, y, w, and h when calling Initialize")
return
end
maze.width = w
maze.height = h
-- vertical doors
for i=0, maze.height - 1, 1 do
maze.eastDoors[i] = {}
for j=0, maze.width - 2, 1 do
local doorType = maze.doorTypes[math.random(table.getn(maze.doorTypes))]
local door = spawn(doorType, level, x + j, y + i, 1)
door:setDoorState("open")
maze.eastDoors[i][j] = door.id
end
end
-- horizontal doors
for i=0, maze.height - 2, 1 do
maze.southDoors[i] = {}
for j=0, maze.width - 1, 1 do
local doorType = maze.doorTypes[math.random(table.getn(maze.doorTypes))]
local door = spawn(doorType, level, x + j, y + i, 2)
door:setDoorState("open")
maze.southDoors[i][j] = door.id
end
end
-- spawn pillars
for i=1, maze.width - 1, 1 do
for j=1, maze.height - 1, 1 do
local pillarType = maze.pillarTypes[math.random(table.getn(maze.pillarTypes))]
spawn(pillarType, level, x + i, y + j, 0)
end
end
maze.initialized = true
end
-- Creates a random maze and sets the doors to that maze.
-- If animate is true, the doors will animate into their new state.
-- this function will return a string representing the map.
function RandomizeMap(maze, animate)
if not maze.initialized then
print("Cannot randomize maze until it has been initialized")
return
end
maze.map = randomMap(maze.width, maze.height)
processMap(maze, animate)
return map
end
-- Sets the state of the doors based on a string representing a maze.
-- If animate is set to true, then the doors wil animate into their new state.
function SetMap(maze, map, animate)
if not maze.initialized then
print("Cannot set the map for the maze until it has been initialized")
return
end
maze.map = map
processMap(maze, animate)
end
--- Helper functions and the like. Don't call these directly
-- Take a map string and sync the doors to match the map.
function processMap(maze, animate)
local i = 0
for line in maze.map:gmatch("[^\r\n]+") do
i = i + 1
if i == 1 or i == 2 * maze.height + 1 then
-- dont do anything for the first or last line in the map
elseif i % 2 == 0 then
-- for even lines, we care about the vertical doors
for j = 0, maze.width - 2, 1 do
local door = findEntity(maze.eastDoors[i / 2 - 1][j])
if line:sub(2 * j + 3, 2 * j + 3) == " " then
if animate then
door:open()
else
door:setDoorState("open")
end
else
if animate then
door:close()
else
door:setDoorState("closed")
end
end
end
else
-- for odd lines, we do horizontal (south) doors
for j = 0, maze.width - 1, 1 do
local door = findEntity(maze.southDoors[(i - 1) / 2 - 1][j])
if line:sub(2*j+2, 2*j+2) == " " then
if animate then
door:open()
else
door:setDoorState("open")
end
else
if animate then
door:close()
else
door:setDoorState("closed")
end
end
end
end
end
end
-- A Simple stack implementation.
-- Gratefully stolen from http://lua-users.org/wiki/SimpleStack
Stack = {}
-- Create a Table with stack functions
function Stack:Create()
-- stack table
local t = {}
-- entry table
t._et = {}
-- push a value on to the stack
function t:push(...)
if ... then
local targs = {...}
-- add values
for _,v in pairs(targs) do
table.insert(self._et, v)
end
end
end
-- pop a value from the stack
function t:pop(num)
-- get num values from stack
local num = num or 1
-- return table
local entries = {}
-- get values into entries
for i = 1, num do
-- get last entry
if #self._et ~= 0 then
table.insert(entries, self._et[#self._et])
-- remove last value
table.remove(self._et)
else
break
end
end
-- return unpacked entries
return unpack(entries)
end
-- get entries
function t:getn()
return #self._et
end
-- list values
function t:list()
for i,v in pairs(self._et) do
print(i, v)
end
end
return t
end
-- given a width and height, generate a random maze. This uses a simplified DFS
-- as outlined here: http://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
function randomMap(width, height)
local visited = {}
local map = {}
table.insert(map, string.rep("+-", width) .. "+")
for i=1, height, 1 do
visited[i] = {}
table.insert(map, string.rep("| ", width) .. "|")
table.insert(map, string.rep("+-", width) .. "+")
end
current = {x=math.random(width), y=math.random(height)}
visited[current.y][current.x] = true
local stack = Stack:Create()
iterations = 0
while hasUnvisited(visited, width, height) do
iterations = iterations + 1
randomNeighbor = randomUnvisited(current, visited, width, height)
if randomNeighbor ~= nil then
stack:push(current)
if current.x ~= randomNeighbor.x then
openDoor(map, math.min(current.x, randomNeighbor.x), current.y, true)
else
openDoor(map, current.x, math.min(current.y, randomNeighbor.y), false)
end
current = randomNeighbor
visited[randomNeighbor.y][randomNeighbor.x] = true
elseif stack:getn() > 0 then
current = stack:pop(1)
else
current = {x=math.random(width), y=math.random(height)}
visited[current.y][current.x] = true
end
end
s = ""
for _, i in ipairs(map) do
s = s .. i .. "\n"
end
return s
end
-- given a map as a string, the x and y coordinates of a cell, will open
-- an east or south door on that cell in the map string.
function openDoor(map, x, y, east)
if east then
local line = map[y * 2]
map[y * 2] = line:sub(1, x * 2) .. " " .. line:sub(x * 2 + 2)
else
local line = map[y * 2 + 1]
map[y * 2 + 1] = line:sub(1, x * 2 - 1) .. " " .. line:sub(x * 2 + 1)
end
end
-- Given a hash of visited cells, returns a random unvisited cell adjacent
-- to a given point. If there are no adjacent unvisited cells, return nil.
function randomUnvisited(point, visited, width, height)
local unvisited = {}
-- north
if point.y - 1 >= 1 and not visited[point.y - 1][point.x] then
table.insert(unvisited, {x=point.x, y=point.y - 1})
end
-- east
if point.x + 1 <= width and not visited[point.y][point.x + 1] then
table.insert(unvisited, {x=point.x + 1, y=point.y})
end
-- south
if point.y + 1 <= height and not visited[point.y + 1][point.x] then
table.insert(unvisited, {x=point.x, y=point.y + 1})
end
-- west
if point.x - 1 >= 1 and not visited[point.y][point.x - 1] then
table.insert(unvisited, {x=point.x - 1, y=point.y})
end
if table.getn(unvisited) == 0 then
return nil
end
local i = math.random(table.getn(unvisited))
return unvisited[i]
end
-- Returns if there is an unvisited node in the map
function hasUnvisited(visited, width, height)
for i=1, height, 1 do
for j=1, width, 1 do
if not visited[i][j] then
return true
end
end
end
return false
end
https://github.com/adharris/grimrock/bl ... s/maze.lua
Lemme know how it works for you all!