/* 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: /turf/proc/AdjacentTurfs And for the distance one i wrote: /turf/proc/Distance So an example use might be: src.path_list = AStar(src.loc, target.loc, /turf/proc/AdjacentTurfs, /turf/proc/Distance) 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. 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. */ // 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 // Also added 'exclude' turf to avoid travelling over; defaults to null //Currently, there's four main ways to call AStar // // 1) adjacent = "/turf/proc/AdjacentTurfsWithAccess" and distance = "/turf/proc/Distance" // Seeks a path moving in all directions (including diagonal) and checking for the correct id to get through doors // // 2) adjacent = "/turf/proc/CardinalTurfsWithAccess" and distance = "/turf/proc/Distance_cardinal" // Seeks a path moving only in cardinal directions and checking if for the correct id to get through doors // Used by most bots, including Beepsky // // 3) adjacent = "/turf/proc/AdjacentTurfs" and distance = "/turf/proc/Distance" // Same as 1), but don't check for ID. Can get only get through open doors // // 4) adjacent = "/turf/proc/AdjacentTurfsSpace" and distance = "/turf/proc/Distance" // Same as 1), but check all turf, including unsimulated ////////////////////// //PriorityQueue object ////////////////////// //an ordered list, using the cmp proc to weight the list elements /PriorityQueue var/list/L //the actual queue var/cmp //the weight function used to order the queue /PriorityQueue/New(compare) L = new() cmp = compare /PriorityQueue/proc/IsEmpty() return !L.len //add an element in the list, //immediatly ordering it to its position using Insertion sort /PriorityQueue/proc/Enqueue(var/atom/A) var/i L.Add(A) i = L.len -1 while(i > 0 && call(cmp)(L[i],A) >= 0) //place the element at it's right position using the compare proc L.Swap(i,i+1) //last inserted element being first in case of ties (optimization) i-- //removes and returns the first element in the queue /PriorityQueue/proc/Dequeue() if(!L.len) return 0 . = L[1] Remove(.) return . //removes an element /PriorityQueue/proc/Remove(var/atom/A) return L.Remove(A) //returns a copy of the elements list /PriorityQueue/proc/List() var/list/ret = L.Copy() return ret //return the position of an element or 0 if not found /PriorityQueue/proc/Seek(var/atom/A) return L.Find(A) //return the element at the i_th position /PriorityQueue/proc/Get(var/i) if(i > L.len || i < 1) return 0 return L[i] //replace the passed element at it's right position using the cmp proc /PriorityQueue/proc/ReSort(var/atom/A) var/i = Seek(A) if(i == 0) return while(i < L.len && call(cmp)(L[i],L[i+1]) > 0) L.Swap(i,i+1) i++ while(i > 1 && call(cmp)(L[i],L[i-1]) <= 0) //last inserted element being first in case of ties (optimization) L.Swap(i,i-1) i-- ////////////////////// //PathNode object ////////////////////// //A* nodes variables /PathNode var/turf/source //turf associated with the PathNode var/PathNode/prevNode //link to the parent PathNode var/f //A* Node weight (f = g + h) var/g //A* movement cost variable var/h //A* heuristic variable var/nt //count the number of Nodes traversed /PathNode/New(s,p,pg,ph,pnt) source = s prevNode = p g = pg h = ph f = g + h source.PNode = src nt = pnt /PathNode/proc/calc_f() f = g + h ////////////////////// //A* procs ////////////////////// //the weighting function, used in the A* algorithm proc/PathWeightCompare(PathNode/a, PathNode/b) return a.f - b.f //search if there's a PathNode that points to turf T in the Priority Queue proc/SeekTurf(var/PriorityQueue/Queue, var/turf/T) var/i = 1 var/PathNode/PN while(i < Queue.L.len + 1) PN = Queue.L[i] if(PN.source == T) return i i++ return 0 //the actual algorithm proc/AStar(start,end,adjacent,dist,maxnodes,maxnodedepth = 30,mintargetdist,minnodedist,id=null, var/turf/exclude=null) var/PriorityQueue/open = new /PriorityQueue(/proc/PathWeightCompare) //the open list, ordered using the PathWeightCompare proc, from lower f to higher var/list/closed = new() //the closed list var/list/path = null //the returned path, if any var/PathNode/cur //current processed turf //sanitation start = get_turf(start) if(!start) return 0 //initialization open.Enqueue(new /PathNode(start,null,0,call(start,dist)(end),0)) //then run the main loop while(!open.IsEmpty() && !path) { //get the lower f node on the open list cur = open.Dequeue() //get the lower f turf in the open list closed.Add(cur.source) //and tell we've processed it //if we only want to get near the target, check if we're close enough var/closeenough if(mintargetdist) closeenough = call(cur.source,dist)(end) <= mintargetdist //if too many steps, abandon that path if(maxnodedepth && (cur.nt > maxnodedepth)) continue //found the target turf (or close enough), let's create the path to it if(cur.source == end || closeenough) path = new() path.Add(cur.source) while(cur.prevNode) cur = cur.prevNode path.Add(cur.source) break //IMPLEMENTATION TO FINISH //do we really need this minnodedist ??? /*if(minnodedist && maxnodedepth) if(call(cur.source,minnodedist)(end) + cur.nt >= maxnodedepth) continue */ //get adjacents turfs using the adjacent proc, checking for access with id var/list/L = call(cur.source,adjacent)(id,closed) for(var/turf/T in L) if(T == exclude) continue var/newg = cur.g + call(cur.source,dist)(T) if(!T.PNode) //is not already in open list, so add it open.Enqueue(new /PathNode(T,cur,newg,call(T,dist)(end),cur.nt+1)) else //is already in open list, check if it's a better way from the current turf if(newg < T.PNode.g) T.PNode.prevNode = cur T.PNode.g = newg T.PNode.calc_f() open.ReSort(T.PNode)//reorder the changed element in the list } //cleaning after us for(var/PathNode/PN in open.L) PN.source.PNode = null for(var/turf/T in closed) T.PNode = null //if the path is longer than maxnodes, then don't return it if(path && maxnodes && path.len > (maxnodes + 1)) return 0 //reverse the path to get it from start to finish if(path) for(var/i = 1; i <= path.len/2; i++) path.Swap(i,path.len-i+1) return path //wrapper that returns an empty list if A* failed to find a path /proc/get_path_to(var/start,var/end,var/adjacent,var/dist,var/maxnodes,var/maxnodedepth = 30,var/mintargetdist,var/minnodedist,id=null, var/turf/exclude=null) var/list/path = AStar(start, end, adjacent, dist, maxnodes, maxnodedepth, mintargetdist, minnodedist,id, exclude) if(!path) path = list() return path