bump to 3.1
[QuestHelper.git] / graph.lua
blobbfe7b89c110ccd86b2ad1c7a9a94200bef86511b
1 QuestHelper_File["graph.lua"] = "Development Version"
2 QuestHelper_Loadtime["graph.lua"] = GetTime()
4 local floor = math.floor
6 local function Graph_Search(self, first, last)
7 if first ~= last then
8 local open = self.open
9 while #open > 0 do table.remove(open) end
10 for _, n in ipairs(self.nodes) do n.s = 0 end
12 table.insert(open, first)
14 first.g = 0
15 first.s = 2
17 while #open > 0 do
18 local current = table.remove(open)
20 if current == last then
21 first.p = nil
22 return true
23 end
25 current.s = 2
26 local cd = current.g
28 for n, d in pairs(current.n) do
29 if n.s == 0 then
30 -- Haven't visited this node yet.
31 local g = cd+d
32 n.g = g
33 n.s = 1
34 n.p = current
36 local mn, mx = 1, #open+1
38 while mn ~= mx do
39 local m = floor((mn+mx)*0.5)
41 if open[m].g > g then
42 mn = m+1
43 else
44 mx = m
45 end
46 end
48 table.insert(open, mn, n)
49 elseif n.s == 1 then
50 local g = cd+d
51 if g < n.g then
52 n.g = g
53 n.p = current
54 local mn, mx = 1, #open
56 while mn ~= mx do
57 local m = floor((mn+mx)*0.5)
58 if open[m].g > g then
59 mn = m+1
60 else
61 mx = m
62 end
63 end
65 while open[mn] ~= n do
66 mn = mn + 1
67 end
68 mx = #open
69 table.remove(open, mn)
71 while mn ~= mx do
72 local m = floor((mn+mx)*0.5)
74 if open[m].g > g then
75 mn = m+1
76 else
77 mx = m
78 end
79 end
81 table.insert(open, mn, n)
82 end
83 end
84 end
85 end
86 end
87 last.p = nil
88 return first == last
89 end
91 local function sanity(list, node)
92 local last = nil
93 local contains = false
94 for i, n in ipairs(list) do
95 if not (not last or last >= n.g) then
96 for i, n in ipairs(list) do
97 QuestHelper:TextOut(i..") "..n.g)
98 end
99 QuestHelper:Error("Order "..i.."/"..#list.." ("..n.g..")")
101 assert(not last or last >= n.g)
102 last = n.g
103 if n == node then
104 contains = true
107 if node and not contains then QuestHelper:Error("Missing") end
110 local function Graph_Node_Link(self, next_node, distance)
111 if type(distance) ~= "number" or distance < 0 then QuestHelper:Error("Boom!") end
112 if self ~= next_node then
113 self.n[next_node] = distance
114 table.insert(next_node.r, self)
118 local function Graph_CreateNode(self)
119 local node = QuestHelper:CreateTable("graph_createnode")
120 node.Link = Graph_Node_Link
121 node.n = QuestHelper:CreateTable("graph_createnode.n")
122 node.r = QuestHelper:CreateTable("graph_createnode.r")
123 table.insert(self.nodes, node)
124 return node
127 local function Graph_DestroyNode(self, node)
128 for i = 1,#self.nodes do
129 if self.nodes[i] == node then
130 table.remove(self.nodes, i)
131 QuestHelper:ReleaseTable(node.n)
132 QuestHelper:ReleaseTable(node.r)
133 QuestHelper:ReleaseTable(node)
134 break
139 local function Graph_Reset(self)
140 while #self.nodes > 0 do
141 local node = table.remove(self.nodes)
142 QuestHelper:ReleaseTable(node.n)
143 QuestHelper:ReleaseTable(node.r)
144 QuestHelper:ReleaseTable(node)
148 local function Graph_AddRouteStartNode(self, n, g, end_list)
149 local open = self.open
151 n.p = nil
153 if n.s == 3 then
154 n.s = 4
155 elseif n.s == 0 then
156 n.s = 1
157 else
158 local og = n.g
159 if g < og then
160 local mn, mx = 1, #open
162 while mn ~= mx do
163 local m = floor((mn+mx)*0.5)
164 if open[m].g > og then
165 mn = m+1
166 else
167 mx = m
171 while open[mn] ~= n do
172 mn = mn + 1
175 table.remove(open, mn)
176 else
177 return -- Don't want to insert the node a second time.
181 local mn, mx = 1, #open+1
183 while mn ~= mx do
184 local m = floor((mn+mx)*0.5)
186 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))
187 if open[m].g > g then
188 mn = m+1
189 else
190 mx = m
194 table.insert(open, mn, n)
196 n.g = g
199 local function Graph_DoRouteSearch(self, end_list)
200 local open = self.open
201 local end_count = #end_list
203 while #open > 0 do
204 local current = table.remove(open)
206 if current.s == 3 or current.s == 4 then
207 current.s = 2
208 return current
211 current.s = 2
213 local cd = current.g
215 for n, d in pairs(current.n) do
216 if n.s == 0 or n.s == 3 then
217 -- Haven't visited this node yet.
218 local g = cd+d
219 n.p = current
220 n.g = g
222 n.s = n.s == 3 and 4 or 1
224 local mn, mx = 1, #open+1
226 while mn ~= mx do
227 local m = floor((mn+mx)*0.5)
229 if open[m].g > g then
230 mn = m+1
231 else
232 mx = m
236 table.insert(open, mn, n)
237 elseif n.s == 1 or n.s == 4 then
238 local g = cd+d
239 local og = n.g
240 if g < og then
241 n.p = current
243 local mn, mx = 1, #open
245 while mn ~= mx do
246 local m = floor((mn+mx)*0.5)
247 if open[m].g > og then
248 mn = m+1
249 else
250 mx = m
254 while open[mn] ~= n do
255 mn = mn + 1
258 mx = #open
260 table.remove(open, mn)
262 while mn ~= mx do
263 local m = floor((mn+mx)*0.5)
265 if open[m].g > g then
266 mn = m+1
267 else
268 mx = m
272 n.g = g
273 table.insert(open, mn, n)
280 local function Graph_PrepareSearch(self)
281 local open = self.open
282 for n in pairs(open) do open[n] = nil end
283 for n in pairs(open) do QuestHelper: Assert(nil, "not empty in preparesearch") end
284 for _, n in pairs(self.nodes) do
285 n.s = 0
289 local function Graph_AddStartNode(self, n, g, end_list)
290 local open = self.open
292 n.p = n
294 if n.s == 3 then
295 n.s = 4
296 elseif n.s == 0 then
297 n.s = 1
298 else
299 local og = n.g
300 if g < og then
301 local mn, mx = 1, #open
303 while mn ~= mx do
304 local m = floor((mn+mx)*0.5)
305 if open[m].g > og then
306 mn = m+1
307 else
308 mx = m
312 while open[mn] ~= n do
313 mn = mn + 1
316 table.remove(open, mn)
317 else
318 return -- Don't want to insert the node a second time.
322 local mn, mx = 1, #open+1
324 while mn ~= mx do
325 local m = floor((mn+mx)*0.5)
327 if open[m].g > g then
328 mn = m+1
329 else
330 mx = m
334 table.insert(open, mn, n)
336 n.g = g
339 local function Graph_DoSearch(self, end_list)
340 local open = self.open
341 local end_count = #end_list
343 while #open > 0 do
344 local current = table.remove(open)
346 if current.s == 3 or current.s == 4 then
347 current.s = 2
348 return current
351 current.s = 2
353 local cd = current.g
355 for n, d in pairs(current.n) do
356 if n.s == 0 or n.s == 3 then
357 -- Haven't visited this node yet.
358 local g = cd+d
359 n.g = g
360 n.p = current.p
362 n.s = n.s == 3 and 4 or 1
364 local mn, mx = 1, #open+1
366 while mn ~= mx do
367 local m = floor((mn+mx)*0.5)
369 if open[m].g > g then
370 mn = m+1
371 else
372 mx = m
376 table.insert(open, mn, n)
377 elseif n.s == 1 or n.s == 4 then
378 local g = cd+d
379 local og = n.g
380 if g < og then
381 n.p = current.p
383 local mn, mx = 1, #open
385 while mn ~= mx do
386 local m = floor((mn+mx)*0.5)
387 if open[m].g > og then
388 mn = m+1
389 else
390 mx = m
394 while open[mn] ~= n do
395 mn = mn + 1
398 mx = #open
400 table.remove(open, mn)
402 while mn ~= mx do
403 local m = floor((mn+mx)*0.5)
405 if open[m].g > g then
406 mn = m+1
407 else
408 mx = m
412 n.g = g
413 table.insert(open, mn, n)
420 local removed = {}
422 local function Graph_DoFullSearch(self, end_list)
423 local open = self.open
424 local end_count = #end_list
426 while #open > 0 do
427 local current = table.remove(open)
429 if current.s == 3 or current.s == 4 then
430 if end_count == 1 then
431 for i = 1,#removed do
432 table.insert(end_list, table.remove(removed))
434 return
437 for i = 1,end_count do
438 if end_list[i] == current then
439 table.insert(removed, table.remove(end_list, i))
440 break
444 end_count = end_count - 1
447 current.s = 2
449 local cd = current.g
451 for n, d in pairs(current.n) do
452 if n.s == 0 or n.s == 3 then
453 -- Haven't visited this node yet.
454 local g = cd+d
455 n.g = g
456 n.p = current.p
458 n.s = n.s == 3 and 4 or 1
460 local mn, mx = 1, #open+1
462 while mn ~= mx do
463 local m = floor((mn+mx)*0.5)
465 if open[m].g > g then
466 mn = m+1
467 else
468 mx = m
472 table.insert(open, mn, n)
473 elseif n.s == 1 or n.s == 4 then
474 local g = cd+d
475 local og = n.g
476 if g < og then
477 n.p = current.p
479 local mn, mx = 1, #open
481 while mn ~= mx do
482 local m = floor((mn+mx)*0.5)
483 if open[m].g > og then
484 mn = m+1
485 else
486 mx = m
490 while open[mn] ~= n do
491 mn = mn + 1
494 mx = #open
496 table.remove(open, mn)
498 while mn ~= mx do
499 local m = floor((mn+mx)*0.5)
501 if open[m].g > g then
502 mn = m+1
503 else
504 mx = m
508 n.g = g
509 table.insert(open, mn, n)
515 for i, n in ipairs(end_list) do
516 QuestHelper:TextOut(i..") Failed to reach: "..n.name..", s="..n.s)
519 for i = 1,#removed do
520 table.insert(end_list, table.remove(removed))
523 for i, n in ipairs(end_list) do
524 QuestHelper:TextOut(i..") End node: "..n.name..", s="..n.s)
527 QuestHelper:Error("Boom!")
529 for i = 1,#removed do
530 table.insert(end_list, table.remove(removed))
534 local function propLinks(node)
535 if node.s ~= 1 then
536 node.s = 1
537 for n in pairs(node.n) do
538 propLinks(n)
543 function Graph_SanityCheck(self)
544 for i = 1,#self.nodes do
545 for _, n in ipairs(self.nodes) do
546 n.s = 0
549 propLinks(self.nodes[i])
551 for _, n in ipairs(self.nodes) do
552 if n.s ~= 1 then
553 QuestHelper:TextOut((n.name or "unknown").." isn't reachable from "..(self.nodes[i].name or "unknown"))
559 function QuestHelper:CreateGraph()
560 local graph = self:CreateTable("graph")
561 graph.nodes = self:CreateTable("graph.nodes")
562 graph.end_nodes = self:CreateTable("graph.end_nodes")
563 graph.open = self:CreateTable("graph.open")
565 graph.CreateNode = Graph_CreateNode
566 graph.DestroyNode = Graph_DestroyNode
567 graph.Reset = Graph_Reset
569 graph.PrepareSearch = Graph_PrepareSearch
571 graph.AddRouteStartNode = Graph_AddRouteStartNode
572 graph.DoRouteSearch = Graph_DoRouteSearch
573 graph.Search = Graph_Search
574 graph.SanityCheck = Graph_SanityCheck
576 -- These don't deal with finding paths and instead only care about finding distances.
577 graph.AddStartNode = Graph_AddStartNode
578 graph.DoSearch = Graph_DoSearch
579 graph.DoFullSearch = Graph_DoFullSearch
581 return graph
584 function QuestHelper:ReleaseGraph(graph)
585 graph:Reset()
586 self:ReleaseTable(graph.nodes)
587 self:ReleaseTable(graph.end_nodes)
588 self:ReleaseTable(graph.open)
589 self:ReleaseTable(graph)
592 QuestHelper.world_graph = QuestHelper:CreateGraph()