diff --git a/src/gen/bsp.lua b/src/gen/bsp.lua new file mode 100644 index 0000000..22cb3fc --- /dev/null +++ b/src/gen/bsp.lua @@ -0,0 +1,69 @@ +-- Binary space partitioning: recursively split the grid into a tree of regions, +-- carve one room per leaf, then connect each internal node's two children by a +-- corridor between the centres of a room drawn from each subtree. + +local Map = require("map") +local Carve = require("gen.carve") + +local Bsp = {} + +local function split(node, rng, minLeaf, depth) + if depth <= 0 or (node.w < minLeaf * 2 and node.h < minLeaf * 2) then + return + end + local horizontal + if node.w > node.h and node.w >= minLeaf * 2 then + horizontal = false + elseif node.h >= minLeaf * 2 then + horizontal = true + else + return + end + + if horizontal then + local cut = rng:range(minLeaf, node.h - minLeaf) + node.left = { x = node.x, y = node.y, w = node.w, h = cut } + node.right = { x = node.x, y = node.y + cut, w = node.w, h = node.h - cut } + else + local cut = rng:range(minLeaf, node.w - minLeaf) + node.left = { x = node.x, y = node.y, w = cut, h = node.h } + node.right = { x = node.x + cut, y = node.y, w = node.w - cut, h = node.h } + end + split(node.left, rng, minLeaf, depth - 1) + split(node.right, rng, minLeaf, depth - 1) +end + +local function carveLeaves(node, map, rng) + if node.left or node.right then + carveLeaves(node.left, map, rng) + carveLeaves(node.right, map, rng) + local a = node.left.room + local b = node.right.room + local ax, ay = Carve.center(a) + local bx, by = Carve.center(b) + Carve.corridor(map, ax, ay, bx, by, rng) + node.room = a + return + end + local w = rng:range(3, math.max(3, node.w - 2)) + local h = rng:range(3, math.max(3, node.h - 2)) + local x = node.x + rng:range(1, math.max(1, node.w - w - 1)) + local y = node.y + rng:range(1, math.max(1, node.h - h - 1)) + local room = { x = x, y = y, w = w, h = h } + Carve.rect(map, room.x, room.y, room.w, room.h) + map.rooms[#map.rooms + 1] = room + node.room = room +end + +function Bsp.generate(width, height, rng, options) + options = options or {} + local minLeaf = options.minLeaf or 7 + local maxDepth = options.maxDepth or 5 + local map = Map.new(width, height) + local root = { x = 2, y = 2, w = width - 3, h = height - 3 } + split(root, rng, minLeaf, maxDepth) + carveLeaves(root, map, rng) + return map +end + +return Bsp diff --git a/src/gen/rooms.lua b/src/gen/rooms.lua new file mode 100644 index 0000000..344ef2b --- /dev/null +++ b/src/gen/rooms.lua @@ -0,0 +1,51 @@ +-- Non-overlapping rectangular rooms joined by L-shaped corridors. Returns a +-- carved Map with map.rooms populated; stair, door and trap placement is the +-- dungeon layer's job, not the generator's. + +local Map = require("map") +local Carve = require("gen.carve") + +local Rooms = {} + +local function overlaps(a, b) + return a.x <= b.x + b.w and a.x + a.w >= b.x + and a.y <= b.y + b.h and a.y + a.h >= b.y +end + +function Rooms.generate(width, height, rng, options) + options = options or {} + local maxRooms = options.maxRooms or 14 + local minSize = options.minRoomSize or 4 + local maxSize = options.maxRoomSize or 9 + local map = Map.new(width, height) + + for _ = 1, maxRooms do + local w = rng:range(minSize, maxSize) + local h = rng:range(minSize, maxSize) + local room = { + x = rng:range(2, width - w - 1), + y = rng:range(2, height - h - 1), + w = w, h = h, + } + local fits = true + for _, other in ipairs(map.rooms) do + if overlaps(room, other) then + fits = false + break + end + end + if fits then + Carve.rect(map, room.x, room.y, room.w, room.h) + if #map.rooms > 0 then + local prevX, prevY = Carve.center(map.rooms[#map.rooms]) + local newX, newY = Carve.center(room) + Carve.corridor(map, prevX, prevY, newX, newY, rng) + end + map.rooms[#map.rooms + 1] = room + end + end + + return map +end + +return Rooms