-
Notifications
You must be signed in to change notification settings - Fork 4
/
astar.lua
109 lines (91 loc) · 2.55 KB
/
astar.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
-- AStar
--
-- map:
-- get_neighbors(node, from_node, add_neighbor_fn, userdata) -- all moveable neighbors
-- get_cost(from_node, to_node, userdata)
-- estimate_cost(start_node, goal_node, userdata)
--
-- node:
-- x:
-- y:
-- ==: check two node is same
--
local M = {}
M.__index = M
local private = {}
local inf = 1 / 0
function M.new(...)
local obj = setmetatable({}, M)
obj:init(...)
return obj
end
function M:init(map)
self.map = map
assert(
map.get_neighbors and map.get_cost and map.estimate_cost,
"Invalid map, must include get_neighbors, get_cost and estimate_cost functions"
)
end
-- start: start node
-- goal: goal node
function M:find(start, goal, userdata)
local map = self.map
local openset = { [start] = true }
local closedset = {}
local came_from = {}
local g_score, h_score, f_score = {}, {}, {}
g_score[start] = 0
h_score[start] = map:estimate_cost(start, goal, userdata)
f_score[start] = h_score[start]
local current
local add_neighbor_fn = function(neighbor, cost)
if not closedset[neighbor] then
if not cost then cost = map:get_cost(current, neighbor, userdata) end
local tentative_g_score = g_score[current] + cost
local openset_idx = openset[neighbor]
if not openset_idx or tentative_g_score < g_score[neighbor] then
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
h_score[neighbor] = h_score[neighbor] or map:estimate_cost(neighbor, goal, userdata)
f_score[neighbor] = tentative_g_score + h_score[neighbor]
openset[neighbor] = true
end
end
end
while next(openset) do
current = private.pop_best_node(openset, f_score)
openset[current] = nil
if current == goal then
local path = private.unwind_path({}, came_from, goal)
table.insert(path, goal)
return path, g_score, h_score, f_score, came_from
end
closedset[current] = true
local from_node = came_from[current]
map:get_neighbors(current, from_node, add_neighbor_fn, userdata)
end
return nil, g_score, h_score, f_score, came_from -- no valid path
end
----------------------------
-- return: node
function private.pop_best_node(set, score)
local best, node = inf, nil
for k, v in pairs(set) do
local s = score[k]
if s < best then
best, node = s, k
end
end
if not node then return end
set[node] = nil
return node
end
function private.unwind_path(flat_path, map, current_node)
if map[current_node] then
table.insert(flat_path, 1, map [ current_node ])
return private.unwind_path(flat_path, map, map [ current_node ])
else
return flat_path
end
end
return M