feat(mapgen): add cellular-automata and drunkard-walk generators

This commit is contained in:
dualfroz committed 2026-08-24 09:00:00 +02:00
1 parent eeb96205cf
commit 9076c6c729
2 files changed
+107

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
-- Cellular-automata caves. Seed the interior with random walls, then relax with
-- the classic 4-5 rule so blobs of open space and rock emerge. Connectivity is
-- enforced afterwards by the dungeon layer.
local Map = require("map")
local Tiles = require("tiles")
local Cellular = {}
local function wallNeighbours(grid, width, height, x, y)
local count = 0
for dy = -1, 1 do
for dx = -1, 1 do
if dx ~= 0 or dy ~= 0 then
local nx, ny = x + dx, y + dy
if nx < 1 or ny < 1 or nx > width or ny > height then
count = count + 1
elseif grid[(ny - 1) * width + nx] then
count = count + 1
end
end
end
end
return count
end
function Cellular.generate(width, height, rng, options)
options = options or {}
local fillChance = options.fillChance or 45
local steps = options.steps or 5
local grid = {}
for y = 1, height do
for x = 1, width do
local idx = (y - 1) * width + x
if x == 1 or y == 1 or x == width or y == height then
grid[idx] = true
else
grid[idx] = rng:range(1, 100) <= fillChance
end
end
end
for _ = 1, steps do
local nextGrid = {}
for y = 1, height do
for x = 1, width do
local idx = (y - 1) * width + x
local walls = wallNeighbours(grid, width, height, x, y)
if x == 1 or y == 1 or x == width or y == height then
nextGrid[idx] = true
else
nextGrid[idx] = walls >= 5
end
end
end
grid = nextGrid
end
local map = Map.new(width, height)
for y = 1, height do
for x = 1, width do
if not grid[(y - 1) * width + x] then
map:set(x, y, Tiles.FLOOR)
end
end
end
return map
end
return Cellular
+37
View File
@@ -0,0 +1,37 @@
-- Drunkard's walk: a set of random walkers stumble through the rock carving
-- floor as they go, until a target fraction of the interior is open. Produces
-- sinuous, organic corridors distinct from the blobby caves of the CA generator.
local Map = require("map")
local Tiles = require("tiles")
local Drunkard = {}
local STEPS = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }
function Drunkard.generate(width, height, rng, options)
options = options or {}
local coverage = options.coverage or 0.42
local target = math.floor((width - 2) * (height - 2) * coverage)
local map = Map.new(width, height)
local x, y = width // 2, height // 2
map:set(x, y, Tiles.FLOOR)
local carved = 1
local budget = width * height * 40
while carved < target and budget > 0 do
budget = budget - 1
local dir = STEPS[rng:range(1, 4)]
local nx, ny = x + dir[1], y + dir[2]
if nx >= 2 and ny >= 2 and nx <= width - 1 and ny <= height - 1 then
x, y = nx, ny
if map:get(x, y) ~= Tiles.FLOOR then
map:set(x, y, Tiles.FLOOR)
carved = carved + 1
end
end
end
return map
end
return Drunkard