feat(gen): extend init with shuffledDirs and Maze.generate
This commit is contained in:
1 parent
598dfaf79e
commit
2dbaa30813
2 files changed
+96
-1
No files matched your search
+6
-1
@@ -12,10 +12,15 @@ Gen.generators = {
|
||||
bsp = require("gen.bsp").generate,
|
||||
cellular = require("gen.cellular").generate,
|
||||
drunkard = require("gen.drunkard").generate,
|
||||
maze = require("gen.maze").generate,
|
||||
}
|
||||
|
||||
-- Deterministic ordering of names for callers that want to cycle generators by
|
||||
-- depth without depending on hash iteration order.
|
||||
-- depth without depending on hash iteration order. gen.maze is deliberately
|
||||
-- absent: builder.lua's chosenGenerator indexes into this list by depth, and
|
||||
-- every existing seed-pinned test downstream of it already assumes today's
|
||||
-- four-way cycle. Reaching a maze floor is always explicit, via
|
||||
-- options.generator = "maze" (see Gen.has/Gen.build below).
|
||||
Gen.order = { "rooms", "bsp", "cellular", "drunkard" }
|
||||
|
||||
function Gen.has(name)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
-- Recursive-backtracker maze: a perfect spanning tree of one-wide corridors
|
||||
-- over a grid of cells spaced two tiles apart, carved by an iterative
|
||||
-- depth-first walk (an explicit stack, never Lua's own call stack, so
|
||||
-- nothing here bounds how large a maze can grow). The result is a topology
|
||||
-- none of the other generators produce - dense and twisty, with exactly one
|
||||
-- path between any two cells - unlike the open rooms of gen.rooms/gen.bsp
|
||||
-- or the organic blobs of gen.cellular/gen.drunkard. A braiding pass
|
||||
-- afterward knocks down a fixed fraction of the walls still separating two
|
||||
-- already-carved cells, so a fraction of what would otherwise be dead ends
|
||||
-- gain a second way out instead. Selectable like any other entry in
|
||||
-- gen.init's registry, but deliberately left out of Gen.order: nothing
|
||||
-- downstream (builder.lua's rng-ordering discipline, every seed-pinned
|
||||
-- depth test) currently expects the auto-cycle to ever hand it a floor, and
|
||||
-- inserting a fifth entry there would reshuffle which of the existing four
|
||||
-- generators every depth from five onward gets, and with it every rng draw
|
||||
-- a builder places afterward. A maze floor is instead reached deliberately,
|
||||
-- via options.generator = "maze".
|
||||
|
||||
local Map = require("map")
|
||||
local Tiles = require("tiles")
|
||||
|
||||
local Maze = {}
|
||||
|
||||
Maze.BRAID_NUMERATOR = 1
|
||||
Maze.BRAID_DENOMINATOR = 5
|
||||
|
||||
local DIRS = { { 2, 0 }, { -2, 0 }, { 0, 2 }, { 0, -2 } }
|
||||
|
||||
local function shuffledDirs(rng)
|
||||
local dirs = { DIRS[1], DIRS[2], DIRS[3], DIRS[4] }
|
||||
for i = 4, 2, -1 do
|
||||
local j = rng:range(1, i)
|
||||
dirs[i], dirs[j] = dirs[j], dirs[i]
|
||||
end
|
||||
return dirs
|
||||
end
|
||||
|
||||
function Maze.generate(width, height, rng, options)
|
||||
options = options or {}
|
||||
local braidNumerator = options.braidNumerator or Maze.BRAID_NUMERATOR
|
||||
local braidDenominator = options.braidDenominator or Maze.BRAID_DENOMINATOR
|
||||
local map = Map.new(width, height)
|
||||
|
||||
local function key(x, y) return y * width + x end
|
||||
local visited = {}
|
||||
|
||||
local startX, startY = 2, 2
|
||||
visited[key(startX, startY)] = true
|
||||
map:set(startX, startY, Tiles.FLOOR)
|
||||
local stack = { { startX, startY } }
|
||||
|
||||
while #stack > 0 do
|
||||
local top = stack[#stack]
|
||||
local cx, cy = top[1], top[2]
|
||||
local advanced = false
|
||||
for _, dir in ipairs(shuffledDirs(rng)) do
|
||||
local nx, ny = cx + dir[1], cy + dir[2]
|
||||
if nx >= 2 and ny >= 2 and nx <= width - 1 and ny <= height - 1 and not visited[key(nx, ny)] then
|
||||
local wx, wy = cx + dir[1] // 2, cy + dir[2] // 2
|
||||
map:set(wx, wy, Tiles.FLOOR)
|
||||
map:set(nx, ny, Tiles.FLOOR)
|
||||
visited[key(nx, ny)] = true
|
||||
stack[#stack + 1] = { nx, ny }
|
||||
advanced = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not advanced then stack[#stack] = nil end
|
||||
end
|
||||
|
||||
-- Braiding: a wall cell with floor on exactly one opposing pair of sides
|
||||
-- (east-west or north-south) sits between two cells the backtracker above
|
||||
-- already connected some longer way around; knocking a fixed fraction of
|
||||
-- these down trims some, but never all, of the maze's dead ends.
|
||||
for y = 3, height - 2 do
|
||||
for x = 3, width - 2 do
|
||||
if map:get(x, y) == Tiles.WALL then
|
||||
local linksEastWest = map:get(x - 1, y) == Tiles.FLOOR and map:get(x + 1, y) == Tiles.FLOOR
|
||||
local linksNorthSouth = map:get(x, y - 1) == Tiles.FLOOR and map:get(x, y + 1) == Tiles.FLOOR
|
||||
if (linksEastWest or linksNorthSouth) and rng:chance(braidNumerator, braidDenominator) then
|
||||
map:set(x, y, Tiles.FLOOR)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return map
|
||||
end
|
||||
|
||||
return Maze
|
||||
Reference in new issue
Block a user