update changes
[QuestHelper.git] / graph.lua
blobcf10d025f18eaacfc98a6e5bf48efd0b076a7e79
1 QuestHelper_File["graph.lua"] = "Development Version"
3 local floor = math.floor
5 local function Graph_Search(self, first, last)
6 if first ~= last then
7 local open = self.open
8 while #open > 0 do table.remove(open) end
9 for _, n in ipairs(self.nodes) do n.s = 0 end
11 table.insert(open, first)
13 first.g = 0
14 first.s = 2
16 while #open > 0 do
17 local current = table.remove(open)
19 if current == last then
20 first.p = nil
21 return true
22 end
24 current.s = 2
25 local cd = current.g
27 for n, d in pairs(current.n) do
28 if n.s == 0 then
29 -- Haven't visited this node yet.
30 local g = cd+d
31 n.g = g
32 n.s = 1
33 n.p = current
35 local mn, mx = 1, #open+1
37 while mn ~= mx do
38 local m = floor((mn+mx)*0.5)
40 if open[m].g > g then
41 mn = m+1
42 else
43 mx = m
44 end
45 end
47 table.insert(open, mn, n)
48 elseif n.s == 1 then
49 local g = cd+d
50 if g < n.g then
51 n.g = g
52 n.p = current
53 local mn, mx = 1, #open
55 while mn ~= mx do
56 local m = floor((mn+mx)*0.5)
57 if open[m].g > g then
58 mn = m+1
59 else
60 mx = m
61 end
62 end
64 while open[mn] ~= n do
65 mn = mn + 1
66 end
67 mx = #open
68 table.remove(open, mn)
70 while mn ~= mx do
71 local m = floor((mn+mx)*0.5)
73 if open[m].g > g then
74 mn = m+1
75 else
76 mx = m
77 end
78 end
80 table.insert(open, mn, n)
81 end
82 end
83 end
84 end
85 end
86 last.p = nil
87 return first == last
88 end
90 local function sanity(list, node)
91 local last = nil
92 local contains = false
93 for i, n in ipairs(list) do
94 if not (not last or last >= n.g) then
95 for i, n in ipairs(list) do
96 QuestHelper:TextOut(i..") "..n.g)
97 end
98 QuestHelper:Error("Order "..i.."/"..#list.." ("..n.g..")")
99 end
100 assert(not last or last >= n.g)
101 last = n.g
102 if n == node then
103 contains = true
106 if node and not contains then QuestHelper:Error("Missing") end
109 local function Graph_Node_Link(self, next_node, distance)
110 if type(distance) ~= "number" or distance < 0 then QuestHelper:Error("Boom!") end
111 if self ~= next_node then
112 self.n[next_node] = distance
113 table.insert(next_node.r, self)
117 local function Graph_CreateNode(self)
118 local node = QuestHelper:CreateTable("graph_createnode")
119 node.Link = Graph_Node_Link
120 node.n = QuestHelper:CreateTable("graph_createnode.n")
121 node.r = QuestHelper:CreateTable("graph_createnode.r")
122 table.insert(self.nodes, node)
123 return node
126 local function Graph_DestroyNode(self, node)
127 for i = 1,#self.nodes do
128 if self.nodes[i] == node then
129 table.remove(self.nodes, i)
130 QuestHelper:ReleaseTable(node.n)
131 QuestHelper:ReleaseTable(node.r)
132 QuestHelper:ReleaseTable(node)
133 break
138 local function Graph_Reset(self)
139 while #self.nodes > 0 do
140 local node = table.remove(self.nodes)
141 QuestHelper:ReleaseTable(node.n)
142 QuestHelper:ReleaseTable(node.r)
143 QuestHelper:ReleaseTable(node)
147 local function Graph_AddRouteStartNode(self, n, g, end_list)
148 local open = self.open
150 n.p = nil
152 if n.s == 3 then
153 n.s = 4
154 elseif n.s == 0 then
155 n.s = 1
156 else
157 local og = n.g
158 if g < og then
159 local mn, mx = 1, #open
161 while mn ~= mx do
162 local m = floor((mn+mx)*0.5)
163 if open[m].g > og then
164 mn = m+1
165 else
166 mx = m
170 while open[mn] ~= n do
171 mn = mn + 1
174 table.remove(open, mn)
175 else
176 return -- Don't want to insert the node a second time.
180 local mn, mx = 1, #open+1
182 while mn ~= mx do
183 local m = floor((mn+mx)*0.5)
185 QuestHelper: Assert(open[m].g and g, string.format("nil-with-number issue, %s %s and %d inside %d %d", tostring(open[m].g), tostring(g), m, mn, mx))
186 if open[m].g > g then
187 mn = m+1
188 else
189 mx = m
193 table.insert(open, mn, n)
195 n.g = g
198 local function Graph_DoRouteSearch(self, end_list)
199 local open = self.open
200 local end_count = #end_list
202 while #open > 0 do
203 local current = table.remove(open)
205 if current.s == 3 or current.s == 4 then
206 current.s = 2
207 return current
210 current.s = 2
212 local cd = current.g
214 for n, d in pairs(current.n) do
215 if n.s == 0 or n.s == 3 then
216 -- Haven't visited this node yet.
217 local g = cd+d
218 n.p = current
219 n.g = g
221 n.s = n.s == 3 and 4 or 1
223 local mn, mx = 1, #open+1
225 while mn ~= mx do
226 local m = floor((mn+mx)*0.5)
228 if open[m].g > g then
229 mn = m+1
230 else
231 mx = m
235 table.insert(open, mn, n)
236 elseif n.s == 1 or n.s == 4 then
237 local g = cd+d
238 local og = n.g
239 if g < og then
240 n.p = current
242 local mn, mx = 1, #open
244 while mn ~= mx do
245 local m = floor((mn+mx)*0.5)
246 if open[m].g > og then
247 mn = m+1
248 else
249 mx = m
253 while open[mn] ~= n do
254 mn = mn + 1
257 mx = #open
259 table.remove(open, mn)
261 while mn ~= mx do
262 local m = floor((mn+mx)*0.5)
264 if open[m].g > g then
265 mn = m+1
266 else
267 mx = m
271 n.g = g
272 table.insert(open, mn, n)
279 local function Graph_PrepareSearch(self)
280 local open = self.open
281 for n in pairs(open) do open[n] = nil end
282 for _, n in pairs(self.nodes) do
283 n.s = 0
287 local function Graph_AddStartNode(self, n, g, end_list)
288 local open = self.open
290 n.p = n
292 if n.s == 3 then
293 n.s = 4
294 elseif n.s == 0 then
295 n.s = 1
296 else
297 local og = n.g
298 if g < og then
299 local mn, mx = 1, #open
301 while mn ~= mx do
302 local m = floor((mn+mx)*0.5)
303 if open[m].g > og then
304 mn = m+1
305 else
306 mx = m
310 while open[mn] ~= n do
311 mn = mn + 1
314 table.remove(open, mn)
315 else
316 return -- Don't want to insert the node a second time.
320 local mn, mx = 1, #open+1
322 while mn ~= mx do
323 local m = floor((mn+mx)*0.5)
325 if open[m].g > g then
326 mn = m+1
327 else
328 mx = m
332 table.insert(open, mn, n)
334 n.g = g
337 local function Graph_DoSearch(self, end_list)
338 local open = self.open
339 local end_count = #end_list
341 while #open > 0 do
342 local current = table.remove(open)
344 if current.s == 3 or current.s == 4 then
345 current.s = 2
346 return current
349 current.s = 2
351 local cd = current.g
353 for n, d in pairs(current.n) do
354 if n.s == 0 or n.s == 3 then
355 -- Haven't visited this node yet.
356 local g = cd+d
357 n.g = g
358 n.p = current.p
360 n.s = n.s == 3 and 4 or 1
362 local mn, mx = 1, #open+1
364 while mn ~= mx do
365 local m = floor((mn+mx)*0.5)
367 if open[m].g > g then
368 mn = m+1
369 else
370 mx = m
374 table.insert(open, mn, n)
375 elseif n.s == 1 or n.s == 4 then
376 local g = cd+d
377 local og = n.g
378 if g < og then
379 n.p = current.p
381 local mn, mx = 1, #open
383 while mn ~= mx do
384 local m = floor((mn+mx)*0.5)
385 if open[m].g > og then
386 mn = m+1
387 else
388 mx = m
392 while open[mn] ~= n do
393 mn = mn + 1
396 mx = #open
398 table.remove(open, mn)
400 while mn ~= mx do
401 local m = floor((mn+mx)*0.5)
403 if open[m].g > g then
404 mn = m+1
405 else
406 mx = m
410 n.g = g
411 table.insert(open, mn, n)
418 local removed = {}
420 local function Graph_DoFullSearch(self, end_list)
421 local open = self.open
422 local end_count = #end_list
424 while #open > 0 do
425 local current = table.remove(open)
427 if current.s == 3 or current.s == 4 then
428 if end_count == 1 then
429 for i = 1,#removed do
430 table.insert(end_list, table.remove(removed))
432 return
435 for i = 1,end_count do
436 if end_list[i] == current then
437 table.insert(removed, table.remove(end_list, i))
438 break
442 end_count = end_count - 1
445 current.s = 2
447 local cd = current.g
449 for n, d in pairs(current.n) do
450 if n.s == 0 or n.s == 3 then
451 -- Haven't visited this node yet.
452 local g = cd+d
453 n.g = g
454 n.p = current.p
456 n.s = n.s == 3 and 4 or 1
458 local mn, mx = 1, #open+1
460 while mn ~= mx do
461 local m = floor((mn+mx)*0.5)
463 if open[m].g > g then
464 mn = m+1
465 else
466 mx = m
470 table.insert(open, mn, n)
471 elseif n.s == 1 or n.s == 4 then
472 local g = cd+d
473 local og = n.g
474 if g < og then
475 n.p = current.p
477 local mn, mx = 1, #open
479 while mn ~= mx do
480 local m = floor((mn+mx)*0.5)
481 if open[m].g > og then
482 mn = m+1
483 else
484 mx = m
488 while open[mn] ~= n do
489 mn = mn + 1
492 mx = #open
494 table.remove(open, mn)
496 while mn ~= mx do
497 local m = floor((mn+mx)*0.5)
499 if open[m].g > g then
500 mn = m+1
501 else
502 mx = m
506 n.g = g
507 table.insert(open, mn, n)
513 for i, n in ipairs(end_list) do
514 QuestHelper:TextOut(i..") Failed to reach: "..n.name..", s="..n.s)
517 for i = 1,#removed do
518 table.insert(end_list, table.remove(removed))
521 for i, n in ipairs(end_list) do
522 QuestHelper:TextOut(i..") End node: "..n.name..", s="..n.s)
525 QuestHelper:Error("Boom!")
527 for i = 1,#removed do
528 table.insert(end_list, table.remove(removed))
532 local function propLinks(node)
533 if node.s ~= 1 then
534 node.s = 1
535 for n in pairs(node.n) do
536 propLinks(n)
541 function Graph_SanityCheck(self)
542 for i = 1,#self.nodes do
543 for _, n in ipairs(self.nodes) do
544 n.s = 0
547 propLinks(self.nodes[i])
549 for _, n in ipairs(self.nodes) do
550 if n.s ~= 1 then
551 QuestHelper:TextOut((n.name or "unknown").." isn't reachable from "..(self.nodes[i].name or "unknown"))
557 function QuestHelper:CreateGraph()
558 local graph = self:CreateTable("graph")
559 graph.nodes = self:CreateTable("graph.nodes")
560 graph.end_nodes = self:CreateTable("graph.end_nodes")
561 graph.open = self:CreateTable("graph.open")
563 graph.CreateNode = Graph_CreateNode
564 graph.DestroyNode = Graph_DestroyNode
565 graph.Reset = Graph_Reset
567 graph.PrepareSearch = Graph_PrepareSearch
569 graph.AddRouteStartNode = Graph_AddRouteStartNode
570 graph.DoRouteSearch = Graph_DoRouteSearch
571 graph.Search = Graph_Search
572 graph.SanityCheck = Graph_SanityCheck
574 -- These don't deal with finding paths and instead only care about finding distances.
575 graph.AddStartNode = Graph_AddStartNode
576 graph.DoSearch = Graph_DoSearch
577 graph.DoFullSearch = Graph_DoFullSearch
579 return graph
582 function QuestHelper:ReleaseGraph(graph)
583 graph:Reset()
584 self:ReleaseTable(graph.nodes)
585 self:ReleaseTable(graph.end_nodes)
586 self:ReleaseTable(graph.open)
587 self:ReleaseTable(graph)
590 QuestHelper.world_graph = QuestHelper:CreateGraph()