/tiles/mapSolver/mapSolver_patterns.lua
Lua | 296 lines | 198 code | 48 blank | 50 comment | 28 complexity | e06532a53b49a2d84feb6e7e4c4c5c3a MD5 | raw file
- prepend = (...):gsub('%.[^%.]+$', '') .. "."
- local basic = require(prepend .. "mapSolver_basic")
- local tileTypes = basic.tileTypes
- local orientation = basic.orientation
- local rect = require(prepend .. "mapSolver_rect")
- -- patterns: pattern library.
- -- Collection of usable patterns to build rects.
- local patterns = {}
- --------------------------------------------------------------------------------
- -- ============== --
- -- HELPERS --
- -- ============== --
- -- Helper function. Returns a rotated copy of a rect so that the original's
- -- East-facing side ends up facing "finalOrientation" (see basic.orientation).
- function patterns.copyFacing(finalOrientation, patternFacingEast)
- for repetitions = finalOrientation, 1, -1 do
- patternFacingEast = patternFacingEast:turn()
- end
- return patternFacingEast
- end
- --------------------------------------------------------------------------------
- -- ==================== --
- -- BASIC SHAPES --
- -- ==================== --
- -- Make a cross-shaped crossing with path width "pathWidth" and with legs of length
- -- legLength. hasE:bool determines if there's a leg to the East, etc.
- function patterns.cross(pathWidth, legLength, hasE, hasN, hasW, hasS)
- local size = 2 * legLength + pathWidth
- local output = rect.new(size, size, tileTypes.WALL)
- output:setRegion(legLength + 1, legLength + 1, pathWidth, pathWidth, tileTypes.FLOOR) -- Clear center.
- if hasE then -- Clear East leg.
- output:setRegion(legLength + pathWidth + 1, legLength + 1, legLength, pathWidth, tileTypes.FLOOR)
- output.connections:add(size, legLength + 1, 1, pathWidth, orientation.EAST)
- end
- if hasN then -- Clear North leg.
- output:setRegion(legLength + 1, 1, pathWidth, legLength, tileTypes.FLOOR)
- output.connections:add(legLength + 1, 1, pathWidth, 1, orientation.NORTH)
- end
- if hasW then -- Clear West leg.
- output:setRegion(1, legLength + 1, legLength, pathWidth, tileTypes.FLOOR)
- output.connections:add(1, legLength + 1, 1, pathWidth, orientation.WEST)
- end
- if hasS then -- Clear South leg.
- output:setRegion(legLength + 1, legLength + pathWidth + 1, pathWidth, legLength, tileTypes.FLOOR)
- output.connections:add(legLength + 1, size, pathWidth, 1, orientation.SOUTH)
- end
- return output
- end
- -- Creates a layered square: "pitSize" tiles (side measure) of "centerType",
- -- surrounded by "pathPadding" tiles of FLOOR, surrounded by 1 tile of WALL. Includes
- -- centered connections in the cardinal directions of width pitSize whenever a has* flag is set to true.
- function patterns.terminus(pitSize, pathPadding, centerType, hasE, hasN, hasW, hasS)
- local pathSize = pitSize + 2 * pathPadding
- local totalSize = pathSize + 2
- local output = patterns.cross(pitSize, pathPadding + 1, hasE, hasN, hasW, hasS)
- output:setRegion(2, 2, pathSize, pathSize, tileTypes.FLOOR)
- output:setRegion(2 + pathPadding, 2 + pathPadding, pitSize, pitSize, centerType)
- return output
- end
- -- Creates a 7x7 entrance block with one connection in the specified direction (actual entrance is 3x3).
- function patterns.entrance(orientation)
- local output = patterns.terminus(3, 1, tileTypes.ENTRANCE, orientation == 0, orientation == 1, orientation == 2, orientation == 3) -- HACK: BAD PRACTISE TO HARDCODE DIRECTIONS, BUT LOOKS UGLY IF WELL DONE.
- output.entrances[1] = {x = 3, y = 3, r = 1.5}
- return output
- end
- -- Creates a 7x7 altar block with one connection in the specified direction (actual altar is 3x3).
- function patterns.altar(orientation)
- local output = patterns.terminus(3, 1, tileTypes.ALTAR, orientation == 0, orientation == 1, orientation == 2, orientation == 3) -- HACK: BAD PRACTISE TO HARDCODE DIRECTIONS, BUT LOOKS UGLY IF WELL DONE.
- output.altars[1] = {x = 3, y = 3, r = 1.5}
- return output
- end
- --------------------------------------------------------------------------------
- -- =============== --
- -- OVERLAYS --
- -- =============== --
- -- Returns a rect of the given size consisting of IGNORE tiles except for a ball of the given radius and tile type.
- function patterns.ball(radius, width, height, tileType)
- local r2 = radius^2
- local cX, cY = width / 2, height / 2
- local output = rect.new(width, height) -- Defaults to IGNORE tiles.
- for j = 1, height do
- for i = 1, width do
- local d2 = math.distSqr(cX, cY, i - 1.5, j - 1.5) -- Check center of tile.
- if d2 <= r2 then
- output:setTile(i, j, tileType)
- end
- end
- end
- return output
- end
- --------------------------------------------------------------------------------
- -- ============== --
- -- ZIG ZAG --
- -- ============== --
- -- Invoke the dark arts to draw a horizontal zig-zag path. Lots of config options!
- function patterns.zigZagHorizontal(mapSolver, config)
- local rng = mapSolver.rng
- local width = config and config.width or 0
- local height = config and config.height or 0
- local minPathWidth = config and config.minPathWidth or 2
- local maxPathWidth = config and config.maxPathWidth or 2
- local minWallWidth = config and config.minWallWidth or 2
- local maxWallWidth = config and config.maxWallWidth or 2
- local minPadX = config and config.minPadX or 1
- local maxPadX = config and config.maxPadX or 1
- local minPadY = config and config.minPadY or 1
- local maxPadY = config and config.maxPadY or 1
- local output = rect.new(width, height, tileTypes.WALL)
- local initalPadding = rng:uniformInt(minPadX, maxPadX)
- local finalPadding = rng:uniformInt(minPadX, maxPadX)
- local finalX = width - finalPadding - 1
- local x = initalPadding + 1
- local goingDown = rng:bernoulli(0.5)
- local padY = rng:uniformInt(minPadY, maxPadY)
- -- These have to be read after the fact:
- local horizontalPathWidth = 3 -- TODO: EVENTUALLY NEGOTIATE PATH SIZE WITH NEIGHBOURING RECT.
- local exitY = goingDown and (padY + 1) or (height - padY - 2)
- -- local firstH = rng:uniformInt(minPathWidth, maxPathWidth)
- -- local firstH = 3
- -- output:setRegion(1, firstY, x, firstH, FLOOR)
- output:setRegion(1, exitY, x, 3, tileTypes.FLOOR)
- output.connections[1] = {i = 1, j = exitY, width = 1, height = 3, orientation = orientation.WEST}
- while x <= finalX - minPathWidth do
- local leftover = finalX - x
- local pathWidth = math.min(leftover, rng:uniformInt(minPathWidth, maxPathWidth))
- local wallWidth = rng:uniformInt(minWallWidth, maxWallWidth)
- local newPadY = rng:uniformInt(minPadY, maxPadY)
- local startY = goingDown and padY + 1 or height - padY
- local endY = goingDown and height - newPadY or newPadY + 1
- local newX = x + pathWidth + wallWidth
- horizontalPathWidth = rng:uniformInt(minPathWidth, maxPathWidth)
- if(newX > finalX - minPathWidth) then
- newX = width
- horizontalPathWidth = 3 -- TODO: EVENTUALLY NEGOTIATE PATH SIZE WITH NEIGHBOURING RECT.
- end
- exitY = endY - (goingDown and (horizontalPathWidth - 1) or 0)
- -- print("[patterns@zigZagHorizontal] x = ", x, ", startY = ", startY, ", pathWidth = ", pathWidth, ", endY = ", endY)
- output:setRegion(x, startY, pathWidth, endY - startY + (goingDown and 1 or -1), tileTypes.FLOOR)
- output:setRegion(x, exitY, newX - x + 1, horizontalPathWidth, tileTypes.FLOOR)
- padY = newPadY
- x = newX
- goingDown = not goingDown
- end
- output.connections[2] = {i = width, j = exitY, width = 1, height = horizontalPathWidth, orientation = orientation.EAST}
- return output
- end
- -- TODO: UNTESTED!
- -- Use zigZagHorizontal to draw either a horizontal or a vertical zigZag. Defaults to horizontal.
- function patterns.zigZag(mapSolver, config)
- local output = patterns.zigZagHorizontal(mapSolver, config)
- if config.isVertical then
- output = output:transpose()
- end
- end
- -- ============ --
- -- PATHS --
- -- ============ --
- -- Creates a straight path and optionally fattens it.
- function patterns.path(length, isHorizontal, connectionWidth, fatten)
- connectionWidth = connectionWidth or 3
- fatten = fatten or 0
- local height = connectionWidth + 2 * fatten + 2
- local output = rect.new(length, height, tileTypes.WALL)
- output:setRegion(1, 2 + fatten, length, connectionWidth, tileTypes.FLOOR) -- Clear central path.
- output.connections:add(1 , 2 + fatten, 1, connectionWidth, orientation.WEST)
- output.connections:add(length, 2 + fatten, 1, connectionWidth, orientation.EAST)
- if fatten > 0 and length > 2 then -- It needs to be long enough!
- output:setRegion(2, 2, length - 2, height - 2, tileTypes.FLOOR) -- Clear fattened path.
- end
- return (isHorizontal and output or output:transpose())
- end
- -- ================== --
- -- CONNECTIONS --
- -- ================== --
- -- TODO: EVEN COMBS ARE FUCKED. FIGURE OUT WHY AND FIX IT!
- -- Creates a multiple joint (a "comb") that joins n paths into a single one, with the single exit set towards orientation.
- function patterns.comb(n, pathWidth, pathPadding, orientation)
- orientation = orientation or basic.orientation.EAST
- pathPadding = pathPadding or 1
- pathWidth = pathWidth or 3
- n = n or 1
- n = (n > 0) and n or 1
- local isEven = (n % 2 == 0)
- local m = math.floor(n / 2)
- local list = {}
- local previous = nil
- for i = 1, m do
- local newCrossing = patterns.cross(pathWidth, pathPadding, false, (i > 1), true, true)
- list[#list + 1] = newCrossing
- if previous then
- newCrossing:connectTo(basic.orientation.NORTH, previous)
- end
- previous = newCrossing
- end
- local middleCrossing = patterns.cross(pathWidth, isEven and 0 or pathPadding, true, (n > 1), not isEven, (n > 1))
- list[#list + 1] = middleCrossing
- if previous then
- middleCrossing:connectTo(basic.orientation.NORTH, previous)
- end
- previous = middleCrossing
- if isEven then
- local extension = patterns.path(pathPadding, true, pathWidth)
- list[#list + 1] = extension
- extension:connectTo(basic.orientation.WEST, middleCrossing)
- end
- for i = 1, m do
- local newCrossing = patterns.cross(pathWidth, pathPadding, false, true, true, (i < m))
- list[#list + 1] = newCrossing
- newCrossing:connectTo(basic.orientation.NORTH, previous)
- previous = newCrossing
- end
- -- patterns.cross(pathWidth, pathPadding, hasE, hasN, hasW, hasS)
- return patterns.copyFacing(orientation, rect.coalesce(list, 0))
- end
- -- TODO: ONLY WORKS IF size-pathWidth IS POSITIVE AND ODD. CHANGE size TO legLength OR SIMILAR?
- -- Make a circular arena with optional entrances in the cardinal directions.
- function patterns.ballCrossing(radius, pathWidth, size, hasE, hasN, hasW, hasS)
- local output = patterns.cross(pathWidth, (size - pathWidth) / 2, hasE, hasN, hasW, hasS) -- TODO: CHECK THIS LEG LENGTH ISN'T PROBLEMATIC
- local overlay = patterns.ball(radius, size, size, tileTypes.FLOOR)
- output:copy(overlay)
- return output
- end
- -- TODO: SHOULD ADD A LIST TO CHOOSE WHICH CONNECTIONS TO INCLUDE AND WHICH ONES TO IGNORE
- -- Make a big rectangular hub with evenly distributed connections on the sides.
- function patterns.hub(rectPadding, outerPathPadding, innerPathPadding, pathWidth, rows, cols)
- local hubWidth = 2 * outerPathPadding + cols * pathWidth + (cols - 1) * innerPathPadding
- local width = 2 * rectPadding + hubWidth
- local hubHeight = 2 * outerPathPadding + rows * pathWidth + (rows - 1) * innerPathPadding
- local height = 2 * rectPadding + hubHeight
- local output = rect.new(width, height, tileTypes.WALL)
- output:setRegion(rectPadding + 1, rectPadding + 1, hubWidth, hubHeight, tileTypes.FLOOR)
- for j = 0, rows - 1 do
- local x, y = 1, rectPadding + outerPathPadding + j * (pathWidth + innerPathPadding) + 1
- local w, h = width, pathWidth
- output:setRegion(x, y, w, h, tileTypes.FLOOR)
- output.connections:add(x , y, 1, h, orientation.WEST)
- output.connections:add(width, y, 1, h, orientation.EAST)
- end
- for i = 0, cols - 1 do
- local x, y = rectPadding + outerPathPadding + i * (pathWidth + innerPathPadding) + 1, 1
- local w, h = pathWidth, height
- output:setRegion(x, y, w, h, tileTypes.FLOOR)
- output.connections:add(x, y , w, 1, orientation.NORTH)
- output.connections:add(x, height, w, 1, orientation.SOUTH)
- end
- return output
- end
- return patterns