DEBUGGING = false
MAX_SEARCHES = 100
-- -----------------------------------------------------------------
-- DEBUG helper function for debugging
-- -----------------------------------------------------------------
function DEBUG (...)
if DEBUGGING then
print ("DEBUG:", table.concat ( { ... }, " "))
end -- if
end -- DEBUG
function findPathsAstar (start, finish)
local NODE_OPEN = 1
local NODE_CLOSED = 2
local NODE_EXPLORED = 3
local finalCost = 0
-- open list and closed list
open, closed = {}, {}
-- for display
explored_towns = {}
-- add to open list
function makeOpen (which, g, parent)
explored_towns [which] = NODE_OPEN -- open
if open [which] then
-- X, Y won't change because the location doesn't change
-- update this if we got here with a lower G cost
if (g + open [which].h) < open [which].f then
DEBUG(string.format ("Amending %s to have G = %0.3f rather than %0.3f (F = %0.3f rather than %0.3f) and new parents %s",
which, g, open [which].g, g + open [which].h, open [which].f, parent))
open [which].g = g
open [which].f = g + open [which].h
open [which].parent = parent
end -- if
return open [which].f
end -- if already there
-- add it, compute H (unknown in this case)
local h = 0
open [which] = { loc = which, g = g, h = h , f = g + h, parent = parent }
DEBUG (string.format ("Adding node %s to the open list, g = %0.3f, h = %0.3f, f = %0.3f", which, g, h, g + h))
return open [which].f
end -- makeOpen
function isOpen (which)
return open [which]
end -- isOpen
-- add to closed list
function makeClosed (which)
closed [which] = open [which] -- copy values
open [which] = nil -- not in open list any more
explored_towns [which] = NODE_CLOSED -- closed
end -- makeClosed
function isClosed (which)
return closed [which]
end -- isOpen
function addNode (which, g, cost, parent)
assert (which ~= parent, "node cannot have itself as parent: " .. which)
if done then
return
end -- if done
-- see if we reached end
if which == finish then
done = true
paths = { }
table.insert (paths, { loc = finish } )
repeat
table.insert (paths, { loc = parent } )
parent = isClosed (parent) -- find its parent
if parent then
parent = parent.parent
end -- if
until parent == nil
return
end -- if
-- factor in land/water cost
makeOpen (which, cost, parent)
end -- addNode
local depth = 0
local count = 0
done = false
-- found path goes here
paths = {}
-- create particle for the initial town
addNode (start, 0, 0, nil) -- no g, no cost, no parent
while (not done) and next (open) and count < MAX_SEARCHES do
count = count + 1
-- find lowest scoring node in the open list
local fMin = 999999999
local gMin = 999999999
local minNode = nil
local openCount = 0
local closedCount = 0
for k, v in pairs (open) do
openCount = openCount + 1
if v.f < fMin then
fMin = v.f
minNode = v
gMin = v.g -- in case next one is equal, not less
elseif v.f == fMin then -- same f score, pick lower g score
if v.g < gMin then
gMin = v.g
minNode = v
end -- if
end -- if lower or same F score
end -- for
for k, v in pairs (closed) do
closedCount = closedCount + 1
end -- for
DEBUG (string.format ("%d nodes open, %d nodes closed", openCount, closedCount))
loc, g, h, f = minNode.loc, minNode.g, minNode.h, minNode.f
-- add this node to the closed list
makeClosed (loc)
DEBUG (string.rep ("-", 10))
DEBUG (string.format ("Selected node %s - put it on the closed list, g = %0.3f, h = %0.3f, f = %0.3f", loc, g, h, f))
DEBUG (string.rep ("-", 10))
for k, v in pairs (travel [loc]) do
-- calculate cost by adding in each speedwalk multiplier number (eg. 5n 6w would be a cost of 11)
cost = 0
for c in string.gmatch (v, "%d+") do
cost = cost + tonumber (c)
end -- for each number
DEBUG ("Adding", k, "path =", v, "cost =", cost)
if not isClosed (k) then
addNode (k, g, cost, loc, v)
end -- if
end -- for
DEBUG (string.format ("Iteration %d, cost = %0.1f", count, g))
end -- while more particles
if not next (paths) then
print ("No route from " .. start .. " to " .. finish .. " found.")
return nil
end -- it
ports = { }
routes = { }
-- calculate routes and ports
for i = #paths, 1, -1 do
local thisPort = paths [i].loc
table.insert (ports, thisPort)
if i > 1 then
table.insert (routes, travel [thisPort] [paths [i - 1].loc])
end -- if not destination
end -- for each route
return table.concat (routes, "; "), table.concat (ports, "/")
end -- function findPathsAstar