Advanced A* Pathfinding utilizing banks
---------------------------------------
Programm: B3D and BP
State: intermediate and advanced users
This Tutorial does not explain A* itself!
For an excelent A* Pathfinding Tutorial check http://www.policyalmanac.org/games/aStarTutorial.htm
You should fully understand A* before continuing this tutorial.
This tutorial explains how to avoid arrays and types while pathfinding.
Why not Arrays?
- arrays are not dynamic and consume lots of memory
- arrays cannot be created on runtime
- arrays are always global
Why not types?
- since types cannot be nested into eachother its hard to parallel process pathfindings. This would take some managment and its hard to deal with.
- types are always global
so my choice is to entirely use banks for pathfinding
Why banks?
- there fast!
- they are small in memory
- they can be nested (will cover this later)
- they are not global
- they can even be stored easily or send through networks
Sorry, but i havent any webspace so i cant include the media.
First you should draw a small image (about 32x32) the background should be black and then paint some walls with pure white(255,255,255).
Save it as Bitmap or PNG. We will call the file "map.png" in the following code
Now we will initialise the graphics
We need some globals
Because accessing images with ReadPixel is much slower than reading in memorybanks we will convert our "map.png" to a bank with this function
This one is quit simple. We create a bank thats imagewidth x imageheight and store the bluevalue row by row, column by column in the bank
Okay now lets convert our image
At this point you should have read the A* Tutorial as mentioned above (or know how A* works), or you wont get it !!!
I've said no types ... i was lying ;-)
I will use types to store the running pathfinding processes
So here we go!
So whats that all about?:
info_bank
- this bank will be link between the map and the "A* node's". Each entry in there contains the current status of the node(open, closed, untouched) and the "handle" to the nodes data.(i will explain that later)
node_bank
- this one stores all the nodes data
sort_bank
- this bank contains the sorted "open" list. Each entry consists of the F-value and the node "handle"
temp_bank
- this bank is for swaping memory blocks. you will see later
result_bank
- this one will containe the resulting path in reverse order.
Why reverse? Because A* traces back the best way and you dont know how many steps you need. It can only be "stored" in reverse order from targetpoint to startpoint.
start_x,start_y
end_x,end_y
- that not too hard to guess
data_size
- this one stores how much data is stored per node. this could be different for some reasons (i will cover this later)
status
- stores if the process of pathfinding is still running or cant be solved etc.
current_node
- the current node. Thats importent to continue the search after a break
time
- stores how much time the pathfind can consume
Okay,now we've got all we need to get started.
now we need a function to create and initialise our "as_path"-object
the function is declerated as returntype ".as_path".
!!! To all that dont know about types as returns you should check the forum about that !!!
so what do we do here:
1. we create the info_bank
--------------------------
each entry in this bank consits of 5 byte
1 Byte - status of the tile (open, closed, untouched)
4 Byte Int - this will containe the handle of the node in the node_bank. Its not truely a handle its the offset to the nodes data.
so we got (mapwidth * mapheight * 5)
2.create the node_bank
----------------------
this is left blank cause we dont have any nodes now
3.create the sort_bank
----------------------
this is left blank cause we dont have any nodes to sort now
4.create the result_bank
----------------------
yep, no results till now
5.create the temp_bank
----------------------
The size of the tempbank results from the size of the map and should be guessed at startup.
I will cover this later in detail
6. increment the as_count
-------------------------
its always good to know how many threads we got
Now we need a function to start the process
The whole thing is setup that "entitys" dont have to delete there as_path object. They use always there personal pathfinding object. But this depents on the game and can be changed easily.
1.cleaning up
-------------
step through all the enrys in the info-bank and set the status-byte to zero(untouched). We dont clear the handles cause they will be overwritten. Reseting this would cosume time and time is short!
2. reset node_bank and sort_bank
--------------------------------
no comment
3. store start and endpoint
---------------------------
no comment
4. setting datasize
-------------------
now its time to explain the nodes data:
each node consits of
4 byte int - the F value
4 byte int - the G value
4 byte int - the H value
4 byte int - the Xposition
4 byte int - the Yposition
4 byte int - handle to the PARENT node
check the constants from the start .. that are the offsets in the nodes data
5. set status
-------------
set to 1 ... initialised and ready to go
Switching status
----------------
Now we we should switch each status in the pathfinding process
so each time we run through the list of as_path objects we will call "as_calc"
on status 1 we first check if the search can be made. If the start or end tile is off the map or not walkable the search cannot be successfull so the status is set to 4(cannot calculate)
if the search can be successfull the status is set to 2(calculating) and the startpoint will be stored in the "openlist"
if status is 2 the search is still in progress and will be continued now
status 3 means the path is allready calcualated and the result can be found in the result_bank
status 4 means there is no wy from start to endpoint
Storing nodes in the "openlist"
-------------------------------
now stuff gets serious and we should take a detailed look on the node_bank
the nodebank is a list of all known nodes
it looks like this:
the handle to a node is the offset in the bank
to store an additional node we increase the size of the bank by the datamount per node (24 bytes)
then we store the nodes data into the last block
since we need to access the node with the lowest f score we will always sort the newly added nodes into rthe sortbank
now lets take a closer look into the sortbank:
and we want to be F sorted descending so that the last entry is the lowest F
now we have to ad one new
first we run through from bottom to top until the new f-value is smaler or equal the f value
We got offset 0 cause the new F=25 is smaler then the F=30
Now we copy everything belowe this entry to the tempbank
now increase the size of the bank
now we have to write back the value
now there is space for our new entry
now we wil take a closer look into the info_bank
this is a representation of the known map:
S = status of the Tile (opend, closed or untouched)
H = handle -> Offset to the nodes data in the node_bank
here we will now mark the node as opend
that was tough ... :-)
A* main
-------
now here comes the big block of A*
i will not get into this to deep
the nodes data can be accessed by there handle + the offset of the data
like:
node_g = PeekInt(a\node_bank,node_handle + DEF_OFF_G)
Closing nodes
-------------
okay ... now lets check what happens if a node gets closed
thats easy ...
first we reduce the sortbank by one set of data cause the node to be closed is allways the last one in the list
then we mark the tile in the infobank as closed
now lets see how we can resort the list of handles if we edited one of the nodes
Updating nodes and sort them
----------------------------
This one gave me some headache and i dont know if this is the optimal approach to do this!
first we will search the node to be updated
then we will erase the node
copy everything below
write the data back on the position of the node to be erased
then do the same as descriped when adding a node to the openlist (see above)
but dont increase the size of the bank ... we allready got one free entry
Tracing back the path
---------------------
at this point we have found the endpoint in our openlist
now lets resize our resultbank back to 8
then step from parent to parent and add the X and Y value to the resultbank until there is no more parent node
this will lead from endpoint to the startpoint and the resultbank contains the X,Y Positions leading to the target in reverse order.
Debugging and drawing
---------------------
this is a small function to draw the result
this is for very small maps only (13 x 13)
[code]
Function draw(a.as_path)
Cls()
scale = 55
If a\result_bank Then
Color(100,0,20)
size = BankSize(a\result_bank)
For i=size To 0 Step -8
xs = PeekInt(a\result_bank,i - 8) * scale
ys = PeekInt(a\result_bank,i - 4) * scale
Rect(xs,ys,scale,scale,1)
Next
End If
For y=0 To as_dim_y-1
For x=0 To as_dim_x-1
xs = x * scale
ys = y * scale
offset = (x + y * as_dim_x)
b = PeekByte(as_map_bank,offset)
Color(b,b,b)
Rect(xs,ys,scale,scale,0)
offset = (x + y * as_dim_x) * 5
status = PeekByte(a\info_bank,offset)
node = PeekInt(a\info_bank,offset + 1)
Select status
Case DEF_AS_OPEN
Color(20,250,20)
Rect(xs,ys,scale,scale,0)
Color(200,250,200)
f = PeekInt(a\node_bank,node + DEF_OFF_F)
g = PeekInt(a\node_bank,node + DEF_OFF_G)
h = PeekInt(a\node_bank,node + DEF_OFF_H)
parent = PeekInt(a\node_bank,node + DEF_OFF_PARENT)
px = PeekInt(a\node_bank,parent + DEF_OFF_X)
py = PeekInt(a\node_bank,parent + DEF_OFF_Y)
Text xs + 1,ys + 1,f
Text xs , ys + scale - 1 - (FontHeight()),g
Text xs + scale - StringWidth(h) - 1 , ys + scale - 1 - FontHeight(),h
Line(xs+scale/2,ys+scale/2,px*scale+scale/2,py*scale+scale/2)
Case DEF_AS_CLOSED
Color(20,20,250)
Rect(x*scale,y*scale,scale,scale,0)
Color(200,200,250)
f = PeekInt(a\node_bank,node + DEF_OFF_F)
g = PeekInt(a\node_bank,node + DEF_OFF_G)
h = PeekInt(a\node_bank,node + DEF_OFF_H)
parent = PeekInt(a\node_bank,node + DEF_OFF_PARENT)
px = PeekInt(a\node_bank,parent + DEF_OFF_X)
py = PeekInt(a\node_bank,parent + DEF_OFF_Y)
Text xs + 1,ys + 1,f
Text xs , ys + scale - 1 - (FontHeight()),g
Text xs + scale - StringWidth(h) - 1 , ys + scale - 1 - FontHeight(),h
Line(xs+scale/2,ys+scale/2,px*scale+scale/2,py*scale+scale/2)
End Select
Next
Next
x = PeekInt(a\node_bank,a\current_node + DEF_OFF_X)
y = PeekInt(a\node_bank,a\current_node + DEF_OFF_y)
Color(250,20,20)
Rect(x*scale,y*scale,scale,scale,0)
Color(250,20,20)
Rect(a\end_x*scale,a\end_y*scale,scale,scale,1)
Color(20,250,20)
Rect(a\start_x*scale,a\start_y*scale,scale,scale,1)
size = BankSize(a\sort_bank)-1
While i < size
count = count + 1
Text 800,count * 17, i + " -> f:" + PeekInt(a\sort_bank,i) +" - node:"+ PeekInt(a\sort_bank,i+4)
i = i + 8
Wend
Flip()
End Function
[\code]
so, thats it for now
I hope you find this usefull.
My english isnt that good so if anyone could correct this tutorial to be more understandable please mail the corrected thread to:
rayzor-s-edge@...
i will then update this post.
thx in advance.
If you got some improvements or questions ... please drop a line.
---------------------------------------
Programm: B3D and BP
State: intermediate and advanced users
This Tutorial does not explain A* itself!
For an excelent A* Pathfinding Tutorial check http://www.policyalmanac.org/games/aStarTutorial.htm
You should fully understand A* before continuing this tutorial.
This tutorial explains how to avoid arrays and types while pathfinding.
Why not Arrays?
- arrays are not dynamic and consume lots of memory
- arrays cannot be created on runtime
- arrays are always global
Why not types?
- since types cannot be nested into eachother its hard to parallel process pathfindings. This would take some managment and its hard to deal with.
- types are always global
so my choice is to entirely use banks for pathfinding
Why banks?
- there fast!
- they are small in memory
- they can be nested (will cover this later)
- they are not global
- they can even be stored easily or send through networks
Sorry, but i havent any webspace so i cant include the media.
First you should draw a small image (about 32x32) the background should be black and then paint some walls with pure white(255,255,255).
Save it as Bitmap or PNG. We will call the file "map.png" in the following code
Now we will initialise the graphics
Graphics 1024,768,32,2 SetBuffer(BackBuffer())
We need some globals
;map width and height Global as_dim_x Global as_dim_y ;this is the variable to store the image ... see below Global as_map_bank ;stores how many pathfindings are in progress Global as_count ;the treshhold when tiles are unwalkable Global as_unwalk = 120 ;some constants i will explain later Const DEF_AS_MANHATTEN = 10 Const DEF_AS_OPEN = 1 Const DEF_AS_CLOSED = 2 Const DEF_OFF_F = 0 Const DEF_OFF_G = 4 Const DEF_OFF_H = 8 Const DEF_OFF_X = 12 Const DEF_OFF_Y = 16 Const DEF_OFF_PARENT = 20
Because accessing images with ReadPixel is much slower than reading in memorybanks we will convert our "map.png" to a bank with this function
Function img2bank(img) ;store the image dimension as global map width and height as_dim_x = ImageWidth(map) as_dim_y = ImageHeight(map) mx = as_dim_x my = as_dim_y ;now we create a bank that has as much bytes as pixels in the image bank = CreateBank(mx * my) buffer = ImageBuffer(img) LockBuffer(buffer) ;step through each pixel For y=0 To my-1 For x=0 To mx-1 ;we use the blue channel to determ the brightness b = ReadPixelFast(x,y,buffer) And $FF PokeByte(bank,(x + mx * y),b) Next Next UnlockBuffer(buffer) Return bank End Function
This one is quit simple. We create a bank thats imagewidth x imageheight and store the bluevalue row by row, column by column in the bank
Okay now lets convert our image
map = LoadImage("map.png") as_map_bank = img2bank(map)
At this point you should have read the A* Tutorial as mentioned above (or know how A* works), or you wont get it !!!
I've said no types ... i was lying ;-)
I will use types to store the running pathfinding processes
So here we go!
Type as_path Field info_bank Field node_bank Field sort_bank Field temp_bank Field result_bank Field start_x,start_y Field end_x,end_y Field data_size Field status Field current_node Field time End Type
So whats that all about?:
info_bank
- this bank will be link between the map and the "A* node's". Each entry in there contains the current status of the node(open, closed, untouched) and the "handle" to the nodes data.(i will explain that later)
node_bank
- this one stores all the nodes data
sort_bank
- this bank contains the sorted "open" list. Each entry consists of the F-value and the node "handle"
temp_bank
- this bank is for swaping memory blocks. you will see later
result_bank
- this one will containe the resulting path in reverse order.
Why reverse? Because A* traces back the best way and you dont know how many steps you need. It can only be "stored" in reverse order from targetpoint to startpoint.
start_x,start_y
end_x,end_y
- that not too hard to guess
data_size
- this one stores how much data is stored per node. this could be different for some reasons (i will cover this later)
status
- stores if the process of pathfinding is still running or cant be solved etc.
current_node
- the current node. Thats importent to continue the search after a break
time
- stores how much time the pathfind can consume
Okay,now we've got all we need to get started.
now we need a function to create and initialise our "as_path"-object
Function as_createPath.as_path() a.as_path = New as_path a\info_bank = CreateBank(as_dim_x * as_dim_y * 5) a\node_Bank = CreateBank() a\sort_Bank = CreateBank() a\result_Bank = CreateBank() a\temp_bank = CreateBank(1024*4) as_count = as_count + 1 Return a End Function
the function is declerated as returntype ".as_path".
!!! To all that dont know about types as returns you should check the forum about that !!!
so what do we do here:
1. we create the info_bank
--------------------------
each entry in this bank consits of 5 byte
1 Byte - status of the tile (open, closed, untouched)
4 Byte Int - this will containe the handle of the node in the node_bank. Its not truely a handle its the offset to the nodes data.
so we got (mapwidth * mapheight * 5)
2.create the node_bank
----------------------
this is left blank cause we dont have any nodes now
3.create the sort_bank
----------------------
this is left blank cause we dont have any nodes to sort now
4.create the result_bank
----------------------
yep, no results till now
5.create the temp_bank
----------------------
The size of the tempbank results from the size of the map and should be guessed at startup.
I will cover this later in detail
6. increment the as_count
-------------------------
its always good to know how many threads we got
Now we need a function to start the process
Function as_pathfind(a.as_path,start_x,start_y,end_x,end_y) size = BankSize(a\info_bank)-1 For i=0 To size Step 5 PokeByte(a\info_bank,i,0) Next ResizeBank(a\node_bank,0) ResizeBank(a\sort_bank,0) a\start_x = start_x a\start_y = start_y a\end_x = end_x a\end_y = end_y a\data_size = 4 * 6 a\status = 1 End Function
The whole thing is setup that "entitys" dont have to delete there as_path object. They use always there personal pathfinding object. But this depents on the game and can be changed easily.
1.cleaning up
-------------
step through all the enrys in the info-bank and set the status-byte to zero(untouched). We dont clear the handles cause they will be overwritten. Reseting this would cosume time and time is short!
2. reset node_bank and sort_bank
--------------------------------
no comment
3. store start and endpoint
---------------------------
no comment
4. setting datasize
-------------------
now its time to explain the nodes data:
each node consits of
4 byte int - the F value
4 byte int - the G value
4 byte int - the H value
4 byte int - the Xposition
4 byte int - the Yposition
4 byte int - handle to the PARENT node
check the constants from the start .. that are the offsets in the nodes data
5. set status
-------------
set to 1 ... initialised and ready to go
Switching status
----------------
Now we we should switch each status in the pathfinding process
Function as_calc(a.as_path,time = 99999999) a\time = time map_bank = as_map_bank mx = as_dim_x my = as_dim_y Select a\status Case 1 ;initialise If a\start_x => mx Or a\start_x < 0 Or a\start_y => my Or a\start_y < 0 Then ;the startpoint is off the map a\status = 4 Return False EndIf If a\end_x => mx Or a\end_x < 0 Or a\end_y => my Or a\end_y < 0 Then ;the endpoint is off the map a\status = 4 Return False EndIf ;start oder endpunkt ist nicht begehbar If PeekByte(map_bank,a\start_x + mx * a\start_y) => as_unwalk Then ;the startpoint is not walkable a\status = 4 Return False EndIf If PeekByte(map_bank,a\end_x + mx * a\end_y) => as_unwalk Then ;the endpoint is not walkable a\status = 4 Return False EndIf ;open the startnode a\current_node = as_openNode(a,f,g,h,a\start_x,a\start_y,parent) a\status = 2 ;start the search Return as_path(a.as_path) Case 2 ;calculating ;continue the search Return as_path(a.as_path) Case 3 ;finished Return 3 Case 4 ;cannot calculate Return 4 End Select End Function
so each time we run through the list of as_path objects we will call "as_calc"
on status 1 we first check if the search can be made. If the start or end tile is off the map or not walkable the search cannot be successfull so the status is set to 4(cannot calculate)
if the search can be successfull the status is set to 2(calculating) and the startpoint will be stored in the "openlist"
if status is 2 the search is still in progress and will be continued now
status 3 means the path is allready calcualated and the result can be found in the result_bank
status 4 means there is no wy from start to endpoint
Storing nodes in the "openlist"
-------------------------------
now stuff gets serious and we should take a detailed look on the node_bank
the nodebank is a list of all known nodes
it looks like this:
Offset Datablock +---+---+---+---+---+------+ 0 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 24 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 48 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ . . . +---+---+---+---+---+------+ n*24 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+
the handle to a node is the offset in the bank
to store an additional node we increase the size of the bank by the datamount per node (24 bytes)
+---+---+---+---+---+------+ 0 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 24 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 48 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 72 | | | | | | | +---+---+---+---+---+------+
then we store the nodes data into the last block
+---+---+---+---+---+------+ 0 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 24 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 48 | F | G | H | X | Y |PARENT| +---+---+---+---+---+------+ 72 | | | | | | | <<< Poke new values +---+---+---+---+---+------+
since we need to access the node with the lowest f score we will always sort the newly added nodes into rthe sortbank
now lets take a closer look into the sortbank:
Offset Datablock +---+--------+ 0 | F | HANDLE | +---+--------+ 5 | F | HANDLE | +---+--------+ 10 | F | HANDLE | +---+--------+ . . . +---+--------+ n*5 | F | HANDLE | +---+--------+
and we want to be F sorted descending so that the last entry is the lowest F
now we have to ad one new
+---+--------+ new | 25| HANDLE | +---+--------+ +---+--------+ 0 | 30| HANDLE | +---+--------+ 5 | 20| HANDLE | +---+--------+ 10 | 10| HANDLE | +---+--------+
first we run through from bottom to top until the new f-value is smaler or equal the f value
size = BankSize(a\sort_bank) offset = size While offset > 0 other_f = PeekInt(a\sort_bank,offset - 8) If f <= other_f Exit EndIf offset = offset - 8 Wend
We got offset 0 cause the new F=25 is smaler then the F=30
Now we copy everything belowe this entry to the tempbank
count = size - offset nOffset = offset + 8 CopyBank(a\sort_bank,offset,a\temp_bank,0,count)
+---+--------+ 0 | 30| HANDLE | +---+--------+ +------------+ 5 | 20| HANDLE | >>> | TEMP | +---+--------+ +------------+ 10 | 10| HANDLE | >>> | TEMP | +---+--------+ +------------+
now increase the size of the bank
size = BankSize(a\sort_bank) + 8
+---+--------+ 0 | 30| HANDLE | +---+--------+ 5 | 20| HANDLE | +---+--------+ 10 | 10| HANDLE | +---+--------+ 15 | | | +---+--------+
now we have to write back the value
ResizeBank(a\sort_bank,size) CopyBank(a\temp_bank,0,a\sort_bank,nOffset,count)
+---+--------+ 0 | 30| HANDLE | +---+--------+ 5 | 20| HANDLE | +---+--------+ +------------+ 10 | 20| HANDLE | <<< | TEMP | +---+--------+ +------------+ 15 | 10| HANDLE | <<< | TEMP | +---+--------+ +------------+
now there is space for our new entry
PokeInt(a\sort_bank,offset,f) PokeInt(a\sort_bank,offset+4,hoffset)
+---+--------+ 0 | 30| HANDLE | +---+--------+ 5 | 25| HANDLE | <<< Poke new values +---+--------+ 10 | 20| HANDLE | +---+--------+ 15 | 10| HANDLE | +---+--------+
now we wil take a closer look into the info_bank
this is a representation of the known map:
S = status of the Tile (opend, closed or untouched)
H = handle -> Offset to the nodes data in the node_bank
Offset DATA(5 byte per tile) +-------+-------+-------+ +-------+ 0*width*5 | S & H | S & H | S & H | | S & H | +-------+-------+-------+ +-------+ 1*width*5 | S & H | S & H | S & H | ... | S & H | +-------+-------+-------+ +-------+ 2*width*5 | S & H | S & H | S & H | | S & H | +-------+-------+-------+ +-------+ . . . +-------+-------+-------+ +-------+ n*width*5 | S & H | S & H | S & H | ... | S & H | +-------+-------+-------+ +-------+
here we will now mark the node as opend
offset = (x + (y * as_dim_x)) * 5 PokeByte(a\info_bank,offset,DEF_AS_OPEN) PokeInt(a\info_bank,offset+1,hOffset)
Function as_openNode(a.as_path,f,g,h,x,y,parent) ;increase the bank to one more entry hOffset = BankSize(a\node_bank) ResizeBank(a\node_bank,hOffset + a\data_size) size = BankSize(a\sort_bank) offset = size While offset > 0 other_f = PeekInt(a\sort_bank,offset - 8) If f <= other_f Exit EndIf offset = offset - 8 Wend count = size - offset nOffset = offset + 8 CopyBank(a\sort_bank,offset,a\temp_bank,0,count) size = BankSize(a\sort_bank) + 8 ResizeBank(a\sort_bank,size) CopyBank(a\temp_bank,0,a\sort_bank,nOffset,count) ;write the f and the handle in the sort_bank PokeInt(a\sort_bank,offset,f) PokeInt(a\sort_bank,offset+4,hoffset) ;write the data into the new node space PokeInt(a\node_bank,hOffset + DEF_OFF_F,f) PokeInt(a\node_bank,hOffset + DEF_OFF_G,g) PokeInt(a\node_bank,hOffset + DEF_OFF_H,h) PokeInt(a\node_bank,hOffset + DEF_OFF_X,x) PokeInt(a\node_bank,hOffset + DEF_OFF_Y,y) PokeInt(a\node_bank,hOffset + DEF_OFF_PARENT,parent) ;mark the tile as opend offset = (x + (y * as_dim_x)) * 5 PokeByte(a\info_bank,offset,DEF_AS_OPEN) PokeInt(a\info_bank,offset+1,hOffset) ;return the handle Return hoffset End Function
that was tough ... :-)
A* main
-------
now here comes the big block of A*
Function as_path(a.as_path) map_bank = as_map_bank mx = as_dim_x my = as_dim_y begin = MilliSecs() ;as long we are in time ... While (MilliSecs() - begin) < a\time ;close the node ;the below for this function as_closeNode(a,a\current_node) current_x = PeekInt(a\node_bank,a\current_node + DEF_OFF_X) current_y = PeekInt(a\node_bank,a\current_node + DEF_OFF_Y) current_g = PeekInt(a\node_bank,a\current_node + DEF_OFF_G) ;check to see if the endpoint has been reached If current_x = a\end_x And current_y = a\end_y Then ;the endpoint has been reach ;create and fill the resultbank ; i will explain this at the end of this tutorial .. so leave this now a\result_bank = CreateBank(8) size = 8 current_node = a\current_node x = PeekInt(a\node_bank,current_node + DEF_OFF_X) y = PeekInt(a\node_bank,current_node + DEF_OFF_Y) PokeInt(a\result_bank,0,x) PokeInt(a\result_bank,4,y) scale = 55 While current_node current_node = PeekInt(a\node_bank,current_node + DEF_OFF_PARENT) x = PeekInt(a\node_bank,current_node + DEF_OFF_X) y = PeekInt(a\node_bank,current_node + DEF_OFF_Y) size = size + 8 ResizeBank(a\result_bank,size) PokeInt(a\result_bank,size - 8,x) PokeInt(a\result_bank,size - 4,y) Wend a\status = 3 Return True EndIf ;open all surrounding nodes For y = current_y - 1 To current_y + 1 For x = current_x - 1 To current_x + 1 ;if its not the center ... If x <> current_x Or y <> current_y ;.. and not off the map ... If x < mx And x >= 0 And y < my And y >= 0 Then offset = (x + mx * y) offset2 = offset * 5 new_status = PeekByte(a\info_bank,offset2) b = PeekByte(map_bank,offset) ;... and not allready closed or unwalkable If new_status <> DEF_AS_CLOSED And b < as_unwalk g = 14 If x = current_x Or y = current_y Then g = 10 g = current_g + g; + (b / 10) Select new_status Case DEF_AS_OPEN ;this one is allready open check if this one should be edited new_node = PeekInt(a\info_bank,offset2+1) new_g = PeekInt(a\node_bank,new_node + DEF_OFF_G) new_h = PeekInt(a\node_bank,new_node + DEF_OFF_H) If g < new_g ;this node must be edited ... as_updateNode will sort this into the sort_bank ; will explain this later f = g + new_h ;update the values in the nodebank PokeInt(a\node_bank,new_node + DEF_OFF_PARENT,a\current_node) PokeInt(a\node_bank,new_node + DEF_OFF_G,g) PokeInt(a\node_bank,new_node + DEF_OFF_F,f) ;resort the sortlist as_updateNode(a.as_path,new_node,f) EndIf Default ;this tile is still untouched ... add this to the openlist h = (Abs(a\end_x - x) + Abs(a\end_y - y)) * DEF_AS_MANHATTEN f = g + h parent = a\current_node as_openNode(a.as_path,f,g,h,x,y,parent) End Select EndIf EndIf EndIf Next Next ;this one is for debbuging and uses the draw function(see below) draw(a) WaitKey ; get the next node with the lowest F score size = BankSize(a\sort_bank) If size = 0 Then ;oops .. no more nodes .. there is no path to the endpoint ;set status to 4 (cannot calculate) a\status = 4 Return False Else ;get next node for next loop a\current_node = PeekInt(a\sort_bank,BankSize(a\sort_bank)-4) EndIf Wend Return True End Function
i will not get into this to deep
the nodes data can be accessed by there handle + the offset of the data
like:
node_g = PeekInt(a\node_bank,node_handle + DEF_OFF_G)
Closing nodes
-------------
okay ... now lets check what happens if a node gets closed
Function as_closeNode(a.as_path,node) size = BankSize(a\sort_bank) ResizeBank(a\sort_bank,size - 8) x = PeekInt(a\node_bank,node + DEF_OFF_X) y = PeekInt(a\node_bank,node + DEF_OFF_Y) offset = (x + y * as_dim_x) * 5 PokeByte(a\info_bank,offset,DEF_AS_CLOSED) Return End Function
thats easy ...
first we reduce the sortbank by one set of data cause the node to be closed is allways the last one in the list
then we mark the tile in the infobank as closed
now lets see how we can resort the list of handles if we edited one of the nodes
Updating nodes and sort them
----------------------------
This one gave me some headache and i dont know if this is the optimal approach to do this!
first we will search the node to be updated
offset = size While offset > 0 offset = offset - 8 other_node = PeekInt(a\sort_bank,offset + 4) If node = other_node Exit EndIf Wend
then we will erase the node
+---+--------+ 0 | 30| HANDLE | +---+--------+ 5 | 20| HANDLE | <<< should be erased +---+--------+ 10 | 10| HANDLE | +---+--------+ 15 | 5 | HANDLE | +---+--------+
copy everything below
+---+--------+ 0 | 30| HANDLE | +---+--------+ 5 | 20| HANDLE | +---+--------+ +------------+ 10 | 10| HANDLE | >>> | | +---+--------+ +---TEMP-----+ 15 | 5 | HANDLE | >>> | | +---+--------+ +------------+
write the data back on the position of the node to be erased
+---+--------+ 0 | 30| HANDLE | +---+--------+ +------------+ 5 | 10| HANDLE | <<< | | +---+--------+ +---TEMP-----+ 10 | 5 | HANDLE | <<< | | +---+--------+ +------------+ 15 | 5 | HANDLE | +---+--------+
then do the same as descriped when adding a node to the openlist (see above)
but dont increase the size of the bank ... we allready got one free entry
Function as_updateNode(a.as_path,node,f) size = BankSize(a\sort_bank) ;find the node to update offset = size While offset > 0 offset = offset - 8 other_node = PeekInt(a\sort_bank,offset + 4) If node = other_node Exit EndIf Wend ;erase the node count = size - offset CopyBank(a\sort_bank,offset,a\temp_bank,0,count) CopyBank(a\temp_bank,8,a\sort_bank,offset,count-8) offset = size - 8 While offset > 0 other_f = PeekInt(a\sort_bank,offset - 8) If f <= other_f Exit EndIf offset = offset - 8 Wend count = size - offset - 8 CopyBank(a\sort_bank,offset,a\temp_bank,0,count) nOffset = offset + 8 CopyBank(a\temp_bank,0,a\sort_bank,nOffset,count) ;write the f and the handle in the sort_bank PokeInt(a\sort_bank,offset,f) PokeInt(a\sort_bank,offset+4,node) End Function
Tracing back the path
---------------------
at this point we have found the endpoint in our openlist
now lets resize our resultbank back to 8
then step from parent to parent and add the X and Y value to the resultbank until there is no more parent node
this will lead from endpoint to the startpoint and the resultbank contains the X,Y Positions leading to the target in reverse order.
Offset Datablock +---+---+ 0 | X | Y | +---+---+ 8 | X | Y | +---+---+ 16 | X | Y | +---+---+ . . . +---+---+ n*8 | X | Y | +---+---+
;create and fill the resultbank resizeBank(a\result_bank,8) size = 8 current_node = a\current_node x = PeekInt(a\node_bank,current_node + DEF_OFF_X) y = PeekInt(a\node_bank,current_node + DEF_OFF_Y) PokeInt(a\result_bank,0,x) PokeInt(a\result_bank,4,y) scale = 55 While current_node current_node = PeekInt(a\node_bank,current_node + DEF_OFF_PARENT) x = PeekInt(a\node_bank,current_node + DEF_OFF_X) y = PeekInt(a\node_bank,current_node + DEF_OFF_Y) size = size + 8 ResizeBank(a\result_bank,size) PokeInt(a\result_bank,size - 8,x) PokeInt(a\result_bank,size - 4,y) Wend a\status = 3 Return True
Debugging and drawing
---------------------
this is a small function to draw the result
this is for very small maps only (13 x 13)
[code]
Function draw(a.as_path)
Cls()
scale = 55
If a\result_bank Then
Color(100,0,20)
size = BankSize(a\result_bank)
For i=size To 0 Step -8
xs = PeekInt(a\result_bank,i - 8) * scale
ys = PeekInt(a\result_bank,i - 4) * scale
Rect(xs,ys,scale,scale,1)
Next
End If
For y=0 To as_dim_y-1
For x=0 To as_dim_x-1
xs = x * scale
ys = y * scale
offset = (x + y * as_dim_x)
b = PeekByte(as_map_bank,offset)
Color(b,b,b)
Rect(xs,ys,scale,scale,0)
offset = (x + y * as_dim_x) * 5
status = PeekByte(a\info_bank,offset)
node = PeekInt(a\info_bank,offset + 1)
Select status
Case DEF_AS_OPEN
Color(20,250,20)
Rect(xs,ys,scale,scale,0)
Color(200,250,200)
f = PeekInt(a\node_bank,node + DEF_OFF_F)
g = PeekInt(a\node_bank,node + DEF_OFF_G)
h = PeekInt(a\node_bank,node + DEF_OFF_H)
parent = PeekInt(a\node_bank,node + DEF_OFF_PARENT)
px = PeekInt(a\node_bank,parent + DEF_OFF_X)
py = PeekInt(a\node_bank,parent + DEF_OFF_Y)
Text xs + 1,ys + 1,f
Text xs , ys + scale - 1 - (FontHeight()),g
Text xs + scale - StringWidth(h) - 1 , ys + scale - 1 - FontHeight(),h
Line(xs+scale/2,ys+scale/2,px*scale+scale/2,py*scale+scale/2)
Case DEF_AS_CLOSED
Color(20,20,250)
Rect(x*scale,y*scale,scale,scale,0)
Color(200,200,250)
f = PeekInt(a\node_bank,node + DEF_OFF_F)
g = PeekInt(a\node_bank,node + DEF_OFF_G)
h = PeekInt(a\node_bank,node + DEF_OFF_H)
parent = PeekInt(a\node_bank,node + DEF_OFF_PARENT)
px = PeekInt(a\node_bank,parent + DEF_OFF_X)
py = PeekInt(a\node_bank,parent + DEF_OFF_Y)
Text xs + 1,ys + 1,f
Text xs , ys + scale - 1 - (FontHeight()),g
Text xs + scale - StringWidth(h) - 1 , ys + scale - 1 - FontHeight(),h
Line(xs+scale/2,ys+scale/2,px*scale+scale/2,py*scale+scale/2)
End Select
Next
Next
x = PeekInt(a\node_bank,a\current_node + DEF_OFF_X)
y = PeekInt(a\node_bank,a\current_node + DEF_OFF_y)
Color(250,20,20)
Rect(x*scale,y*scale,scale,scale,0)
Color(250,20,20)
Rect(a\end_x*scale,a\end_y*scale,scale,scale,1)
Color(20,250,20)
Rect(a\start_x*scale,a\start_y*scale,scale,scale,1)
size = BankSize(a\sort_bank)-1
While i < size
count = count + 1
Text 800,count * 17, i + " -> f:" + PeekInt(a\sort_bank,i) +" - node:"+ PeekInt(a\sort_bank,i+4)
i = i + 8
Wend
Flip()
End Function
[\code]
so, thats it for now
I hope you find this usefull.
My english isnt that good so if anyone could correct this tutorial to be more understandable please mail the corrected thread to:
rayzor-s-edge@...
i will then update this post.
thx in advance.
If you got some improvements or questions ... please drop a line.