diff --git a/src/gen/cellular.lua b/src/gen/cellular.lua new file mode 100644 index 0000000..778f850 --- /dev/null +++ b/src/gen/cellular.lua @@ -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 diff --git a/src/gen/drunkard.lua b/src/gen/drunkard.lua new file mode 100644 index 0000000..59e4ef8 --- /dev/null +++ b/src/gen/drunkard.lua @@ -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