PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/tiles/mapSolver/mapSolver_patterns.lua

https://gitlab.com/Aconitin/TowerDefense
Lua | 296 lines | 198 code | 48 blank | 50 comment | 28 complexity | e06532a53b49a2d84feb6e7e4c4c5c3a MD5 | raw file
  1. prepend = (...):gsub('%.[^%.]+$', '') .. "."
  2. local basic = require(prepend .. "mapSolver_basic")
  3. local tileTypes = basic.tileTypes
  4. local orientation = basic.orientation
  5. local rect = require(prepend .. "mapSolver_rect")
  6. -- patterns: pattern library.
  7. -- Collection of usable patterns to build rects.
  8. local patterns = {}
  9. --------------------------------------------------------------------------------
  10. -- ============== --
  11. -- HELPERS --
  12. -- ============== --
  13. -- Helper function. Returns a rotated copy of a rect so that the original's
  14. -- East-facing side ends up facing "finalOrientation" (see basic.orientation).
  15. function patterns.copyFacing(finalOrientation, patternFacingEast)
  16. for repetitions = finalOrientation, 1, -1 do
  17. patternFacingEast = patternFacingEast:turn()
  18. end
  19. return patternFacingEast
  20. end
  21. --------------------------------------------------------------------------------
  22. -- ==================== --
  23. -- BASIC SHAPES --
  24. -- ==================== --
  25. -- Make a cross-shaped crossing with path width "pathWidth" and with legs of length
  26. -- legLength. hasE:bool determines if there's a leg to the East, etc.
  27. function patterns.cross(pathWidth, legLength, hasE, hasN, hasW, hasS)
  28. local size = 2 * legLength + pathWidth
  29. local output = rect.new(size, size, tileTypes.WALL)
  30. output:setRegion(legLength + 1, legLength + 1, pathWidth, pathWidth, tileTypes.FLOOR) -- Clear center.
  31. if hasE then -- Clear East leg.
  32. output:setRegion(legLength + pathWidth + 1, legLength + 1, legLength, pathWidth, tileTypes.FLOOR)
  33. output.connections:add(size, legLength + 1, 1, pathWidth, orientation.EAST)
  34. end
  35. if hasN then -- Clear North leg.
  36. output:setRegion(legLength + 1, 1, pathWidth, legLength, tileTypes.FLOOR)
  37. output.connections:add(legLength + 1, 1, pathWidth, 1, orientation.NORTH)
  38. end
  39. if hasW then -- Clear West leg.
  40. output:setRegion(1, legLength + 1, legLength, pathWidth, tileTypes.FLOOR)
  41. output.connections:add(1, legLength + 1, 1, pathWidth, orientation.WEST)
  42. end
  43. if hasS then -- Clear South leg.
  44. output:setRegion(legLength + 1, legLength + pathWidth + 1, pathWidth, legLength, tileTypes.FLOOR)
  45. output.connections:add(legLength + 1, size, pathWidth, 1, orientation.SOUTH)
  46. end
  47. return output
  48. end
  49. -- Creates a layered square: "pitSize" tiles (side measure) of "centerType",
  50. -- surrounded by "pathPadding" tiles of FLOOR, surrounded by 1 tile of WALL. Includes
  51. -- centered connections in the cardinal directions of width pitSize whenever a has* flag is set to true.
  52. function patterns.terminus(pitSize, pathPadding, centerType, hasE, hasN, hasW, hasS)
  53. local pathSize = pitSize + 2 * pathPadding
  54. local totalSize = pathSize + 2
  55. local output = patterns.cross(pitSize, pathPadding + 1, hasE, hasN, hasW, hasS)
  56. output:setRegion(2, 2, pathSize, pathSize, tileTypes.FLOOR)
  57. output:setRegion(2 + pathPadding, 2 + pathPadding, pitSize, pitSize, centerType)
  58. return output
  59. end
  60. -- Creates a 7x7 entrance block with one connection in the specified direction (actual entrance is 3x3).
  61. function patterns.entrance(orientation)
  62. 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.
  63. output.entrances[1] = {x = 3, y = 3, r = 1.5}
  64. return output
  65. end
  66. -- Creates a 7x7 altar block with one connection in the specified direction (actual altar is 3x3).
  67. function patterns.altar(orientation)
  68. 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.
  69. output.altars[1] = {x = 3, y = 3, r = 1.5}
  70. return output
  71. end
  72. --------------------------------------------------------------------------------
  73. -- =============== --
  74. -- OVERLAYS --
  75. -- =============== --
  76. -- Returns a rect of the given size consisting of IGNORE tiles except for a ball of the given radius and tile type.
  77. function patterns.ball(radius, width, height, tileType)
  78. local r2 = radius^2
  79. local cX, cY = width / 2, height / 2
  80. local output = rect.new(width, height) -- Defaults to IGNORE tiles.
  81. for j = 1, height do
  82. for i = 1, width do
  83. local d2 = math.distSqr(cX, cY, i - 1.5, j - 1.5) -- Check center of tile.
  84. if d2 <= r2 then
  85. output:setTile(i, j, tileType)
  86. end
  87. end
  88. end
  89. return output
  90. end
  91. --------------------------------------------------------------------------------
  92. -- ============== --
  93. -- ZIG ZAG --
  94. -- ============== --
  95. -- Invoke the dark arts to draw a horizontal zig-zag path. Lots of config options!
  96. function patterns.zigZagHorizontal(mapSolver, config)
  97. local rng = mapSolver.rng
  98. local width = config and config.width or 0
  99. local height = config and config.height or 0
  100. local minPathWidth = config and config.minPathWidth or 2
  101. local maxPathWidth = config and config.maxPathWidth or 2
  102. local minWallWidth = config and config.minWallWidth or 2
  103. local maxWallWidth = config and config.maxWallWidth or 2
  104. local minPadX = config and config.minPadX or 1
  105. local maxPadX = config and config.maxPadX or 1
  106. local minPadY = config and config.minPadY or 1
  107. local maxPadY = config and config.maxPadY or 1
  108. local output = rect.new(width, height, tileTypes.WALL)
  109. local initalPadding = rng:uniformInt(minPadX, maxPadX)
  110. local finalPadding = rng:uniformInt(minPadX, maxPadX)
  111. local finalX = width - finalPadding - 1
  112. local x = initalPadding + 1
  113. local goingDown = rng:bernoulli(0.5)
  114. local padY = rng:uniformInt(minPadY, maxPadY)
  115. -- These have to be read after the fact:
  116. local horizontalPathWidth = 3 -- TODO: EVENTUALLY NEGOTIATE PATH SIZE WITH NEIGHBOURING RECT.
  117. local exitY = goingDown and (padY + 1) or (height - padY - 2)
  118. -- local firstH = rng:uniformInt(minPathWidth, maxPathWidth)
  119. -- local firstH = 3
  120. -- output:setRegion(1, firstY, x, firstH, FLOOR)
  121. output:setRegion(1, exitY, x, 3, tileTypes.FLOOR)
  122. output.connections[1] = {i = 1, j = exitY, width = 1, height = 3, orientation = orientation.WEST}
  123. while x <= finalX - minPathWidth do
  124. local leftover = finalX - x
  125. local pathWidth = math.min(leftover, rng:uniformInt(minPathWidth, maxPathWidth))
  126. local wallWidth = rng:uniformInt(minWallWidth, maxWallWidth)
  127. local newPadY = rng:uniformInt(minPadY, maxPadY)
  128. local startY = goingDown and padY + 1 or height - padY
  129. local endY = goingDown and height - newPadY or newPadY + 1
  130. local newX = x + pathWidth + wallWidth
  131. horizontalPathWidth = rng:uniformInt(minPathWidth, maxPathWidth)
  132. if(newX > finalX - minPathWidth) then
  133. newX = width
  134. horizontalPathWidth = 3 -- TODO: EVENTUALLY NEGOTIATE PATH SIZE WITH NEIGHBOURING RECT.
  135. end
  136. exitY = endY - (goingDown and (horizontalPathWidth - 1) or 0)
  137. -- print("[patterns@zigZagHorizontal] x = ", x, ", startY = ", startY, ", pathWidth = ", pathWidth, ", endY = ", endY)
  138. output:setRegion(x, startY, pathWidth, endY - startY + (goingDown and 1 or -1), tileTypes.FLOOR)
  139. output:setRegion(x, exitY, newX - x + 1, horizontalPathWidth, tileTypes.FLOOR)
  140. padY = newPadY
  141. x = newX
  142. goingDown = not goingDown
  143. end
  144. output.connections[2] = {i = width, j = exitY, width = 1, height = horizontalPathWidth, orientation = orientation.EAST}
  145. return output
  146. end
  147. -- TODO: UNTESTED!
  148. -- Use zigZagHorizontal to draw either a horizontal or a vertical zigZag. Defaults to horizontal.
  149. function patterns.zigZag(mapSolver, config)
  150. local output = patterns.zigZagHorizontal(mapSolver, config)
  151. if config.isVertical then
  152. output = output:transpose()
  153. end
  154. end
  155. -- ============ --
  156. -- PATHS --
  157. -- ============ --
  158. -- Creates a straight path and optionally fattens it.
  159. function patterns.path(length, isHorizontal, connectionWidth, fatten)
  160. connectionWidth = connectionWidth or 3
  161. fatten = fatten or 0
  162. local height = connectionWidth + 2 * fatten + 2
  163. local output = rect.new(length, height, tileTypes.WALL)
  164. output:setRegion(1, 2 + fatten, length, connectionWidth, tileTypes.FLOOR) -- Clear central path.
  165. output.connections:add(1 , 2 + fatten, 1, connectionWidth, orientation.WEST)
  166. output.connections:add(length, 2 + fatten, 1, connectionWidth, orientation.EAST)
  167. if fatten > 0 and length > 2 then -- It needs to be long enough!
  168. output:setRegion(2, 2, length - 2, height - 2, tileTypes.FLOOR) -- Clear fattened path.
  169. end
  170. return (isHorizontal and output or output:transpose())
  171. end
  172. -- ================== --
  173. -- CONNECTIONS --
  174. -- ================== --
  175. -- TODO: EVEN COMBS ARE FUCKED. FIGURE OUT WHY AND FIX IT!
  176. -- Creates a multiple joint (a "comb") that joins n paths into a single one, with the single exit set towards orientation.
  177. function patterns.comb(n, pathWidth, pathPadding, orientation)
  178. orientation = orientation or basic.orientation.EAST
  179. pathPadding = pathPadding or 1
  180. pathWidth = pathWidth or 3
  181. n = n or 1
  182. n = (n > 0) and n or 1
  183. local isEven = (n % 2 == 0)
  184. local m = math.floor(n / 2)
  185. local list = {}
  186. local previous = nil
  187. for i = 1, m do
  188. local newCrossing = patterns.cross(pathWidth, pathPadding, false, (i > 1), true, true)
  189. list[#list + 1] = newCrossing
  190. if previous then
  191. newCrossing:connectTo(basic.orientation.NORTH, previous)
  192. end
  193. previous = newCrossing
  194. end
  195. local middleCrossing = patterns.cross(pathWidth, isEven and 0 or pathPadding, true, (n > 1), not isEven, (n > 1))
  196. list[#list + 1] = middleCrossing
  197. if previous then
  198. middleCrossing:connectTo(basic.orientation.NORTH, previous)
  199. end
  200. previous = middleCrossing
  201. if isEven then
  202. local extension = patterns.path(pathPadding, true, pathWidth)
  203. list[#list + 1] = extension
  204. extension:connectTo(basic.orientation.WEST, middleCrossing)
  205. end
  206. for i = 1, m do
  207. local newCrossing = patterns.cross(pathWidth, pathPadding, false, true, true, (i < m))
  208. list[#list + 1] = newCrossing
  209. newCrossing:connectTo(basic.orientation.NORTH, previous)
  210. previous = newCrossing
  211. end
  212. -- patterns.cross(pathWidth, pathPadding, hasE, hasN, hasW, hasS)
  213. return patterns.copyFacing(orientation, rect.coalesce(list, 0))
  214. end
  215. -- TODO: ONLY WORKS IF size-pathWidth IS POSITIVE AND ODD. CHANGE size TO legLength OR SIMILAR?
  216. -- Make a circular arena with optional entrances in the cardinal directions.
  217. function patterns.ballCrossing(radius, pathWidth, size, hasE, hasN, hasW, hasS)
  218. local output = patterns.cross(pathWidth, (size - pathWidth) / 2, hasE, hasN, hasW, hasS) -- TODO: CHECK THIS LEG LENGTH ISN'T PROBLEMATIC
  219. local overlay = patterns.ball(radius, size, size, tileTypes.FLOOR)
  220. output:copy(overlay)
  221. return output
  222. end
  223. -- TODO: SHOULD ADD A LIST TO CHOOSE WHICH CONNECTIONS TO INCLUDE AND WHICH ONES TO IGNORE
  224. -- Make a big rectangular hub with evenly distributed connections on the sides.
  225. function patterns.hub(rectPadding, outerPathPadding, innerPathPadding, pathWidth, rows, cols)
  226. local hubWidth = 2 * outerPathPadding + cols * pathWidth + (cols - 1) * innerPathPadding
  227. local width = 2 * rectPadding + hubWidth
  228. local hubHeight = 2 * outerPathPadding + rows * pathWidth + (rows - 1) * innerPathPadding
  229. local height = 2 * rectPadding + hubHeight
  230. local output = rect.new(width, height, tileTypes.WALL)
  231. output:setRegion(rectPadding + 1, rectPadding + 1, hubWidth, hubHeight, tileTypes.FLOOR)
  232. for j = 0, rows - 1 do
  233. local x, y = 1, rectPadding + outerPathPadding + j * (pathWidth + innerPathPadding) + 1
  234. local w, h = width, pathWidth
  235. output:setRegion(x, y, w, h, tileTypes.FLOOR)
  236. output.connections:add(x , y, 1, h, orientation.WEST)
  237. output.connections:add(width, y, 1, h, orientation.EAST)
  238. end
  239. for i = 0, cols - 1 do
  240. local x, y = rectPadding + outerPathPadding + i * (pathWidth + innerPathPadding) + 1, 1
  241. local w, h = pathWidth, height
  242. output:setRegion(x, y, w, h, tileTypes.FLOOR)
  243. output.connections:add(x, y , w, 1, orientation.NORTH)
  244. output.connections:add(x, height, w, 1, orientation.SOUTH)
  245. end
  246. return output
  247. end
  248. return patterns