dijkstraMap.lua 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. --- DijkstraMap Pathfinding.
  2. -- Based on the DijkstraMap Article on RogueBasin, http://roguebasin.roguelikedevelopment.org/index.php?title=The_Incredible_Power_of_Dijkstra_Maps
  3. -- @module ROT.DijkstraMap
  4. local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', ''))
  5. local DijkstraMap = ROT.Path:extend("DijkstraMap")
  6. local PointSet = ROT.Type.PointSet
  7. local Grid = ROT.Type.Grid
  8. --- Constructor.
  9. -- @tparam int goalX x-position of cell that map will 'roll down' to
  10. -- @tparam int goalY y-position of cell that map will 'roll down' to
  11. -- @tparam function passableCallback a function with two parameters (x, y) that returns true if a map cell is passable
  12. -- @tparam table options Options
  13. -- @tparam[opt=8] int options.topology Directions for movement Accepted values (4 or 8)
  14. function DijkstraMap:init(goalX, goalY, passableCallback, options)
  15. DijkstraMap.super.init(self, goalX, goalY, passableCallback, options)
  16. self._map = Grid()
  17. self._goals = PointSet()
  18. self._dirty = true
  19. if goalX and goalY then
  20. self:addGoal(goalX, goalY)
  21. end
  22. end
  23. --- Establish values for all cells in map.
  24. -- call after ROT.DijkstraMap:new(goalX, goalY, passableCallback)
  25. function DijkstraMap:compute(x, y, callback, topology)
  26. self:_rebuild()
  27. local dx, dy = self:dirTowardsGoal(x, y, topology)
  28. if dx then
  29. callback(x, y)
  30. end
  31. while dx do
  32. x, y = x + dx, y + dy
  33. callback(x, y)
  34. dx, dy = self:dirTowardsGoal(x, y, topology)
  35. end
  36. end
  37. --- Run a callback function on every cell in the map
  38. -- @tparam function callback A function with x and y parameters that will be run on every cell in the map
  39. function DijkstraMap:create(callback)
  40. self:_rebuild()
  41. for _, x, y, v in self._map:each() do
  42. callback(x, y, v)
  43. end
  44. end
  45. --- Check if a goal exists at a position.
  46. -- @tparam int x the x-value to check
  47. -- @tparam int y the y-value to check
  48. function DijkstraMap:hasGoal(x, y)
  49. return not not self._goals:find(x, y)
  50. end
  51. --- Add new goal.
  52. -- @tparam int x the x-value of the new goal cell
  53. -- @tparam int y the y-value of the new goal cell
  54. function DijkstraMap:addGoal(x, y)
  55. if self._goals:push(x, y) then
  56. self._dirty = true
  57. end
  58. return self
  59. end
  60. --- Remove a goal.
  61. -- @tparam int gx the x-value of the goal cell
  62. -- @tparam int gy the y-value of the goal cell
  63. function DijkstraMap:removeGoal(x, y)
  64. if self._goals:prune(x, y) then
  65. self._dirty = true
  66. end
  67. return self
  68. end
  69. --- Remove all goals.
  70. function DijkstraMap:clearGoals()
  71. self._goals = PointSet()
  72. self._dirty = true
  73. return self
  74. end
  75. --- Get the direction of the goal from a given position
  76. -- @tparam int x x-value of current position
  77. -- @tparam int y y-value of current position
  78. -- @treturn int xDir X-Direction towards goal. Either -1, 0, or 1
  79. -- @treturn int yDir Y-Direction towards goal. Either -1, 0, or 1
  80. function DijkstraMap:dirTowardsGoal(x, y, topology)
  81. local low = self._map:getCell(x, y)
  82. if not low or low == 0 or low == math.huge then return end
  83. local dir=nil
  84. for i = 1, topology or self._options.topology do
  85. local v = ROT.DIRS.FOUR[i] or ROT.DIRS.EIGHT[(i - 4) * 2]
  86. local tx=(x+v[1])
  87. local ty=(y+v[2])
  88. local val = self._map:getCell(tx, ty)
  89. if val and i < 5 and val <= low or val < low then
  90. low=val
  91. dir=v
  92. end
  93. end
  94. if dir then return dir[1],dir[2] end
  95. end
  96. --- Output map values to console.
  97. -- For debugging, will send a comma separated output of cell values to the console.
  98. -- @tparam boolean[opt=false] returnString Will return the output in addition to sending it to console if true.
  99. function DijkstraMap:debug(returnString)
  100. self:_rebuild()
  101. local ls
  102. if returnString then ls='' end
  103. for y=1,self._dimensions.h do
  104. local s=''
  105. for x=1,self._dimensions.w do
  106. s=s..self._map:getCell(x, y)..','
  107. end
  108. io.write(s); io.flush()
  109. if returnString then ls=ls..s..'\n' end
  110. end
  111. if returnString then return ls end
  112. end
  113. function DijkstraMap:_addCell(x, y, value)
  114. self._nextCells:push(x, y)
  115. self._map:setCell(x, y, value)
  116. return value
  117. end
  118. function DijkstraMap:_visitAdjacent(x, y)
  119. if not self._passableCallback(x, y) then return end
  120. local low = math.huge
  121. for i = 1, #self._dirs do
  122. local tx = x + self._dirs[i][1]
  123. local ty = y + self._dirs[i][2]
  124. local value = self._map:getCell(tx, ty)
  125. or self:_addCell(tx, ty, math.huge)
  126. low = math.min(low, value)
  127. end
  128. if self._map:getCell(x, y) > low + 2 then
  129. self._map:setCell(x, y, low + 1)
  130. end
  131. end
  132. function DijkstraMap:_rebuild(callback)
  133. if not self._dirty then return end
  134. self._dirty = false
  135. self._nextCells = PointSet()
  136. self._map = Grid()
  137. for _, x, y in self._goals:each() do
  138. self:_addCell(x, y, 0)
  139. end
  140. while #self._nextCells > 0 do
  141. for i in self._nextCells:each() do
  142. self:_visitAdjacent(self._nextCells:pluck(i))
  143. end
  144. end
  145. end
  146. return DijkstraMap