Merge pull request #12076 from Menshin/letsbeaheapstar

Some datastructures and A* tweaks
This commit is contained in:
Razharas
2015-10-03 18:15:23 +03:00
10 changed files with 172 additions and 111 deletions
+75
View File
@@ -0,0 +1,75 @@
//////////////////////
//Heap object
//////////////////////
/Heap
var/list/L
var/cmp
/Heap/New(compare)
L = new()
cmp = compare
/Heap/proc/IsEmpty()
return !L.len
//Insert and place at its position a new node in the heap
/Heap/proc/Insert(atom/A)
L.Add(A)
Swim(L.len)
//removes and returns the first element of the heap
//(i.e the max or the min dependant on the comparison function)
/Heap/proc/Pop()
if(!L.len)
return 0
. = L[1]
L[1] = L[L.len]
L.Cut(L.len)
Sink(1)
//Get a node up to its right position in the heap
/Heap/proc/Swim(var/index)
var/parent = round(index * 0.5)
while(parent > 0 && (call(cmp)(L[index],L[parent]) > 0))
L.Swap(index,parent)
index = parent
parent = round(index * 0.5)
//Get a node down to its right position in the heap
/Heap/proc/Sink(var/index)
var/g_child = GetGreaterChild(index)
while(g_child > 0 && (call(cmp)(L[index],L[g_child]) < 0))
L.Swap(index,g_child)
index = g_child
g_child = GetGreaterChild(index)
//Returns the greater (relative to the comparison proc) of a node children
//or 0 if there's no child
/Heap/proc/GetGreaterChild(var/index)
if(index * 2 > L.len)
return 0
if(index * 2 + 1 > L.len)
return index * 2
if(call(cmp)(L[index * 2],L[index * 2 + 1]) < 0)
return index * 2 + 1
else
return index * 2
//Replaces a given node so it verify the heap condition
/Heap/proc/ReSort(atom/A)
var/index = L.Find(A)
Swim(index)
Sink(index)
/Heap/proc/List()
. = L.Copy()
+29 -11
View File
@@ -15,23 +15,42 @@
/PriorityQueue/proc/IsEmpty()
return !L.len
//return the index the element should be in the priority queue using dichotomic search
/PriorityQueue/proc/FindElementIndex(atom/A)
var/i = 1
var/j = L.len
var/mid
while(i < j)
mid = round((i+j)/2)
if(call(cmp)(L[mid],A) < 0)
i = mid + 1
else
j = mid
if(i == 1 || i == L.len) //edge cases
return (call(cmp)(L[i],A) > 0) ? i : i+1
else
return i
//add an element in the list,
//immediatly ordering it to its position using Insertion sort
//immediatly ordering it to its position using dichotomic search
/PriorityQueue/proc/Enqueue(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--
if(!L.len)
L.Add(A)
return
L.Insert(FindElementIndex(A),A)
//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(atom/A)
@@ -39,12 +58,11 @@
//returns a copy of the elements list
/PriorityQueue/proc/List()
var/list/ret = L.Copy()
return ret
. = L.Copy()
//return the position of an element or 0 if not found
/PriorityQueue/proc/Seek(atom/A)
return L.Find(A)
. = L.Find(A)
//return the element at the i_th position
/PriorityQueue/proc/Get(i)
+1 -1
View File
@@ -122,7 +122,7 @@ var/list/diseases = typesof(/datum/disease) - /datum/disease
if(isturf(source.loc))
for(var/mob/living/carbon/C in oview(spread_range, source))
if(isturf(C.loc))
if(AStar(source.loc, C.loc, null, /turf/proc/Distance, spread_range))
if(AStar(source.loc, C.loc, null, /turf/proc/Distance, spread_range, adjacent = (spread_flags & AIRBORNE) ? /turf/proc/reachableAdjacentAtmosTurfs : /turf/proc/reachableAdjacentTurfs))
C.ContractDisease(src)
+1 -1
View File
@@ -10,7 +10,7 @@
viable_mobtypes = list(/mob/living/carbon/human)
var/datum/dna/original_dna = null
var/transformed = 0
desc = "This disease transplants the genetic code of the intial vector into new hosts."
desc = "This disease transplants the genetic code of the initial vector into new hosts."
severity = MEDIUM
@@ -38,10 +38,8 @@
return
if(!isturf(user.loc))
return
if(get_dist(user, target) > (user.mind.changeling.sting_range))
return //sanity check as AStar is still throwing insane stunts
if(!AStar(user.loc, target.loc, null, /turf/proc/Distance, user.mind.changeling.sting_range))
return //hope this ancient magic still works
if(!AStar(user.loc, target.loc, null, /turf/proc/Distance, user.mind.changeling.sting_range, simulated_only = 0))
return
if(target.mind && target.mind.changeling)
sting_feedback(user,target)
take_chemical_cost(user.mind.changeling)
+1 -1
View File
@@ -166,7 +166,7 @@ text("<A href='?src=\ref[src];power=1'>[on ? "On" : "Off"]</A>"))
if(target)
if(!path || path.len == 0) //No path, need a new one
//Try to produce a path to the target, and ignore airlocks to which it has access.
path = get_path_to(loc, target.loc, src, /turf/proc/Distance, 0, 30, id=botcard)
path = get_path_to(loc, target.loc, src, /turf/proc/Distance_cardinal, 0, 30, id=botcard)
if (!bot_move(target))
add_to_ignore(target)
target = null
+2 -2
View File
@@ -260,9 +260,9 @@
if(path.len == 0)
if(!istype(target, /turf/))
var/turf/TL = get_turf(target)
path = get_path_to(loc, TL, src, /turf/proc/Distance, 0, 30, id=botcard)
path = get_path_to(loc, TL, src, /turf/proc/Distance_cardinal, 0, 30, id=botcard, simulated_only = 0)
else
path = get_path_to(loc, target, src, /turf/proc/Distance, 0, 30, id=botcard)
path = get_path_to(loc, target, src, /turf/proc/Distance_cardinal, 0, 30, id=botcard, simulated_only = 0)
if(!bot_move(target))
add_to_ignore(target)
@@ -619,7 +619,7 @@
if((C.l_hand && C.l_hand.w_class <= 2) || (C.r_hand && C.r_hand.w_class <= 2))
item = C
if(item)
if(!AStar(loc, get_turf(item), src, /turf/proc/Distance))
if(!AStar(loc, get_turf(item), src, /turf/proc/Distance_cardinal))
item = null
continue
return item
+59 -90
View File
@@ -3,49 +3,27 @@ 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.
your_list = AStar(start location, end location, moving atom, distance proc, max nodes, maximum node depth, minimum distance to target, adjacent proc, atom id, turfs to exclude, check only simulated)
Optional extras to add on (in order):
Distance proc : the distance used in every A* calculation (length of path and heuristic)
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.
Adjacent proc : returns the turfs to consider around the actually processed node
Simulated only : whether to consider unsimulated turfs or not (used by some Adjacent proc)
Also added 'exclude' turf to avoid travelling over; defaults to null
Actual Adjacent procs :
/turf/proc/reachableAdjacentTurfs : returns reachable turfs in cardinal directions (uses simulated_only)
/turf/proc/reachableAdjacentAtmosTurfs : returns turfs in cardinal directions reachable via atmos
*/
// 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
//////////////////////
//PathNode object
//////////////////////
@@ -79,27 +57,27 @@ length to avoid portals or something i guess?? Not that they're counted right no
/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, 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
//reversed so that the Heap is a MinHeap rather than a MaxHeap
/proc/HeapPathWeightCompare(PathNode/a, PathNode/b)
return b.f - a.f
//wrapper that returns an empty list if A* failed to find a path
/proc/get_path_to(start, end, atom, dist, maxnodes, maxnodedepth = 30, mintargetdist, minnodedist, id=null, turf/exclude=null)
var/list/path = AStar(start, end, atom, dist, maxnodes, maxnodedepth, mintargetdist, minnodedist,id, exclude)
/proc/get_path_to(start, end, atom, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1)
var/list/path = AStar(start, end, atom, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
if(!path)
path = list()
return path
//the actual algorithm
/proc/AStar(start, end, atom, dist, maxnodes, maxnodedepth = 30, mintargetdist, minnodedist, id=null, turf/exclude=null)
var/PriorityQueue/open = new /PriorityQueue(/proc/PathWeightCompare) //the open list, ordered using the PathWeightCompare proc, from lower f to higher
/proc/AStar(start, end, atom, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1)
if(maxnodes)
//if start turf is farther than maxnodes from end turf, no need to do anything
if(call(start, dist)(end) > maxnodes)
return 0
maxnodedepth = maxnodes //no need to consider path longer than maxnodes
var/Heap/open = new /Heap(/proc/HeapPathWeightCompare) //the open list
var/list/closed = new() //the closed list
var/list/path = null //the returned path, if any
var/PathNode/cur //current processed turf
@@ -110,13 +88,13 @@ length to avoid portals or something i guess?? Not that they're counted right no
return 0
//initialization
open.Enqueue(new /PathNode(start,null,0,call(start,dist)(end),0))
open.Insert(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
cur = open.Pop() //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
@@ -132,33 +110,28 @@ length to avoid portals or something i guess?? Not that they're counted right no
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)
var/list/L = cur.source.reachableAdjacentTurfs(atom, id)
var/list/L = call(cur.source,adjacent)(atom,id, simulated_only)
for(var/turf/T in L)
if(T == exclude)
if(T == exclude || T in closed)
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))
open.Insert(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()
T.PNode.nt = cur.nt + 1
open.ReSort(T.PNode)//reorder the changed element in the list
}
@@ -169,10 +142,6 @@ length to avoid portals or something i guess?? Not that they're counted right no
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++)
@@ -180,33 +149,33 @@ length to avoid portals or something i guess?? Not that they're counted right no
return path
/turf/proc/reachableAdjacentTurfs(atom, ID)
//Returns adjacent turfs in cardinal directions that are reachable
//simulated_only controls whether only simulated turfs are considered or not
/turf/proc/reachableAdjacentTurfs(atom, ID, simulated_only)
var/list/L = new()
var/turf/simulated/T
if(ID)
for(var/dir in cardinal)
T = get_step(src,dir)
if(!istype(T) || T.density)
continue
if(!LinkBlockedWithAccess(T, ID))
L.Add(T)
else
for(var/dir in cardinal)
if(dir & atmos_adjacent_turfs)
T = get_step(src,dir)
if(!istype(T))
continue
if(!LinkBlocked(atom, T))
L.Add(T)
for(var/dir in cardinal)
T = get_step(src,dir)
if(simulated_only && !istype(T))
continue
if(!T.density && !LinkBlockedWithAccess(T, ID))
L.Add(T)
return L
/turf/proc/LinkBlocked(atom, turf/T)
if(istype(atom, /atom/movable))
for(var/obj/O in T)
if(!O.CanPass(atom, T, 1))
return 1
return 0
return 0
//Returns adjacent turfs in cardinal directions that are reachable via atmos
/turf/proc/reachableAdjacentAtmosTurfs()
var/list/L = new()
var/turf/simulated/T
for(var/dir in cardinal)
if(dir & atmos_adjacent_turfs)
T = get_step(src,dir)
if(!istype(T))
continue
if(CanAtmosPass(T))
L.Add(T)
return L
/turf/proc/LinkBlockedWithAccess(turf/T, obj/item/weapon/card/id/ID)
var/adir = get_dir(src, T)
@@ -231,4 +200,4 @@ length to avoid portals or something i guess?? Not that they're counted right no
for(var/obj/machinery/door/D in T)
if(!D.CanAStarPass(ID, dir))
return 1
return 0
return 0
+1
View File
@@ -17,6 +17,7 @@
#include "code\_compile_options.dm"
#include "code\hub.dm"
#include "code\world.dm"
#include "code\__DATASTRUCTURES\heap.dm"
#include "code\__DATASTRUCTURES\linked_lists.dm"
#include "code\__DATASTRUCTURES\priority_queue.dm"
#include "code\__DATASTRUCTURES\stacks.dm"