mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 23:58:07 +01:00
Merge pull request #12076 from Menshin/letsbeaheapstar
Some datastructures and A* tweaks
This commit is contained in:
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user