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.
This commit is contained in:
carnie
2013-05-19 09:54:39 +01:00
parent 3443ee2319
commit e6c8e67c3f
4 changed files with 45 additions and 68 deletions
+10 -6
View File
@@ -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
+1 -1
View File
@@ -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]
+1 -1
View File
@@ -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))
+33 -60
View File
@@ -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.