From 1273a75ceb7a42d2888df9303f27bbd12be8b8fb Mon Sep 17 00:00:00 2001 From: Yvar Date: Wed, 15 May 2013 21:23:40 +1000 Subject: [PATCH 1/3] Wholesale A-star replacement I wish I'd just been able to update the original version, but something kept going wrong. This version creates paths of identical lengths (not necessarily the same paths!) while evaluating, on average, only 35% of the tiles that the original algorithm would. Seems like a decent improvement. Also fixes some grammar in the mulebot file. --- code/defines/procs/AStar.dm | 323 +++++++++++++--------------- code/game/machinery/bots/mulebot.dm | 8 +- 2 files changed, 155 insertions(+), 176 deletions(-) diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm index 0af47ba12c6..04a6180750c 100644 --- a/code/defines/procs/AStar.dm +++ b/code/defines/procs/AStar.dm @@ -1,185 +1,164 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 +priorityqueue + var/pathnode/nodes[] //list of pathnodes, sorted descending by f_score + New() + nodes = new() -/* -A Star pathfinding algorithm -Returns a list of tiles forming a path from A to B, taking dense objects as well as walls, and the orientation of -windows along the route into account. -Use: -your_list = AStar(start location, end location, adjacent turf proc, distance proc) -For the adjacent turf proc i wrote: +priorityqueue/proc/insert(pathnode/n) + if (!nodes.len) + nodes.Add(n) + else + //linear search - slower than binary but guaranteed to work! + var/pos = nodes.len + while (pos > 0 && compare(n,nodes[pos]) > 0) + pos-- + nodes.Insert(pos+1,n) + +priorityqueue/proc/extract_last() + var/pathnode/t = nodes[nodes.len] + nodes.Cut(nodes.len) + return t + +priorityqueue/proc/compare(pathnode/a, pathnode/b) + return (a.f_score - b.f_score) + +pathnode + var/turf/me //turf + var/pathnode/parent //pathnode this turf was discovered from + var/f_score //total path-length of this turf + var/g_score //length of path to this turf + + New(m,p,f,g) + me = m + parent = p + f_score = f + g_score = g + +/******************** +/A* pathfinding algorithm +/This version updated May 2013 by Yvarov +/Returns a list of turfs along the discovered path, in order from start to finish. +/ +/A* is more complex than can be easily explained here, but basically +/it checks tiles, assigning a score f = g + h where +/g = distance from start & h = minimum distance to end, and repeats with +/new tiles starting with the smallest f_score. It's capable of finding +/a path (assuming one exists), without getting stuck in wrong turns or +/unnecessarily checking unpromising tiles. +/ +/The syntax for calling remains roughly the same as the version this replaces, +/minus two arguments (ones that were never given by any of +/the procs that called it anyway). + +Possible procs to use for adjacency (there may be others): /turf/proc/AdjacentTurfs -And for the distance one i wrote: -/turf/proc/Distance -So an example use might be: +/turf/proc/AdjacentTurfsSpace +/turf/proc/CardinalTurfsWithAccess (best for most occasions, four-directional) -src.path_list = AStar(src.loc, target.loc, /turf/proc/AdjacentTurfs, /turf/proc/Distance) +Possible procs to use for adjacency: +/turf/proc/Distance_cardinal (requires four-directional adjacency proc) +/turf/proc/Distance (requires eight-directional adjacency proc) -Note: The path is returned starting at the END node, so i wrote reverselist to reverse it for ease of use. +Arguments: +/turf/start_turf: The turf to start the path from. +/turf/dest_turf: The turf to finish the path on. +adjacent_proc: The proc used to find adjacent tiles. When called, id is provided as an argument. + For the best path to be found, it must only output tiles that could be reached in + one move AND aren't occupied by something else, like a wall. +distance_proc: Formula to use for calculating distance. +maxnodes: Not used. Maximum number of nodes to examine; values for it were never provided. + To avoid an (unlikely) infinite loop, a cutoff occurs at somewhat less than it took + the old algorithm to path across the station. +max_length: If the path is longer, return only this many elements of it (starting from the front). + It can only be shortened after finding the whole path, so doesn't save any time, + but as different values were provided throughout the code it's kept for compatibility. +id: Supplied to the adjacency proc for doors/etc. Default is null. +/var/turf/exclude: Turfs to avoid when finding a path. Default is null. -src.path_list = reverselist(src.pathlist) +Nodes not kept at all from previous version (which were never supplied): +"Mintargetdist: Minimum distance to the target before path returns, could be used to get +near a target, but not right to it - for an AI mob with a gun, for example." +"Minnodedist: Minimum number of nodes to return in the path, could be used to give a path a minimum +length to avoid portals or something i guess?? Not that they're counted right now but w/e." +*********************/ +proc/AStar(turf/start_turf, turf/dest_turf, adjacent_proc, distance_proc, maxnodes, max_length, id=null, var/turf/exclude=null) + set background=1 //don't hog the CPU + var/const/DEPTH_BIAS = 0.5 //preference of discovered paths. decrease with extreme caution and don't set >1! + //var/evaluations = 0 //Total number of tiles examined, including discards and duplicates. Used for debugging + var/lpct = 0 //Loop counter - turfs properly examined. Debugging and infinite loop prevention. + var/pathnode/checked[] = new() //list of pathnodes already examined + var/priorityqueue/pq = new /priorityqueue() //list of pathnodes not yet examined, sorted descending by f_score + var/pathnode/current //current pathnode being examined + var/next_g //calculated g_score of a potential turf + var/next_f //calculated f_score of a potential turf + var/skip //used for internal logic branching, don't touch. + var/pathnode/temp //temporary pathnode used for comparison, don't touch. -Then to start on the path, all you need to do it: -Step_to(src, src.path_list[1]) -src.path_list -= src.path_list[1] or equivilent to remove that node from the list. + //sanity check to avoid a huge waste of CPU time on impossible tasks + for(var/obj/O in dest_turf) + if(O.density && !istype(O, /obj/machinery/door) && !(O.flags & ON_BORDER)) + var/list/path = new //something is blocking the destination + return path -Optional extras to add on (in order): -MaxNodes: The maximum number of nodes the returned path can be (0 = infinite) -Maxnodedepth: The maximum number of nodes to search (default: 30, 0 = infinite) -Mintargetdist: Minimum distance to the target before path returns, could be used to get -near a target, but not right to it - for an AI mob with a gun, for example. -Minnodedist: Minimum number of nodes to return in the path, could be used to give a path a minimum -length to avoid portals or something i guess?? Not that they're counted right now but w/e. -*/ + pq.insert(new /pathnode(start_turf, null, call(start_turf,distance_proc)(dest_turf),0)) //add the starting turf and manhattan distance to the queue! -// Modified to provide ID argument - supplied to 'adjacent' proc, defaults to null -// Used for checking if route exists through a door which can be opened + while (!pq.nodes.len || lpct < 1500) //maximum for DEPTH_BIAS=0.5 is 958 loops to Research Division (originally 2743) + lpct++ + current = pq.extract_last() + checked.Add(current) -// Also added 'exclude' turf to avoid travelling over; defaults to null + if (!current) //no more possibilities, or an unhandled error + //world << "Impossible path" + var/list/path = new //returning 0 will get runtime errors + return path //FAILURE + if (current.me == dest_turf) //reached the destination + var/list/path = new() + path.Add(current.me) + while (current.parent) //recreate path by looping through parents + current = current.parent + path.Add(current.me) + //world << "Path found! ([path.len] steps, [lpct] loops, [evaluations] evaluations)" -PriorityQueue - var/L[] - var/cmp - New(compare) - L = new() - cmp = compare - proc - IsEmpty() - return !L.len - Enqueue(d) - var/i - var/j - L.Add(d) - i = L.len - j = i>>1 - while(i > 1 && call(cmp)(L[j],L[i]) > 0) - L.Swap(i,j) - i = j - j >>= 1 - - Dequeue() - if(!L.len) return 0 - . = L[1] - Remove(1) - - Remove(i) - if(i > L.len) return 0 - L.Swap(i,L.len) - L.Cut(L.len) - if(i < L.len) - _Fix(i) - _Fix(i) - var/child = i + i - var/item = L[i] - while(child <= L.len) - if(child + 1 <= L.len && call(cmp)(L[child],L[child + 1]) > 0) - child++ - if(call(cmp)(item,L[child]) > 0) - L[i] = L[child] - i = child - else - break - child = i + i - L[i] = item - List() - var/ret[] = new() - var/copy = L.Copy() - while(!IsEmpty()) - ret.Add(Dequeue()) - L = copy - return ret - RemoveItem(i) - var/ind = L.Find(i) - if(ind) - Remove(ind) -PathNode - var/datum/source - var/PathNode/prevNode - var/f - var/g - var/h - var/nt // Nodes traversed - New(s,p,pg,ph,pnt) - source = s - prevNode = p - g = pg - h = ph - f = g + h - source.bestF = f - nt = pnt - -datum - var/bestF -proc - PathWeightCompare(PathNode/a, PathNode/b) - return a.f - b.f - - AStar(start,end,adjacent,dist,maxnodes,maxnodedepth = 30,mintargetdist,minnodedist,id=null, var/turf/exclude=null) - -// world << "A*: [start] [end] [adjacent] [dist] [maxnodes] [maxnodedepth] [mintargetdist], [minnodedist] [id]" - var/PriorityQueue/open = new /PriorityQueue(/proc/PathWeightCompare) - var/closed[] = new() - var/path[] - start = get_turf(start) - if(!start) return 0 - - open.Enqueue(new /PathNode(start,null,0,call(start,dist)(end))) - - while(!open.IsEmpty() && !path) - { - var/PathNode/cur = open.Dequeue() - closed.Add(cur.source) - - var/closeenough - if(mintargetdist) - closeenough = call(cur.source,dist)(end) <= mintargetdist - - if(cur.source == end || closeenough) - path = new() - path.Add(cur.source) - while(cur.prevNode) - cur = cur.prevNode - path.Add(cur.source) - break - - var/L[] = call(cur.source,adjacent)(id) - if(minnodedist && maxnodedepth) - if(call(cur.source,minnodedist)(end) + cur.nt >= maxnodedepth) - continue - else if(maxnodedepth) - if(cur.nt >= maxnodedepth) - continue - - for(var/datum/d in L) - if(d == exclude) - continue - var/ng = cur.g + call(cur.source,dist)(d) - if(d.bestF) - if(ng + call(d,dist)(end) < d.bestF) - for(var/i = 1; i <= open.L.len; i++) - var/PathNode/n = open.L[i] - if(n.source == d) - open.Remove(i) - break - else - continue - - open.Enqueue(new /PathNode(d,cur,ng,call(d,dist)(end),cur.nt+1)) - if(maxnodes && open.L.len > maxnodes) - open.L.Cut(open.L.len) - } - - var/PathNode/temp - while(!open.IsEmpty()) - temp = open.Dequeue() - temp.source.bestF = 0 - while(closed.len) - temp = closed[closed.len] - temp.bestF = 0 - closed.Cut(closed.len) - - if(path) - for(var/i = 1; i <= path.len/2; i++) + for(var/i = 1; i <= path.len/2; i++) //reverse path path.Swap(i,path.len-i+1) - return path + if (path.len > max_length) //Truncate list at max_length (if necessary) + path.Cut(max_length,0) + return path //SUCCESS + + next_g = current.g_score + DEPTH_BIAS + var/adjacent = call(current.me,adjacent_proc)(id) + for (var/next_turf in adjacent) + //evaluations++ + if (next_turf == exclude) + continue + skip = 0 + next_f = next_g + call(next_turf,distance_proc)(dest_turf) + var/i = 0 + while ( i < checked.len && !skip) + i++ + temp = checked[i] + if (temp.me == next_turf) //if this turf has already been checked + skip = 1 + if (temp.f_score > next_f) //BUT the current path to it is better + checked[i] = new /pathnode(next_turf, current, next_f, next_g) + //then update it + if (!skip) //if it hasn't been checked + i = 0 + while (i < pq.nodes.len && !skip) + i++ + temp = pq.nodes[i] + if (temp.me == next_turf) //if this turf was already on the openlist + if (temp.f_score > next_f) //BUT the current path to it is better + pq.nodes.Cut(i,i+1) //then remove it + skip = 2 + else + skip = 1 //otherwise leave it and don't add this version + if (!(skip % 2)) + pq.insert(new /pathnode(next_turf, current, next_f, next_g)) + + + //world << "Didn't find a path within [evaluations] evaluations and [lpct] loops." + var/list/path = new //prevents runtime errors + return path //FAILURE + //This return should only be reached if the path is too long, and the while loop gives up. diff --git a/code/game/machinery/bots/mulebot.dm b/code/game/machinery/bots/mulebot.dm index 8f56a805db5..cecbf903b3e 100644 --- a/code/game/machinery/bots/mulebot.dm +++ b/code/game/machinery/bots/mulebot.dm @@ -201,7 +201,7 @@ var/global/mulebot_count = 0 if(5,6) dat += "Calculating navigation path" if(7) - dat += "Unable to locate destination" + dat += "Unable to reach destination" dat += "" dat += "Current Load: [load ? load.name : "none"]
" @@ -225,7 +225,7 @@ var/global/mulebot_count = 0 if(load) dat += "Unload Now
" - dat += "
The maintenance hatch is closed
" + dat += "
The maintenance hatch is closed.
" else if(!ai) @@ -590,7 +590,7 @@ var/global/mulebot_count = 0 blockcount++ mode = 4 if(blockcount == 3) - src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") + src.visible_message("[src] makes an annoyed buzzing sound.", "You hear an electronic buzzing sound.") playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) if(blockcount > 5) // attempt 5 times before recomputing @@ -608,7 +608,7 @@ var/global/mulebot_count = 0 return return else - src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") + src.visible_message("[src] makes an annoyed buzzing sound.", "You hear an electronic buzzing sound.") playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) //world << "Bad turf." mode = 5 From 3443ee23190b3cd9f22ec0a3035f99bfc15b0681 Mon Sep 17 00:00:00 2001 From: Yvar Date: Thu, 16 May 2013 20:40:01 +1000 Subject: [PATCH 2/3] A-star fixes Will obey the maxnodes parameter if it's provided (although perhaps not in the way that it was originally meant?) The only one that uses it is disease, and it wasn't an issue there, but at least in future it won't be a problem. Also slightly increased the maximum loop count from 1500 to 1800, on the off-chance that the previous value was pessimistically low. In the initial station this doesn't go past 1000, but if bots understand doors their paths might get squiggly. --- code/defines/procs/AStar.dm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm index 04a6180750c..b18366dfed4 100644 --- a/code/defines/procs/AStar.dm +++ b/code/defines/procs/AStar.dm @@ -65,9 +65,9 @@ adjacent_proc: The proc used to find adjacent tiles. When called, id is provide For the best path to be found, it must only output tiles that could be reached in one move AND aren't occupied by something else, like a wall. distance_proc: Formula to use for calculating distance. -maxnodes: Not used. Maximum number of nodes to examine; values for it were never provided. - To avoid an (unlikely) infinite loop, a cutoff occurs at somewhat less than it took - the old algorithm to path across the station. +maxnodes: Maximum _depth_ of nodes to examine; currently used only by disease. + Not equal to the number of nodes to examine (lpct, internal) or the maximum path + length returned (max_length below). max_length: If the path is longer, return only this many elements of it (starting from the front). It can only be shortened after finding the whole path, so doesn't save any time, but as different values were provided throughout the code it's kept for compatibility. @@ -95,17 +95,21 @@ proc/AStar(turf/start_turf, turf/dest_turf, adjacent_proc, distance_proc, maxnod //sanity check to avoid a huge waste of CPU time on impossible tasks for(var/obj/O in dest_turf) - if(O.density && !istype(O, /obj/machinery/door) && !(O.flags & ON_BORDER)) + if (O.density && (!istype(O, /obj/machinery/door) || !(O.flags & ON_BORDER))) var/list/path = new //something is blocking the destination return path pq.insert(new /pathnode(start_turf, null, call(start_turf,distance_proc)(dest_turf),0)) //add the starting turf and manhattan distance to the queue! - while (!pq.nodes.len || lpct < 1500) //maximum for DEPTH_BIAS=0.5 is 958 loops to Research Division (originally 2743) - lpct++ + for (lpct = 0; lpct < 1800; lpct++) //maximum for DEPTH_BIAS=0.5 is 958 loops to Research Division (originally 2743) + if (!pq.nodes.len) break + current = pq.extract_last() checked.Add(current) + next_g = current.g_score + DEPTH_BIAS + //if (maxnodes && next_g > (maxnodes*DEPTH_BIAS)) continue + if (!current) //no more possibilities, or an unhandled error //world << "Impossible path" var/list/path = new //returning 0 will get runtime errors @@ -126,7 +130,6 @@ proc/AStar(turf/start_turf, turf/dest_turf, adjacent_proc, distance_proc, maxnod path.Cut(max_length,0) return path //SUCCESS - next_g = current.g_score + DEPTH_BIAS var/adjacent = call(current.me,adjacent_proc)(id) for (var/next_turf in adjacent) //evaluations++ From e6c8e67c3f00435476c0befafe89c0bc0154fa60 Mon Sep 17 00:00:00 2001 From: carnie Date: Sun, 19 May 2013 09:36:52 +0100 Subject: [PATCH 3/3] Minor modifications to Yvar's A* fixes: -Removed the priorityqueue datum. It is now a list() -priorityqueue/proc/insert is now a helper /proc/sorted_insert() as it may be helpful in maintaining pre-sorted lists or insertion sorts. -Replaced priorityqueue/proc/extract_last() calls with calls to pop(), which already existed. -Removed last few references to "bestF" (from old awful A) in lighting and MC -Replaced a section were it'd append to the end of the returned path list and then reverse the list. Now it inserts at the start of the list. --- code/__HELPERS/lists.dm | 16 +++-- code/controllers/lighting_controller.dm | 2 +- code/controllers/master_controller.dm | 2 +- code/defines/procs/AStar.dm | 93 +++++++++---------------- 4 files changed, 45 insertions(+), 68 deletions(-) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 70cf55ecbf6..a08e6827d48 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -134,12 +134,16 @@ proc/listclearnulls(list/list) L.Cut(picked,picked+1) //Cut is far more efficient that Remove() //Returns the top(last) element from the list and removes it from the list (typical stack function) -/proc/pop(list/listfrom) - if (listfrom.len > 0) - var/picked = listfrom[listfrom.len] - listfrom.len-- - return picked - return null +/proc/pop(list/L) + if(L.len) + . = L[L.len] + L.len-- + +/proc/sorted_insert(list/L, thing, comparator) + var/pos = L.len + while(pos > 0 && call(comparator)(thing, L[pos]) > 0) + pos-- + L.Insert(pos+1, thing) /* * Sorting diff --git a/code/controllers/lighting_controller.dm b/code/controllers/lighting_controller.dm index 503fc10c93c..3428503e2d6 100644 --- a/code/controllers/lighting_controller.dm +++ b/code/controllers/lighting_controller.dm @@ -117,7 +117,7 @@ datum/controller/lighting/proc/Recover() var/msg = "## DEBUG: [time2text(world.timeofday)] lighting_controller restarted. Reports:\n" for(var/varname in lighting_controller.vars) switch(varname) - if("tag","bestF","type","parent_type","vars") continue + if("tag","type","parent_type","vars") continue else var/varval1 = lighting_controller.vars[varname] var/varval2 = vars[varname] diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index fb369946c34..1481225d5af 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -287,7 +287,7 @@ datum/controller/game_controller/proc/Recover() //Mostly a placeholder for now. var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n" for(var/varname in master_controller.vars) switch(varname) - if("tag","bestF","type","parent_type","vars") continue + if("tag","type","parent_type","vars") continue else var/varval = master_controller.vars[varname] if(istype(varval,/datum)) diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm index b18366dfed4..3c97935a29d 100644 --- a/code/defines/procs/AStar.dm +++ b/code/defines/procs/AStar.dm @@ -1,25 +1,5 @@ -priorityqueue - var/pathnode/nodes[] //list of pathnodes, sorted descending by f_score - New() - nodes = new() - -priorityqueue/proc/insert(pathnode/n) - if (!nodes.len) - nodes.Add(n) - else - //linear search - slower than binary but guaranteed to work! - var/pos = nodes.len - while (pos > 0 && compare(n,nodes[pos]) > 0) - pos-- - nodes.Insert(pos+1,n) - -priorityqueue/proc/extract_last() - var/pathnode/t = nodes[nodes.len] - nodes.Cut(nodes.len) - return t - -priorityqueue/proc/compare(pathnode/a, pathnode/b) - return (a.f_score - b.f_score) +/proc/cmp_pathnodes(pathnode/A, pathnode/B) + return (A.f_score - B.f_score) pathnode var/turf/me //turf @@ -81,87 +61,80 @@ near a target, but not right to it - for an AI mob with a gun, for example." length to avoid portals or something i guess?? Not that they're counted right now but w/e." *********************/ proc/AStar(turf/start_turf, turf/dest_turf, adjacent_proc, distance_proc, maxnodes, max_length, id=null, var/turf/exclude=null) - set background=1 //don't hog the CPU + set background=1 //allows proc to sleep should it take more than a single frame to complete + var/const/DEPTH_BIAS = 0.5 //preference of discovered paths. decrease with extreme caution and don't set >1! //var/evaluations = 0 //Total number of tiles examined, including discards and duplicates. Used for debugging var/lpct = 0 //Loop counter - turfs properly examined. Debugging and infinite loop prevention. - var/pathnode/checked[] = new() //list of pathnodes already examined - var/priorityqueue/pq = new /priorityqueue() //list of pathnodes not yet examined, sorted descending by f_score + var/list/checked = list() //list of pathnodes already examined + var/list/priorityqueue = list() //list of pathnodes not yet examined, sorted descending by f_score var/pathnode/current //current pathnode being examined var/next_g //calculated g_score of a potential turf var/next_f //calculated f_score of a potential turf var/skip //used for internal logic branching, don't touch. var/pathnode/temp //temporary pathnode used for comparison, don't touch. + + var/list/path = list() //return value //sanity check to avoid a huge waste of CPU time on impossible tasks for(var/obj/O in dest_turf) - if (O.density && (!istype(O, /obj/machinery/door) || !(O.flags & ON_BORDER))) - var/list/path = new //something is blocking the destination + if(O.density && (!istype(O, /obj/machinery/door) || !(O.flags & ON_BORDER))) //something is blocking the destination return path + + priorityqueue += new /pathnode(start_turf, null, call(start_turf,distance_proc)(dest_turf), 0)//add the starting turf and manhattan distance to the queue! - pq.insert(new /pathnode(start_turf, null, call(start_turf,distance_proc)(dest_turf),0)) //add the starting turf and manhattan distance to the queue! + for(lpct=0, lpct<1800, lpct++) //maximum for DEPTH_BIAS=0.5 is 958 loops to Research Division (originally 2743) + if(!priorityqueue.len) break - for (lpct = 0; lpct < 1800; lpct++) //maximum for DEPTH_BIAS=0.5 is 958 loops to Research Division (originally 2743) - if (!pq.nodes.len) break - - current = pq.extract_last() + current = pop(priorityqueue) checked.Add(current) next_g = current.g_score + DEPTH_BIAS - //if (maxnodes && next_g > (maxnodes*DEPTH_BIAS)) continue + //if(maxnodes && next_g > (maxnodes*DEPTH_BIAS)) continue - if (!current) //no more possibilities, or an unhandled error + if(!current) //no more possibilities, or an unhandled error //world << "Impossible path" - var/list/path = new //returning 0 will get runtime errors return path //FAILURE - if (current.me == dest_turf) //reached the destination - var/list/path = new() - path.Add(current.me) - while (current.parent) //recreate path by looping through parents + if(current.me == dest_turf) //reached the destination + while(current) //recreate path by looping through parents + path.Insert(1, current.me) current = current.parent - path.Add(current.me) //world << "Path found! ([path.len] steps, [lpct] loops, [evaluations] evaluations)" - for(var/i = 1; i <= path.len/2; i++) //reverse path - path.Swap(i,path.len-i+1) - - if (path.len > max_length) //Truncate list at max_length (if necessary) - path.Cut(max_length,0) + path.len = min(path.len, max_length-1) return path //SUCCESS var/adjacent = call(current.me,adjacent_proc)(id) - for (var/next_turf in adjacent) + for(var/next_turf in adjacent) //evaluations++ - if (next_turf == exclude) + if(next_turf == exclude) continue skip = 0 next_f = next_g + call(next_turf,distance_proc)(dest_turf) var/i = 0 - while ( i < checked.len && !skip) + while(i < checked.len && !skip) i++ temp = checked[i] - if (temp.me == next_turf) //if this turf has already been checked + if(temp.me == next_turf) //if this turf has already been checked skip = 1 - if (temp.f_score > next_f) //BUT the current path to it is better + if(temp.f_score > next_f) //BUT the current path to it is better checked[i] = new /pathnode(next_turf, current, next_f, next_g) //then update it - if (!skip) //if it hasn't been checked + if(!skip) //if it hasn't been checked i = 0 - while (i < pq.nodes.len && !skip) + while(i < priorityqueue.len && !skip) i++ - temp = pq.nodes[i] - if (temp.me == next_turf) //if this turf was already on the openlist - if (temp.f_score > next_f) //BUT the current path to it is better - pq.nodes.Cut(i,i+1) //then remove it + temp = priorityqueue[i] + if(temp.me == next_turf) //if this turf was already on the openlist + if(temp.f_score > next_f) //BUT the current path to it is better + priorityqueue.Cut(i,i+1) //then remove it skip = 2 else skip = 1 //otherwise leave it and don't add this version - if (!(skip % 2)) - pq.insert(new /pathnode(next_turf, current, next_f, next_g)) - + if(!(skip % 2)) + sorted_insert(priorityqueue, new /pathnode(next_turf, current, next_f, next_g), /proc/cmp_pathnodes) //world << "Didn't find a path within [evaluations] evaluations and [lpct] loops." - var/list/path = new //prevents runtime errors return path //FAILURE //This return should only be reached if the path is too long, and the while loop gives up.