2023-12-04 23:56:42 +00:00
|
|
|
--[[
|
|
|
|
general pathfinding with a general goal
|
|
|
|
]]
|
|
|
|
|
|
|
|
local path = (...):gsub("pathfind", "")
|
|
|
|
local sort = require(path .. "sort")
|
|
|
|
|
|
|
|
--helper to generate a constant value for default weight, heuristic
|
|
|
|
local generate_constant = function() return 1 end
|
|
|
|
|
|
|
|
--find a path in a weighted graph
|
|
|
|
-- uses A* algorithm internally
|
|
|
|
--returns a table of all the nodes from the start to the goal,
|
|
|
|
-- or false if no path was found
|
|
|
|
--arguments table requires the following fields
|
|
|
|
-- start - the value of the node to start from
|
|
|
|
-- alias: start_node
|
|
|
|
-- is_goal - a function which takes a node, and returns true is a goal
|
|
|
|
-- alias: goal
|
|
|
|
-- neighbours - a function which takes a node, and returns a table of neighbour nodes
|
|
|
|
-- alias: generate_neighbours
|
|
|
|
-- distance - a function which given two nodes, returns the distance between them
|
|
|
|
-- defaults to a constant distance, which means all steps between nodes have the same cost
|
|
|
|
-- alias: weight, g
|
|
|
|
-- heuristic - a function which given a node, returns a heuristic indicative of the distance to the goal;
|
|
|
|
-- can be used to speed up searches at the cost of accuracy by returning some proportion higher than the actual distance
|
|
|
|
-- defaults to a constant value, which means the A* algorithm degrades to breadth-first search
|
|
|
|
-- alias: h
|
2023-12-05 01:33:29 +00:00
|
|
|
local function pathfind(args)
|
2023-12-04 23:56:42 +00:00
|
|
|
local start = args.start or args.start_node
|
|
|
|
local is_goal = args.is_goal or args.goal
|
|
|
|
local neighbours = args.neighbours or args.generate_neighbours
|
|
|
|
local distance = args.distance or args.weight or args.g or generate_constant
|
|
|
|
local heuristic = args.heuristic or args.h or generate_constant
|
|
|
|
|
|
|
|
local predecessor = {}
|
|
|
|
local seen = {}
|
|
|
|
local f_score = {[start] = 0}
|
|
|
|
local g_score = {[start] = 0}
|
|
|
|
|
|
|
|
local function search_compare(a, b)
|
|
|
|
return
|
|
|
|
(f_score[a] or math.huge) >
|
|
|
|
(f_score[b] or math.huge)
|
|
|
|
end
|
|
|
|
local to_search = {}
|
|
|
|
|
|
|
|
local current = start
|
|
|
|
while current and not is_goal(current) do
|
|
|
|
seen[current] = true
|
|
|
|
for i, node in ipairs(neighbours(current)) do
|
|
|
|
if not seen[node] then
|
|
|
|
local tentative_g_score = (g_score[current] or math.huge) + distance(current, node)
|
|
|
|
if g_score[node] == nil then
|
|
|
|
if tentative_g_score < (g_score[node] or math.huge) then
|
|
|
|
predecessor[node] = current
|
|
|
|
g_score[node] = tentative_g_score
|
|
|
|
f_score[node] = tentative_g_score + heuristic(node)
|
|
|
|
end
|
|
|
|
table.insert(to_search, node)
|
|
|
|
sort.insertion_sort(to_search, search_compare)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
current = table.remove(to_search)
|
|
|
|
end
|
|
|
|
|
|
|
|
--didn't make it to the goal
|
|
|
|
if not current or not is_goal(current) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2023-12-05 01:33:29 +00:00
|
|
|
--build up result path
|
|
|
|
local result = {}
|
2023-12-04 23:56:42 +00:00
|
|
|
while current do
|
2023-12-05 01:33:29 +00:00
|
|
|
table.insert(result, 1, current)
|
2023-12-04 23:56:42 +00:00
|
|
|
current = predecessor[current]
|
|
|
|
end
|
2023-12-05 01:33:29 +00:00
|
|
|
return result
|
2023-12-04 23:56:42 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Based on https://github.com/lewtds/pathfinder.lua/blob/master/pathfinder.lua
|
|
|
|
-- with modification to allow for generalised goals/heuristics
|
|
|
|
-- Modified 2022 Max Cahill
|
|
|
|
|
|
|
|
-- Original License:
|
|
|
|
-- Copyright © 2016 Trung Ngo
|
|
|
|
|
|
|
|
-- Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
-- copy of this software and associated documentation files (the \"Software\"),
|
|
|
|
-- to deal in the Software without restriction, including without limitation
|
|
|
|
-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
-- and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
-- Software is furnished to do so, subject to the following conditions:
|
|
|
|
--
|
|
|
|
-- The above copyright notice and this permission notice shall be included in
|
|
|
|
-- all copies or substantial portions of the Software.
|
|
|
|
--
|
|
|
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
-- DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
return pathfind
|