diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f162aba --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Saved_Map.cbastar \ No newline at end of file diff --git a/Example Media/cdm2.til b/Example Media/cdm2.til new file mode 100644 index 0000000..6f8ebcb Binary files /dev/null and b/Example Media/cdm2.til differ diff --git a/Example Media/guy.bmp b/Example Media/guy.bmp new file mode 100644 index 0000000..fd70158 Binary files /dev/null and b/Example Media/guy.bmp differ diff --git a/Example Media/tileset.bmp b/Example Media/tileset.bmp new file mode 100644 index 0000000..b542ba6 Binary files /dev/null and b/Example Media/tileset.bmp differ diff --git a/Tilemap Example.CB b/Tilemap Example.CB new file mode 100644 index 0000000..f1c824a --- /dev/null +++ b/Tilemap Example.CB @@ -0,0 +1,153 @@ +//CbAStar Tilemap Example Program +'Use this example program to watch the cbAStar library in action and to learn how to implement it into a tilemap based +'game. + +//Include cbAStrar in order to use its functions. +Include "cbAStar.cb" + +//Configure window settings +SCREEN 1024,768 +FrameLimit 40 +SetWindow "cbAStar Version "+CBASTAR_VERSION+" - Tilemap Example Program" + +//Define global variables +Global Map +Global TileW, TileH + +//Load media +Map = LoadMap("Example Media\cdm2.til","Example Media\tileset.bmp") +character = LoadObject("Example Media\guy.bmp",72) +PlayObject map,0,0,.5 +'Set character speed +Const CHARACTER_SPEED = 3 'in PPC (Pixels Per main loop Cycle) + +//Initialize cbAStar in order to be able to use it correctly +'When initialized with ...UsingTilemap() function, cbAStar automatically reads the whole tilemap and copies obstacles +'from the tilemap to cbAStar's own map array. We do not even need to pass map width and height to this function, +'because the function will read those values from the tilemap. +'Note! A tilemap must be loaded BEFORE using this function! +cbAStarInitializeUsingTilemap() + +//Configure collision between the character and the tilemap +'This actually is not needed because of the intelligent pathfinding system :) +'I should not affect anywhere, but we do this just in case. +SetupCollision character, Map, 2, 4, 1 + +//Calculate a single tile dimensions +TileW = ObjectSizeX(Map)/MapWidth() +TileH = ObjectSizeY(Map)/MapHeight() + +//Now we are ready to start the main loop +Repeat + + //Click with mouse starts the pathfinding + If MouseHit(1) Then + If path > 0 Then DeleteMEMBlock path 'Delete old path If exists so that it will Not consume futile memory + + //Calculate starting And Ending nodes + 'Node coordinates must always be so that they fit in a 2D array! + 'Starting node is calculated from the character's position + start_node_x = WorldXtoNodeX(ObjectX(character)) + start_node_y = WorldYtoNodeY(ObjectY(character)) + 'Ending node is calculated from the mouse cursor's position + end_node_x = WorldXtoNodeX(MouseWX()) + end_node_y = WorldYtoNodeY(MouseWY()) + + //Calculate whole path at once + 'Variable path will have one of the following values: + ' Positive Integer = Pointer To a MemBlock containing the calculated path + ' -1 (Negative One) = Means that there is no possible path between the starting And ending node. + path = CalculatePath(start_node_x,start_node_y, end_node_x,end_node_y) + + //If the path calculating succeeded, Read the First node OFF from the path + 'The First node is the same As the character's current location. That is why we can skip it. + If path > 0 Then FollowPath(path) 'FollowPath() now seeks To the Next node in the path + + //Reset next_node_x And next_node_y + 'When next_node_x is -1 Or next_node_y is -1, we know that it is needed To Read the Next node's coordinates + 'ON the path in order To know where the character should be heading For. + next_node_x = -1 + next_node_y = -1 + + //A note about the terms node And tile! + 'A node's coordinates are calculated exactly the same way As a tile's coordinates. So you can actually think + 'that the term "node" means the same As the term "tile" If it is easier For you To figure out. The only difference between the + 'term node And the term tile is that inside the cbAStar library the term tile is Not used anywhere because + 'the library has been designed To be used also in environments that does Not necessarily use tiles Or tilemaps. + 'So the point is: The term node is used mainly inside the cbAStar library And the term tile is used outside the + 'library in those implementations that uses tiles And tilemaps - such As this example program. + EndIf + + //If we do have a succesfully calculated path, make the character To follow it. + If path > 0 Then + + //If we do Not know coordinates where the character should Next be heading For, find them out. + If next_node_x = -1 Or next_node_y = -1 Then + //Try To follow the path. + 'If we are already at the End of the path, FollowPath() will Return False And the character just stays in + 'its place. + If FollowPath(path) Then + 'FollowPath() Return the Next node's coordinates in Global variables. + 'Pick up the results: + next_node_x = FollowPathX + next_node_y = FollowPathY + EndIf + EndIf + + //If we do know coordinates of a node where the character should be heading For, move the character + If next_node_x > -1 Or next_node_y > -1 Then + //Convert the Next node's coordinates To the world coordinates so that they can be used with the character object. + target_x = NodeXtoWorldX(next_node_x) + target_y = NodeYtoWorldY(next_node_y) + + //Calculate an angle required To head For the target coordinates And turn the character + character_angle#= GetAngle(ObjectX(character),-ObjectY(character), target_x,-target_y) + RotateObject character, character_angle + + //Finally move the character + 'The speed of the character is configured in a constant at the beginning of this example program. + MoveObject character, CHARACTER_SPEED + + //Figure If the character has reached its current target coordinates? + 'It would be unrealistic To check out the character's And the target's coordinates like they should be exactly the equal + 'To Each other. If you make the comparing statement To do it that way, the character can First move a little over its + 'target And Then try To come back because the character speed makes it move a litle bit unaccurately. That would be ugly + 'thing To happen! + If Distance(ObjectX(character),ObjectY(character), target_x,target_y) < character_speed Then + 'The character is near enough To the target. + 'Reset next_node_x And next_node_y To trigger the code block at lines 85 - 95 To fetch a Next target. + next_node_x = -1 + next_node_y = -1 + EndIf + EndIf + EndIf + + //Move the camera so that we can always see the character + 'UpdateGame is prefered To be commanded Before moving the camera. + UpdateGame + CloneCameraPosition character + + //Finally draw anything we have in this example program + DrawScreen +Forever + +//Tool functions for converting coordinates +'These four functions are used to convert node coordinates to world coordinates - and on the contrary +'The same functions can also be used with tiles instead of nodes. + +Function NodeXtoWorldX(x) + Return (x*TileW-TileW/2) - (ObjectSizeX(Map)/2-ObjectX(Map)) +End Function + +Function NodeYtoWorldY(y) + Return -((y*TileH-TileH/2) - (ObjectSizeY(Map)/2-ObjectY(Map))) +End Function + +Function WorldXtoNodeX(x As Integer) + Return ((ObjectSizeX(Map)/2-ObjectX(Map)) + (x+TileW/2))/TileW +End Function + +Function WorldYtoNodeY(y As Integer) + Return ((ObjectSizeY(Map)/2-ObjectY(Map)) - (y-TileH/2))/TileH +End Function + diff --git a/Ultimate Example Program.cb b/Ultimate Example Program.cb new file mode 100644 index 0000000..72cba45 --- /dev/null +++ b/Ultimate Example Program.cb @@ -0,0 +1,272 @@ +//cbAStar Ultimate Testing Program +'Use this testing program if you want to test all of the features provided by cbAStar +'However, if you want to see cbAStar in action in a real looking game, please see Tilemap Example Program. +'Note! To use all of cbAStar's features with this example program, you need to modify the cbAStar.cb file +'and set rules F, G and H to 1. You can also configure those rules more closely there. + +//Include cbAStrar in order to use its functions. +Include "cbAStar.cb" + +//Configure window settings +SCREEN 800,600 +ClsColor 128,128,128 +FrameLimit 40 +SetWindow "cbAStar Version "+CBASTAR_VERSION+" - Ultimate Testing Program" + +//Define constants to be used in this example program +'The actual cbAStar library is NOT depended on these constants! +Const NODE_MAP_WIDTH = 20 'Map width will be 20 nodes And +Const NODE_MAP_HEIGHT = 13 ' map height will be 13 nodes. +Const NODE_WIDTH = 40 'A node's width will be 40 pixels And +Const NODE_HEIGHT = 40 ' height 40 pixels too. +Const MAP_SAVE_FILE = "Saved_Map.cbastar" 'A file where a created map will be saved (when pressing S) And loaded from (when pressing L). +Const CHARACTER_SPEED = 3 'Character's move speed in PPC (Pixels Per main loop Cycle) + +//Initialize cbAStar +cbAStarInitialize(NODE_MAP_WIDTH,NODE_MAP_HEIGHT) + +//Set path following related variables +start_x = - 1 +start_y = - 1 +end_x = - 1 +end_y = - 1 + +//Now we are ready to start the main loop +Repeat + //Keyboard And Mouse controls + update_path = False + node_x = MouseX()/NODE_WIDTH + node_y = MouseY()/NODE_HEIGHT + If MouseHit(1) Then + 'Place starting node + start_x = node_x + start_y = node_y + update_path = True + ElseIf MouseHit(2) Then + 'Place ending node + end_x = node_x + end_y = node_y + update_path = True + ElseIf KeyHit(cbKeySpace) Then + 'Place Or remove obstacle + x = node_x + y = node_y + CbAStarMap(x,y,1) = Not CbAStarMap(x,y,1) + update_path = True + EndIf + If CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then + If KeyHit(cbKeyAdd) Then + 'Increase single node penalty + SetAStarPenalty(node_x,node_y, GetAstarPenalty(node_x,node_y) + 2) + update_path = True + ElseIf KeyHit(cbKeySubtract) Then + 'Decrease single node penalty + SetAStarPenalty(node_x,node_y, GetAstarPenalty(node_x,node_y) - 2) + update_path = True + ElseIf KeyHit(cbKeyBackspace) Then + 'Clear single node penalty + SetAStarPenalty(node_x,node_y, 0) + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + 'Clear also single node penalty direction + '(clear the actual penalty value, Not the direction) + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT,CBASTAR_DEFAULT, 0) + EndIf + update_path = True + EndIf + EndIf + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + 'Set single node penalty direction + old_update_path = update_path + update_path = True + If KeyHit(cbKeyNum6) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 0) + ElseIf KeyHit(cbKeyNum9) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 1) + ElseIf KeyHit(cbKeyNum8) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 2) + ElseIf KeyHit(cbKeyNum7) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 3) + ElseIf KeyHit(cbKeyNum4) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 4) + ElseIf KeyHit(cbKeyNum1) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 5) + ElseIf KeyHit(cbKeyNum2) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 6) + ElseIf KeyHit(cbKeyNum3) Then + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, 7) + ElseIf KeyHit(cbKeyMultiply) Then + 'Increase single penalty attached To a specific direction + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, CBASTAR_DEFAULT, GetAStarPenalty(node_x,node_y,2) + 2) + ElseIf KeyHit(cbKeyDivide) Then + 'Decrease single penalty attached To a specific direction + SetAStarPenalty(node_x,node_y, CBASTAR_DEFAULT, CBASTAR_DEFAULT, GetAStarPenalty(node_x,node_y,2) - 2) + Else + update_path = old_update_path + EndIf + EndIf + + If KeyHit(cbKeyS) Then + 'Save all obstacles And penalties To a file. + 'Note! This does Not save the starting And ending node locations because SaveAStarMap() does Not save any + 'information about already calculated paths! + SaveAStarMap(map_save_file) + ElseIf KeyHit(cbKeyL) Then + 'Load all obstacles And penalties from a file. + LoadAStarMap(map_save_file,True) + update_path = True + EndIf + + //Calculate a path If requested by any above condition via update_path variable. + If (update_path = True) And start_x > -1 And start_y > -1 And end_x > -1 And end_y > -1 Then + If path > 0 Then DeleteMEMBlock path 'Delete old path If exists + start_timer = Timer() + path = CalculatePath(start_x,start_y, end_x,end_y) + calculation_time = Timer()-start_timer 'Measure Time that the calculation will take + + //Place the character To the start of the path + character_x# = start_x*NODE_WIDTH+NODE_WIDTH/2 + character_y# = start_y*NODE_HEIGHT+NODE_HEIGHT/2 + + //Set character's target node coordinates To -1 To trigger path following code + character_node_x = -1 + character_node_y = -1 + EndIf + + //Draw nice obstacles the starting point And the ending point + For x = 0 To NODE_MAP_WIDTH-1 + For y = 0 To NODE_MAP_HEIGHT-1 + + 'Draw square + Color 0,0,0 + If start_x = x And start_y = y Then Color 255, 255, 0 + If end_x = x And end_y = y Then Color 0, 255, 0 + If CbAStarMap(x,y,1) Then Color 255, 0, 0 + Box x*NODE_WIDTH,y*NODE_HEIGHT, NODE_WIDTH,NODE_HEIGHT + + 'Draw grid + Color 255,255,255 + Line x*NODE_WIDTH, y*NODE_HEIGHT, (x+1)*NODE_WIDTH, y*NODE_HEIGHT + Line x*NODE_WIDTH, y*NODE_HEIGHT, x*NODE_WIDTH, (y+1)*NODE_HEIGHT + + 'Show single node penalty + If CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then + Color 255,255,255 + If CbAStarMap(x,y,2) Then Text x*NODE_WIDTH,y*NODE_HEIGHT, "P"+CbAStarMap(x,y,2) + EndIf + + 'Show direction based single node penalty + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + If CbAStarMap(x,y,4) <> 0 Then + Color 0,0,255 + coordinate_change$ = DirectionToCoordinateChange(CbAStarMap(x,y,3)) + x_change = Int(GetWord(coordinate_change,1)) + y_change = Int(GetWord(coordinate_change,2)) + Line x*NODE_WIDTH+NODE_WIDTH/2,y*NODE_HEIGHT+NODE_HEIGHT/2, x*NODE_WIDTH+NODE_WIDTH/2+x_change*NODE_WIDTH/2,y*NODE_HEIGHT+NODE_HEIGHT/2+y_change*NODE_HEIGHT/2 + Color 0,0,128 + Text x*NODE_WIDTH,y*NODE_HEIGHT+15, "DP"+CbAStarMap(x,y,4) + EndIf + EndIf + Next y + Next x + + //Draw path If it was calculate successfully + If path > 0 Then + old_x = -1 + old_y = -1 + Color 255,0,255 + For i = 0 To MEMBlockSize(path)-4 Step 4 + //Fetch node coordinates + node_x = PeekShort(path,i) + node_y = PeekShort(path,i+2) + + //Calculate node's drawing coordinates + draw_x = node_x * NODE_WIDTH + NODE_WIDTH/2 - 2 + draw_y = node_y * NODE_HEIGHT + NODE_HEIGHT/2 - 2 + If old_x=-1 Then old_x=draw_x : old_y = draw_y + + //Draw node + Box draw_x,draw_y, 4,4 + Line old_x+2,old_y+2, draw_x+2,draw_y+2 + old_x = draw_x + old_y = draw_y + Next i + Color 0,0,0 + Text 0,ScreenHeight()-75, "Path calculation took "+calculation_time+" milliseconds" + ElseIf path = -1 Then + Color 0,0,0 + Text 0,ScreenHeight()-75, "Path cannot be calculated" + EndIf + + //Show direction based single node penalty + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + For x = 0 To CbAStarMapWidth-1 + For y = 0 To CbAStarMapHeight-1 + If CbAStarMap(x,y,4) <> 0 Then + Color 0,0,50 + coordinate_change$ = DirectionToCoordinateChange(CbAStarMap(x,y,3)) + x_change = Int(GetWord(coordinate_change,1)) + y_change = Int(GetWord(coordinate_change,2)) + Line x*NODE_WIDTH+NODE_WIDTH/2,y*NODE_HEIGHT+NODE_HEIGHT/2, x*NODE_WIDTH+NODE_WIDTH/2+x_change*NODE_WIDTH/2,y*NODE_HEIGHT+NODE_HEIGHT/2+y_change*NODE_HEIGHT/2 + Color 0,0,255 + Text x*NODE_WIDTH,y*NODE_HEIGHT+15, "DP"+CbAStarMap(x,y,4) + EndIf + Next y + Next x + EndIf + + //Show moving character + If path > 0 Then + //If Next node is Not known, find it out + If (character_node_x = -1 Or character_node_y = -1) + If FollowPath(path) Then + //FollowPath() gave the Next node + character_node_x = FollowPathX + character_node_y = FollowPathY + Else + //The path does Not have anymore nodes, so restart the path. + ResetFollowPath(path) + character_x = start_x*NODE_W+NODE_W/2 + character_y = start_y*NODE_H+NODE_H/2 + EndIf + EndIf + + //If Next node is know so head For it + If (character_node_x > -1 And character_node_y > -1) Then + //Calculate target coordinates + character_target_x = character_node_x*NODE_WIDTH+NODE_WIDTH/2 + character_target_y = character_node_y*NODE_HEIGHT+NODE_HEIGHT/2 + + //Move the character + character_angle# = GetAngle(character_x,character_y, character_target_x,character_target_y) + character_x# = character_x + Cos(character_angle) * CHARACTER_SPEED + character_y# = character_y - Sin(character_angle) * CHARACTER_SPEED + + //Check If the character is close enough To the target + If Distance(character_x,character_y, character_target_x,character_target_y) < CHARACTER_SPEED Then + 'Reset target node coordinates To trigger the program To fetch New ones. + character_node_x = -1 + character_node_y = -1 + EndIf + EndIf + + //Draw the character + Color 0,255,255 + Circle character_x-3,character_y-3, 6, True + EndIf + + //Show cursor location + Color 128,0,0 + Box MouseX()/NODE_WIDTH*NODE_WIDTH,MouseY()/NODE_HEIGHT*NODE_HEIGHT, NODE_WIDTH+1,NODE_HEIGHT+1, OFF + + //Show instructions + Color 0,0,0 + Text 0,ScreenHeight()-60, "Mouse1: Place Start Mouse2: Place End Space: Place/Remove Obstacle" + If CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then Text 0,ScreenHeight()-45, "Num+: Increase single node penalty (SNP) Num-: Decrease single node penalty (SNP)" + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + Text 0, ScreenHeight()-30, "Numpad numbers: set single node penalty direction (SNPD) Num*: Increase direction penalty in SNPD" + Text 0, ScreenHeight()-15, "Num/: Decrease direction penalty in SNPD Backspace: Clear penalties from a node" + EndIf + + //Finally draw anything we have in this testing program + DrawScreen +Forever diff --git a/cbAStar Documentation.txt b/cbAStar Documentation.txt new file mode 100644 index 0000000..e5d7a89 --- /dev/null +++ b/cbAStar Documentation.txt @@ -0,0 +1,815 @@ + + B AA SSSS T R + CCC B A A S TTTTT AAAA R RR + C BBBB AAAAAA SSS T A A RR + C B B A A S T A AA R + CCC BBBB A A SSSS T AA A R + +--------------------------------------------------+ + | cbAStar version 1.0 2009-08-04 | + | Made By Jarkko 'Jare' Linnanvirta | + +--------------------------------------------------+ + +---------------------- +DOCUMENTATION CONTENTS +---------------------- +1. Introduction to cbAStar + - The algorithm +2. Guide to calculate a path + - The Pathfinding Rules + - Include cbAStar into a project + - Initialize cbAStar with a tilemap + - Initialize cbAStar with other map format + - Calculating a path +3. Guide to follow a path + - A note about multiple objects using the same path +4. Advanced features + - Use single node penalty + - Use node direction penalty + - Use move direction penalty + - Negative penalty + - Configuring advanced rules +5. The complete function reference +6. Author and license +^^^^^^^^^^^^^^^^^^^^^^ + + +-------------------------- +1. INTRODUCTION TO CBASTAR +-------------------------- + +Hello, folks! + +cbAStar is a function library written in CoolBasic which provides you an easy way to produce your game the intelligence to generate paths on a playground from point A to point B. As it stands in the library's name, cbAStar is based on the popular A* (A Star) pathfind algorithm. + +With cbAStar, you can for example: +* Implement a pathfinding system into a 2D game that is viewed from the side or from the top. +* Implement a pathfinding system to a Tilemap based playfield engine - or to your own made engine. +* Calculate a path step by step without interrupting the whole program. +* Avoid paths that would give problems to character due to gravity, enemy influence, slow moving speed or any other reason. +* Use advanced map configuration to guide which general directions your characters should go - and which directions they should not go - on a specific places on your playfield. +* Calculate paths for board game peaces. + +Even though cbAStar is called as a 'library', it does not contain any DLL files or other external libraries. The whole library is written in pure CoolBasic code and therefore can be easily kept with any project and does not require skills in other programming languages in order to be modified. Yes, every user is allowed to modify the library, but please see the chapter Author and license for more details. + + +The algorithm +------------- + +I learned the A* algorithm at the same time I developed this library. I studied a great article of the algorithm written by Patrick Lester. So many thanks to him for writing the article. Here is an address to it: + +http://www.policyalmanac.org/games/aStarTutorial.htm + +(The address was checked 2009-08-04) + +^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +---------------------------- +2. GUIDE TO CALCULATE A PATH +---------------------------- + +To get started with cbAStar, you need to have the following programs downloaded and installed onto your computer: +* CoolBasic (version Beta 10.43 (aka CoolBasic Classic). A newer version (if available) is not suitable!) + - This includes: + - CoolBasic Compiler + - CoolBasic IDE (Code Editor and Manual) + - Can be downloaded from: http://www.coolbasic.com (free of charge) +* cbAStar library (version 1.0. A newer version (if available) has it's own documentation, so please check that the version number of this documentation is the same as the version number of your cbAStar source code!) + - This includes: + - cbAStar source code + - cbAStar documentation (probably the same file you are currently reading) + - Can be downloaded from: http://cbkk.systec.fi (site in Finnish) OR from http://www.coolbasic.com/phpBB3/ (from the English Projects area). (Free of charge). + +I quess you probably have the cbAStar already because you are reading its documentation. And you should also have CoolBasic since you have been interested in downloading CoolBasic source code. + +There is no need for any other libraries. + + +The Pathfinding Rules +--------------------- + +The first thing to do is to open a file named cbAStar.cb from cbAStar's directory in your CoolBasic Editor. In the beginning of the file you see a list of pathfinding rules. With the rules you can configure how the library tries to produce a path. Some of the rules are marked as Advanced Rules and you don't have to use them right now. See chapter Advanced Features for more information about them. + +Take a look at the following rules and configure them as you want. Each rule can be set to 1 (=ON) or 0 (=OFF). + * A) Accept Collide Wall Corners, + - If ON, a path may move diagonally so that an obstacle node's corner will touch the path. + * B) Move Between Wall Corners, + - If ON, a path may move diagonally between two obstacles that are adjacent to each other by their corners. + - You are not allowed to set this rule ON, if Rule A if OFF! + * C) Move Horizontally, + - If ON, allow the path to move along the x axis. + - Do not set all of these three rules OFF: C, D and E! + * D) Move Vertically, + - If ON, allow the path to move along the y axis. + - Do not set all of these three rules OFF: C, D and E! + * E) Move Diagonally AND + - If ON, allow the path to move along the x and y axis at the same time. + - Do not set all of these three rules OFF: C, D and E! + - Can be ON even if C and D are OFF. + * I) Use Turn Penalty + - If ON, a node that is candidate to become a part of a path will have penalty if it turns off the path's current direction. The algorithm tries to select a node that has low penalty or no penalty at all. + - The given penalty can be configured in the beginning of cbAStar.cb file, after the rules list. + + +Include cbAStar Into A Project +------------------------------ + +After configuring the rules you are ready to implement the library into your project. If you do not have a suitable project for implementing the library at this point, please read still the implementation instructions here. After learning how the implementation is done, you can see the attached example programs and explore how the library is implemented in the examples. + +To include the cbAStar library into your project's compilation process, write the following code at the begining of the project's source code: + +> Include "cbAStar.cb" + +If the cbAStar.cb file is located elsewhere than where your project's source code is located, apply the correct path of the cbAStar.cb file to the Include command. After typing the command, press F4 to ensure that the library's compilation goes well. This is a critical point! The library may use same global names (for functions, global variables, constants or types) that are used in your project. In that case, the compilation will fail causing an error message. If this happens, you need to go through either the library's or your project's source code and change the global names that are mentioned in error messages given by the CoolBasic Compiler. Be careful and check that you change all names that are depending on the structure that you have already renamed. + +If you have problems that you are not able to solve at this point, please see the cbAStar topic in CoolBasic forum's English section. If you do not find an answer there, please post a description of your problem there. Or you can send me PM on the forums or send me email, but please check out the cbAStar's topic before sending questions. + +If the compilation went well, go to the section of your project where the actual game begins and a map is loaded. A map can be in the Tilemap format or in your own made format. If you use the tilemap format in your project, please goto read the chapter Initialize cbAStar With A Tilemap. Otherwise goto read the chapter Initialize cbAStar With Other Map Format. After these chapters the instructions continues the same way without depending on if you are using tilemaps or not. + + +Initialize cbAStar With A Tilemap +-------------------------------- + +In your project's source code, find the code line where a tilemap is loaded. Insert the following statement somewhere AFTER the line that loads the tilemap: + +> cbAStarInitializeUsingTilemap() + +After inserting this line, press F4 to launch the compilation process which will check that there is no errors. If the compilation process throws an error, try to find a solution for it. If you cannot get rid of the problem, please refer to the cbAStar's topic in the CoolBasic Forums. + +If the compilation goes well, the initialization is done. The initialization function does the following for us: +* Redim cbAStar's map array to cover the current tilemap. +* Copy the tilemap's hit level to the map array's obstacle level. + +Therefore the library is ready to take orders to calculate paths. You can now skip the following chapter (named Initialize cbAStar With Other Map Format) and continue reading from chapter Calculating a path. However, if you want to learn more about cbAStar's obstacles and how they are defined, reading the next chapter will give you useful information. + + +Initialize cbAStar With Other Map Format +----------------------------------------- + +In your project's source code, find the location where a map is loaded or generated. Put the following function call AFTER the code that loads or generates the map: + +> cbAStarInitialize(map_width, map_height) + +Parameters map_width and map_height state the map's dimensions measured in nodes. In general, you need a map that can somehow be diveded into rectangles. If your map is already in the form of an array, you can use the array's width and height here as parameters. If your map is for example in the form of an image, you need to decide how many nodes it will be divided into by horizontally and vertically. The term 'node' can actually mean also the same as the terms 'rectangle' or 'square'. + +Well then. How tall and how wide is one node in pixels? This is a thing that you decide your self, BUT you do not ever need to pass that information to cbAStar. That's because there is no need for the library to know any measurements related to drawing graphics on the screen or moving objects in the game world. The library just calculates routes in a relative sized array, which causes that the library's basic functionality always takes coordinates and measurements in nodes and always returns them in nodes. + +The next thing to do now is to configure the cbAStar's map's obstacle floor. This is a very freeform operation that needs you to somehow scan through your map and indicate the nodes that have such obstacles that must always be avoided when calculating a path. The following function can be used to define obstacle nodes in cbAStar: + +> SetAStarObstacle(node_x,node_y, [is_obstacle=ON]) + +This function sets the node pointed by node_x and node_y to be treated like an obstacle. The optional parameter can be set to False if you want to cancel an obstacle definition. The cancellation may become handy if the game's map changes in some situations. But the normal use of this function is to add obstacles. + +If your map is for example in the form of an image, you need to scan through the map image and read pixels from it in certain locations that you must conclude to be rational. This way you can read obstacles from the image. + +SetAStarObstacle() is a good choice if you are scanning your map in a double For...Next loop and reading the map one node at a time. But there are also two extra functions that may become handy in certain circumstances: + +> SetAStarObstacleLine(node1_x,node1_y, node2_x,node2_y, [is_obstacle=ON]) + +This function works like CoolBasic's inbuild drawing command named Line. It takes an obstacle line's starting and ending coordinates as parameters and then sets the whole line to the cbAStar's map. This is handy if you know the starting and eding points of a wall in your game's map. Then you can just pass the whole wall to the library in one function call! And as like the previous function, this function can be used to erase obstacles from cbAStar's map by setting the optional parameter to OFF. + +> SetAStarObstacleBox(node_x,node_y, width, height, [filled=OFF], [is_obstacle=ON]) + +This function is meant to define big obstacle areas that are in the form of rectangle. It has two modes: it can fill the whole area with obstacles or just define the area's borders to be obstacles. The second mode is the function's default behaviour and is handy when you are defining for example a house that is in a shape of rectangle. Just use this function and after that use SetAStarObstacle to unset obstacle in the location of the house's door. Or use this function with the filling mode ON to define unwalkable terrain such as sea, mountain or gorge. The function's four first parameters are the same as in CoolBasic's inbuild drawing command named Box: first a rectangle's upper left corner and then the rectangle's dimensions. After that you can optionally enter whether you want the rectangle to be filled or not and should the function place or remove obstacles. + +After scanning through the game's map and indicating all necessary obstacles, the library is set up correctly. + + +Calculating a Path +------------------ + +Locate a place in your game's source code where an object needs to find a path to it's destination. Then calculate the path with the following function: + +> CalculatePath(start_node_x,start_node_y, end_node_x,end_node_y, [one_node_at_time=OFF]) + +This function's only required parameters are coordinates to nodes that start and end the path. With this information the function calculates a suitable path from the start to the end. The function's default behaviour is to calculate the whole path upon one call. This way is suitable for most situations and is very easy to take control of. + +However, when talking about performance, cbAStar has a special feature to be considered in circumstances where a lot of power is needed to share with many processes at the same time and not only with path calculation. By setting the optional parameter ON, CalculatePath() only calculates one node of the path upon a call. The calculated node may or may not be one of the nodes in the final resulting path. This is why the function won't return parts of a path before the whole path is calculated. After calculating one node, the calculation will continue when the function is called again. + +When using the One Node at a Time mode (ONT mode), The starting and ending nodes' coordinates must be passed as parameters when the pathfinding starts. Next times you will also need to pass the applicable parameters - but it does not anymore matter what values they have. When called again in the ONT mode, the function ignores all parameters and continues the earlier started calculation. The thing that you must really be careful with is that if you have multiple objects that need to have calculated paths, you can only calculate ONE PATH AT A TIME! When in ONT mode, there is no way to first calculate a little bit of one path and then a little bit of another. You need to go through one calculation process before starting a new one. There is no way to cancel a process. + +What if the ONT mode is used on first few times when a path is calculated, but then set to OFF? This is a rare behaviour that maybe has no advantageous reason. But anyway, the result will be fine. The rest of the path is calculated upon the call that sets the ONT mode OFF. + +Whether you are using the ONT mode or not, you need to pay attention to the function's return values and ensure that you treat them correctly: +* A positive integer is returned when a path calculation is done successfully. The returned integer is a pointer to a MemBlock containing the calculated path. We will go into this more closely later. +* A zero (0) is returned if the ONT mode is ON and the path calculation is not yet finished. +* A negative one (-1) is returned if a path between the given coordinates cannot be formed due to obstacles that are impossible to circulate. + +To find out if the return value was a pointer to a MemBlock containing a path, you need to use the following If statement in order to do the verification correctly: + +> path = CalculatePath(2,3, 8,7) +> If path > 0 Then 'Correct +> ... + +The following way to do the verification is WRONG: + +> path = CalculatePath(2,3, 8,7) +> If path Then 'Incorrect +> ... + +The problem in the last condition is that it will have a value of True even if the return value is -1. That's because in CoolBasic all values except 0 (zero) are treated as True. This is why you need to exclude the possibility of the return value to be -1. + +To calculate a path using the ONT mode, you can use for example the following expedient: + +> Dim enemy_path 'This variable will hold a pointer to a MemBlock containing a calculated path when the calculation is complete. +> Repeat 'Game Main Loop starts +> ... 'Some game engine code here +> If enemy_path < 1 Then +> 'Enemy does not have a path to player +> path = CalculatePath(enemy_node_x,enemy_node_y, player_node_x,player_node_y, ON) 'Remember to set the last parameter to ON in order to enable the ONT mode! +> If path > 0 Then +> 'A path has just been found +> enemy_path = path +> EndIf +> EndIf +> ... 'Some more game engine code here +> Forever +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +------------------------- +3. GUIDE TO FOLLOW A PATH +------------------------- + +Now that you have got your game to the point that it can retrieve calculated paths from the library, you should notice that the main base of the library has actually done it's work and the next steps does not belong to the main part of the library. This is because when deciding plans about a programming project, limits have to be drawn somewhere not too far away from the marrow point of what for the project was originally started to be made for. However, the library would be useless without any written documentation on how to read the calculated paths. To make the library's interface more easier to approach, there is also a few functions that can be optionally used to follow and handle the resulting final paths. + +A path is stored in a MemBlock. And just for you to know: actually in that form, a path is not anymore part of the standard A* algotrithm since the algorithm does not take a stance on how the resulting path should actually be stored or presented. As was said before, the cbAStar library is designed so that its main purpose is to do the path calculation - not to actually present the resulting path in any way or to make an object to follow the path. The library just returns the path in a MemBlock. + +To be able to read the given path, you need to know its format. The format is very simple: All path nodes are stored to the MemBlock in a queue. Each node has only two attributes stored in the MemBlock: X coordinate and Y coordinate. Both are in data type of a Short Integer. After these two values either begins another node OR the MemBlock ends meaning that the path ends also. + +To read the path, you can write your source code based on this example: +> path = CalculatePath(2,3, 8,7) +> path_size = MemBlockSize(path) +> For i = 0 To path_size-4 Step 4 +> node_x = PeekShort(path, i) +> node_y = PeekShort(path, i+2) +> Next i + +This can sound confusing, but please read this documentation ahead. There is another way to do this for those who prefer to get this done easier or does not have learned how to use MemBlocks. + +A secondary purpose of this library is to give a little bit easier way to read a path returned by the library. There is a function called FollowPath() which takes a path MemBlock as a parameter. The function returns coordinates of the next node of the given path in global variables FollowPathX and FollowPathY. To keep track on which node are we currently going on, the function also makes a little modification to the path's MemBlock: it resizes it and stores at the end of the MemBlock an index of the current node. + +You need to consider the modification made by this function, but you do not need to worry about it. If you use FollowPath() and some of your own made code that goes through the same path MemBlock in a For...Next loop described above, you should be aware that the path's MemBlock in the For...Next loop may also contain an index value written by FollowPath(). Well, usually this is not a problem because the index value is a Short Integer so it increases the MemBlock's size by two bytes. The For...Next loop stated above will never read the index because after reading all of the path's nodes, it actually jumps over the index value and ends the loop without ever reading the index value. So the index value should not ever disturb any process that reads node coordinates from a path's MemBlock. + +But if you for some reason use a For...Next loop with any other Step value than 4, it may be possible for the index value to appear into the For...Next loop. This is why it is strongly recommended to always use a Step value of 4 when reading nodes from a path's MemBlock. This is mentioned just for that you know that if you will ever come up with a strange value coming from the end of some path's MemBlock, you know that it's just an index value written there by FollowPath(). + + +A Note About Multiple Objects Using A Same Path +----------------------------------------------- + +If you are using FollowPath() to read paths, you should always make every object to keep an own copy of their path. If the objects are in different positions on the same path AND are using actually the same path MemBlock, it will confuse FollowPath() and produce chaos. Therefore use CloneMemBlock command to create a unique copy of a path to each object that uses the path. CloneMemBlock command can also be used in any other situations too - and it does not break anything inside the cbAStar library. +^^^^^^^^^^^^^^^^^^^^^^^^^ + + +-------------------- +4. ADVANCED FEATURES +-------------------- + +While cbAStar should produce paths that look good in theory, there may be a number of matters that makes it so that a shortest way is not always the best way. In your game there may be for example the following elements that restricks movements: + * Gravity: A side viewed game has platforms which means that moving from a lower platform to an upper platform will require that vertical movement through open air (by jumping) is avoided as much as possible. Also in some cases when moving from an upper platform to a lower platform, large drops must be avoided in order to conserve the moving obstact's life. + * Hills: Moving uphill is much slower than moving downhill. That's why paths should prefer downhills and avoid uphills. + * Terrain with limited speed: A path should circulate areas that limit the object's speed too much. In the other hand, if the circulation would be very large compared to the trip that would be travelled through a limited speed terrain, the path should still take a shortcut through the terrain. + * Enemy influence: If a group of enemies is guarding a portion of a desired path's travelling area, the area should be circulated in order to make a safe journey. + +There are probably a lot more situations where a pathfinding algorithm should not take the shortest way. The simpliest way to avoid the issues stated above would be to define the involved nodes as obstacles. But that would only lead the pathfinding algorithm into other problems and raise the change of impossible paths dramatically. + +In cbAStar, there are three Advanced Rules that can be used to manage quite a mass of cases like the ones stated above. Using any of the rules reserves more system memory, which is why the rules are not used by default. In order to use the rules, you must open cbAStar.cb file into the CoolBasic's editor and apply the rules from the beginning of the file. They can be found easily through inline comments. + +The Advanced Rules are: + * Use Single Node Penalty + * Use Node Direction Penalty + * Use Move Direction Penalty + +In the next chapters we will consider what each of the rules do. The actual instructions to use each of the rules is presented afterwards in chapter Configuring advanced rules. + + +Use single node penalty +----------------------- + +Each node can be given a simple penalty. The pathfinding algorithm will prevent using the node if its penalty is too high in relation with adjacent nodes. The penalty can be given for any reason - for example an enemy influence. + + +Use node direction penalty +-------------------------- + +While a node can have all-round penalty (as stated in the previous chapter), there is a way to define a more conditional penalty. A node can be given extra penalty if it is in a special direction when coming from a path's previous node. + +The direction can be measured by integer numbers from 0 to 7. The directions are: + * 0: Direction to east + * 1: Direction to north east + * 2: Direction to north + * 3: Direction to north west + * 4: Direction to west + * 5: Direction to south west + * 6: Direction to south + * 7: Direction to south east + +The direction format may sound confusing, but it has a logic if you compare it to your keyboard's numpad: direction 0 is the key Num6, direction 1 is the key Num9 and next directions goes counter-clockwise. Why not use a degree or radian based direction? Because that would give too much direction that could not be treated separately. When looking from the aspect of nodes, one node can have eight neighbours, which is the base idea for node directions. + +A node can hold information about penalty of one direction (the direction value and the penalty amount). Multiple directions per node cannot be defined. + +A direction based penalty will make the pathfinding algorithm to avoid approaching the intended node if it's in a certain direction. However, moving into the node is not avoided when the node is in other direction related to a previous node. + +IMPORTANT NOTE! In order to use Node Direction Penalty, the rule Single Node Penalty must be set ON too! Otherwise the library's memory handler will fail causing Memory Access Violation error message at some stage of a compiled program. + + +Use move direction penalty +-------------------------- + +This rule is like the one above, but much simplier. You can give penalty separately to all of the directions you want - and the penalty is applied everytime when the pathfinding algorithm tries to make a path move towards any of the intended directions. The penalty amount may even differ between the directions! + + +Negative penalty +---------------- + +Data type of a penalty amount is always integer. Based on this point, you can also give negative penalty. This way you can make the pathfinding algorithm to prefer certain nodes or directions. This is a very good way to produce roads that are more easier to move on than their environment. This affects to the performance also: it makes much less load for the CPU if you give negative penalty to a road than if you scan through all of the road's environment giving non-road nodes positive penalty. + + +Configuring advanced rules +-------------------------- + +Move direction penalty has a constant effect and it is configured only by setting the rule on and giving each direction a penalty value in cbAStar.cb source code file's configuration part. + +Rules Single Node Penalty and Node Direction Penalty can be enabled in the cbAStar.cb source code file. Configuring the rules goes through your implementation by using mainly a function named SetAStarPenalty(). Statement of the function is as follows: + +> SetAStarPenalty(node_x,node_y, single_node_penalty, single_node_penalty_direction, single_node_penalty_direction_amount) + +Parameters of the function: + * X and Y coordinates define a node whose penalties will be changed. + * single_node_penalty: Set penalty amount that the node will give to the path if the pathfinding algorithm selects the node into a path. + * single_node_penalty_direction: Penalty direction to be set to the node. If the algorithm selects this node into a path and the path comes to this node in this direction, the path will have extra penalty defined in the next parameter. + * single_node_penalty_direction_amount: Relating to the previous parameter, this parameter sets the amount of the penalty. + +The function does not return any values. Use the function to define all nodes that you wish to apply any penalties. + +If you want to skip some of the penalty parameters, leave them unset and their values in the node wont be changed. The same behaviour can be achieved if you use a constant named CBASTAR_DEFAULT in the penalty parameters that you do not wish to change. + +You should also refer The Function Reference for functions called SetAStarPenaltyBox() and SetAStarPenaltyLine(). Those functions work the same way as SetAStarPenalty(), but they can define multiple nodes with the same penalties at the same time. + +Now you know all the advanced features. With them you are able to produce a very versatile pathfinder for your game's artificial intelligence. The system is open for wide use. +^^^^^^^^^^^^^^^^^^^^ + + +---------------------------------- +5. THE COMPLETE FUNCTION REFERENCE +---------------------------------- + +This chaper contais all functions defined in the cbAStar library. Functions can have the following markings: +* [MF] - (Main Function) This function belongs to the main part of the library meaning that you are probably going to need to call this function. +* [OF] - (Optional Function) This function belongs to the optinal part of the library meaning that it does side tasks that are not required in all implementations. This does not mean that it can be left out from the compilation process since the library itself may need it. +* [IL] - (Inside Library) Function is meant to be used only inside the library - using it in your implementation is not recommended! +* [RC] - (Required to Call) This function is needed to be called atleast once for the library to work correctly. +* [RC*]- Same as [RC], but can be replaced with another function marked with [RC*]. +* [AU] - (for Advanced Use only) This function is needed only if used The Advanced Rules of the library. + +List of all functions (excluding [IL] functions): +------------------------------------------------- +CalculatePath() [MF] [RC] +cbAStarInitialize() [MF] [RC*] +cbAStarInitializeUsingTilemap() [MF] [RC*] +CoordinateChangeToDirection() [OF] [AU] +DirectionToCoordinateChange() [OF] [AU] +FollowPath() [OF] +GetAStarObstacle() [OF] +GetAStarPenalty() [OF] [AU] +LoadAStarMap() [OF] +ResetFollowPath() [OF] +ReversePath() [OF] +SaveAStarMap() [OF] +SetAStarObstacle() [MF] +SetAStarObstacleLine() [MF] +SetAStarObstacleBox() [MF] +SetAStarPenalty() [OF] [AU] +SetAStarPenaltyLine() [OF] [AU] +SetAStarPenaltyBox() [OF] [AU] + +List of [IL] functions: +----------------------- +AddNodeToClosedList() +AddNodeToOpenList() +CalculateGCost() +CalculateHCost() +CheckAdjacentNodes() +CheckIfNodeCouldGetBetterParent() +CreateNode() +FindNodeWithLowestPathScore() +GatherPath() +IsOpenListEmpty() +IsWallBetweenNodes() +RemoveNodeFromOpenList() +ResetPath() + + +Function manuals: +----------------- + +IL functions does not yet have documentation. If you need them, see their comments in the cbAStar.cb file. + +CalculatePath() [MF] [RC] +------------------------- + +Statement: path = CalculatePath(start_x,start_y, end_x,end_y, [mode=OFF]) + +Parameters: + * start_x, start_y: Coordinates from where the pathfinding should start. + * end_x, end_y: Coordinates to where the pathfinding should get. + * [mode=OFF]: If OFF, the whole path will be calculated at once. If ON, only one node of the path will be calculated and the calculation will be continued next time. + +Return values: + * Positive Integer: A pointer to a MemBlock containing the calculated map, OR + * 0 (Integer Zero): Means that the calculation has not yet finished (only when mode parameter is 1). + * -1 (Negative One): The calculation has ended without results because there is no path between the starting node and the ending node. + +Description: +This function does the actual path calculation. + +Notes: +All coordinates must be presented in node coordinate form - not in World coordinate form or Screen coordinate form! The node coordinate form is equal to tile coordinate form (if you are using tilemaps). + +When checking if the return value is a path, DO NOT USE THE FOLLOWING STATEMENT: + +> If path Then 'Wrong +> ... + +This is because the possible return value can also be -1, which equals to a true condition. Always use the following statement to correctly find out if the function has successfully calculated a path: + +> If path > 0 Then 'Right +> ... + + +cbAStarInitialize() [MF] [RC*] +------------------------------ + +Statement: cbAStarInitialize(map_width,map_height) + +Parameters: + * map_width, map_height: dimensions of your playfield measured in nodes. + +Return values: + * None + +Description: +This function is needed to be called at an early stage for the library to work correctly. It resizes the library's own map array and therefore makes it possible to setup a map configuration and perform path calculations. + +Notes: +If you are using tilemaps in your game, please use cbAStarInitializeUsingTilemap() instead of this function. + +This function can be used with tilemaps too, but the one stated above makes some additional tasks related to tilemaps so that it is more comfortable with tilemaps than this function. + +Use this function especially when you are using your own format for playfields. + + +cbAStarInitializeUsingTilemap() [MF] [RC*] +------------------------------------------ + +Statement: cbAStarInitializeUsingTilemap() + +Parameters: + * None + +Return values: + * None + +Description: +This function is needed to be called at an early stage for the library to work correctly. It resizes the library's own map array and therefore makes it possible to setup map configuration and perform path calculations. + +It takes the map's width and height value directly from a loaded tilemap so those values do not need to be passed as parameters. The function also scans the tilemap and copies it's obstacle floor to cbAStars own map array. In the other words: After calling this function, your tilemap is ready for path calculations. + +Notes: +You need to have a tilemap loaded into your game before calling this function! + + +CoordinateChangeToDirection() [OF] [AU] +--------------------------------------- + +Statement: direction = CoordinateChangeToDirection(x_change,y_change) + +Parameters: + * x_change: change in a x coordinate. Actual value can be -1, 0 OR 1. + * y_change: change in a y coordinate. Actual value can be -1, 0 OR 1. + +Return values: + * A direction value from 0 to 7 (like in keyboard's numpad): + - 0: Direction to east + - 1: Direction to north east + - 2: Direction to north + - 3: Direction to north west + - 4: Direction to west + - 5: Direction to south west + - 6: Direction to south + - 7: Direction to south east + +Description: +This function is used when giving direction based penalty to a node. This function returns the right direction number when it is correctly given coordinate changes that describe a direction. + + +DirectionToCoordinateChange() [OF] [AU] +--------------------------------------- + +Statement: +> x_change_and_y_change$ = DirectionToCoordinateChange(direction) +> x_change = GetWord(x_change_and_y_change,1) +> y_change = GetWord(x_change_and_y_change,2) + +Parameters: + * A direction value from 0 to 7. See function CoordinateChangeToDirection() for more information about the direction. + +Return values + * A string containing x_change and y_change separated by one space character ( " " ). Use GetWord() function to separate the values. + +Description: +Converts a direction value used in Node Direction Penalty to a relative coordinate change measured in X and Y axis. + + +FollowPath() [OF] +----------------- + +Statement: succeeded = FollowPath(path) + +Parameters: + * path: A MemBlock containing a path that should be followed + +Return values: + * True: Next node of the given path was succesfully read and its coordinates are written in the following global variables: + - FollowPathX + - FollowPathY + * False: The path has ended. + +Description: +Reads a next node from the given path and returns its coordinates. If the path has ended, returns False. + +Notes: +The function makes a little modification to the MemBlock containing the path: the function increases the MemBlock's size by two bytes and stores there an index of a next node. You should consider this if you write code that reads paths directly from their MemBlocks. + + +GetAStarObstacle() [OF] +----------------------- + +Statement: is_obstacle = GetAStarObstacle(node_x,node_y) + +Parameters: + * X and Y coordinates for the node that should be viewed. + +Return values: + * True or False depending if the given node contains an obstacle. + +Description: +This function can be used to check if a node has been defined as an obstacle or not. + + +GetAStarPenalty() [OF] [AU] +--------------------------- + +Statement: penalty = GetAStarPenalty(node_x,node_y, [penalty_type=0]) + +Parameters: + * X and Y coordinates for the node that should be viewed. + * Optional parameter penalty_type: + - 0: Check Single Node Penalty [Default] + - 1: Check Single Node Direction Penalty (direction) + - 2: Check Single Node Direction Penalty (amount) + +Return values: + * Depends on given penalty_type: + - If penalty_type = 0: Basic Single Node Penalty amount. Can be negative or positive integer or zero. + - If penalty_type = 1: Direction, value from 0 to 7. + - If penalty_type = 2: Penalty amount given for a special direction. + +Description: +Use this function if you need to know if a node has penalties applied for it. + +Notes: +cbAStar library does not automatically apply any penalties, so if you are not using the features, you do not need to worry about any penalties and there is no need for using this function. + + +LoadAStarMap() [OF] +------------------- + +Statement: succeeded = LoadAStarMap(file_path$, [compatibility_mode=OFF]) + +Parameters: + * file_path$: A string of a file path to be loaded. + * compatibility_mode: If ON, continue loading the file even if it has different amount of advanced features used in it. If OFF, stop loading and return False if the file has different amount of features. Default is OFF. + +Return values: + * 0 (False): Loading failed because the map file does not exist, is in wrong format or has a different amount of advanced features than cbAStar has currently configured to use. + * 1 (True): Loading succeeded and the map file had just the same amount of advanced features that the library has configured to use. + * 2: Loading succeeded but the map file had different amount of features than the current configuration. The map can still be used, but there may be some problems relating to the advanced features. This value can only be returned when the compatibility mode if set to ON. + +Description: +This function loads a previously saved cbAStar's map details. This gives you another way to initialize cbAStar when using your own map format. Though this function is also accessible if you use tilemaps. The main benefit of this function is achieved in situations where generating the map details (obstacles and optional penalties) from scratch would consume too much resources. + +Notes: +To ensure that the loadable map is in an absolutely correct shape, keep the compatibility mode OFF. If the loadable map has different amount of advanced features used in it than cbAStar is currently configured to use, the compatibility mode ignores the differences between the advanced features which may lead to unexpected behaviour of the path finding algorithm. In most common situtations related to applications that are currently in production, the compatibility mode is not needed. + +When an application is still in development process, there is a possibility that the library's configuration will be changed by the programmer who uses the library. In this case, the compatibility mode can be handy when set to ON. But when you are releasing a version of your application to the public, consider the recommendation to turn OFF the compatibility mode. + + +ResetFollowPath() [OF] +---------------------- + +Statement: ResetFollowPath(path) + +Parameters: + * path: A pointer to a MemBlock containing the desired path. + +Return values: + * None + +Description: +If you have used FollowPath() function for a certain path, you can discard the path MemBlock's modifications made by FollowPath() by using this function. The effect in practice is that the MemBlock's size is cutted to it's original dimension and if you continue using FollowPath() with the same path, The path will be followed again from its begining. + +Notes: +If the path was newer passed to FollowPath() but it is still passed to ResetFollowPath(), this function does not make any changes to the path MemBlock. This means that the function is very secure and does not always cut a MemBlock's size. + + +SaveAStarMap() [OF] +------------------- + +Statement: SaveAStarMap(file_path$) + +Parameters: + * file_path$: A string containing a file path where the current cbAStar's map details should be saved. + +Return values: + * None + +Description: +This function saves details of the cbAStar's map array into a file. It saves all obstacles and penalties. + +Notes: +BE CAREFUL with this function! If the given file exists, it will be overwritten! + +Size of the saved file grows if the library is configured to use its advanced features. + + +SetAStarObstacle() [MF] +----------------------- + +Statement: SetAStarObstacle(node_x,node_y, [is_obstacle=ON]) + +Parameters: + * X and Y coordinates to indicate the desired node. + * Optional is_obstacle parameter: + - ON: Place an obstacle (default) + - OFF: Remove an obstacle + +Return values: + * None + +Description: +With this function you can place obstacles to the map. The pathfinding algorithm will circulate these obstacles. + + +SetAStarObstacleBox() [MF] +-------------------------- + +Statement: SetAStarObstacleBox(node_x,node_y, width,height, [filled=False], [is_obstacle=ON]) + +Parameters: + * X, Y, width and height defines a rectangle in the cbAStar's map. + * Optional parameter filled: + - True: Fill the whole rectangle. + - False: Only fill the rectangle's borders. (default) + * Optional parameter is_obstacle: + - ON: Place obstacles (default) + - OFF: Remove obstacles + +Return values: + * None + +Description: +Use this function to define a large area as obstacles - or remove obstacles from a large area. + +Keep the filling OFF if you are for example creating a house whose walls must be considered as obstacles. Then Use SetAStarObstacle() function to put little holes to the obstacle walls in order to allow access to the house through doors. + +Set the filling ON if you are creating an unwalkable area such as sea, mountain or gorge. + + +SetAStarObstacleLine() [MF] +--------------------------- + +Statement: SetAStarObstacleLine(node1_x,node1_y, node2_x,node2_y, [is_obstacle=ON]) + +Parameters: + * node1_x and node1_y: Define a starting node for the obstacle line. + * node2_x and node2_y: Define an ending node for the obstacle line. + * Optional parameter is_obstacle: + - ON: Place obstacles (default) + - OFF: Remove obstacles + +Return values: + * None + +Description: +Use this function to define a line of obstacles (or remove them from a line). This is a really nice solution to define a single, long wall to be an obstacle. + + +SetAStarPenalty() [OF] [AU] +--------------------------- + +Statement: SetAStarPenalty(node_x,node_y, single_node_penalty, single_node_penalty_direction, single_node_penalty_direction_amount) + +Parameters: + * X and Y coordinates define a node whose penalties will be changed. + * single_node_penalty: Set penalty amount that the node will give to the path if the pathfinding algorithm selects the node into a path. + * single_node_penalty_direction: Penalty direction to be set to the node. If the algorithm selects this node into a path and the path comes to this node in this direction, the path will have extra penalty defined in the next parameter. + * single_node_penalty_direction_amount: Relating to the previous parameter, this parameter sets the amount of the penalty. + +Return values: + * None + +Description: +With this function you can give penalties for a single node. Read more about the penalties in chapter Advanced features. + +Notes: +All the penalty parameters are optional. If you leave some of them unset, their values in the node wont be changed. The same behaviour can be achieved if you use a constant named CBASTAR_DEFAULT in the penalty parameters that you do not wish to change. + + +SetAStarPenaltyBox() [OF] [AU] +------------------------------ + +Statement: SetAStarPenaltyBox(node_x,node_y, width,height, filled, single_node_penalty, single_node_penalty_direction, single_node_penalty_direction_amount) + +Parameters: + * X and Y coordinates and width and height dimension define a node rectangle area whose penalties will be changed. + * filled: + - ON: Fill the whole rectangle. + - OFF: Only fill the borders. + * single_node_penalty: Set penalty amount that the node will give to the path if the pathfinding algorithm selects the node into a path. + * single_node_penalty_direction: Penalty direction to be set to the node. If the algorithm selects this node into a path and the path comes to this node in this direction, the path will have extra penalty defined in the next parameter. + * single_node_penalty_direction_amount: Relating to the previous parameter, this parameter sets the amount of the penalty. + +Return values: + * None + +Description: +With this function you can give penalties for an node area. Read more about the penalties in chapter Advanced features. + +Notes: +All the penalty parameters are optional. If you leave some of them unset, their values in the node wont be changed. The same behaviour can be achieved if you use a contants named CBASTAR_DEFAULT in the penalty parameters that you do not wish to change. + + +SetAStarPenaltyLine() [OF] [AU] +------------------------------- + +Statement: SetAStarPenaltyLine(node1_x,node1_y, node2_x,node2_y, single_node_penalty, single_node_penalty_direction, single_node_penalty_direction_amount) + +Parameters: + * X and Y coordinates define the starting and ending nodes for a node line whose penalties will be changed. + * single_node_penalty: Set penalty amount that the node will give to the path if the pathfinding algorithm selects the node into a path. + * single_node_penalty_direction: Penalty direction to be set to the node. If the algorithm selects this node into a path and the path comes to this node in this direction, the path will have extra penalty defined in the next parameter. + * single_node_penalty_direction_amount: Relating to the previous parameter, this parameter sets the amount of the penalty. + +Return values: + * None + +Description: +With this function you can give penalties for nodes that are in a line. Read more about the penalties in chapter Advanced features. + +Notes: +All the penalty parameters are optional. If you leave some of them unset, their values in the node wont be changed. The same behaviour can be achieved if you use a contants named CBASTAR_DEFAULT in the penalty parameters that you do not wish to change. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +--------------------- +6. AUTHOR AND LICENCE +--------------------- + +The cbAStar library (version 1.0) is made by me, Jarkko Linnanvirta, in 2009. Everything in the library is made by me - except the media files located in Example Media directory. Those files are included in the library only for the needs of the Tilemap Example Program provided also withing the library. So this license affects anything in the library except the files in the Example Media directory. + +A user is allowed to use, modify and distribute the cbAStar library but not allowed to directly sell the library in any way. An exception to this is that when the library is a part of a bigger, commercial application aimed for end users (for example game players BUT NOT third party game/application developers), it can be sold at the same time WITH the master application (but not as a commercial plugin or a separate product) if the product is NOT SOLD OR DELIVERED WITH the library's source code. + +When distributing the library, a user does not have to mention the original author, but he or she is not allowed to claim that he or she (or someone else other than the real original author) is the original author of the library. + +The library can be distributed with some parts of it ripped off and/or with some new parts taken in. + +This means: You can distribute the library as a source code (even if you have modified it) if you are not selling it. If you sell the library, it must be distributed within a game or application that is a compiled executable (not anymore in a source code) meant for the end users and not for redevelopment by the customers that it is being sold to. + + +The cbAStar logo +---------------- + +The above license applies also to the file named cbAStar_Logo.png, which is cbAStar's logo. The following additional license conditions applies to the logo. + +The logo is meant to be used in games that uses cbAStar. It is not required to be used, but it can give a game a little more professional look. The logo can be used only inside the game itself, on the game's website and/or the game's brochure without depending of publishing methods. It is not allowed to use the logo without a composite game or with a game that does not use the cbAStar library. + +The logo can be modified freely. If it is modified so strongly that it cannot be anymore connected to the original (and OFFICIAL) logo, the above license paragraph does not anymore apply to the modified logo. But you should keep this in mind when modifying the logo: people recognizes better the original logo. Modifying it too much can take off all of its benefits. Therefore the main idea for modifying the logo is that it can be made to more better suit any background image, color or menu that it will be used on. Little modifications can make the logo more better suit together with your game. + +What are the benefits of the logo? Well, not much. But it gives you a simple way to make a professional first glance to your game when a player launches it. Thus it tells the player that the game has an intelligent pathfinding algorithm that makes computer controlled characters and objects to make wise decisions about their moving routes. And it gives honour to the author of cbAStar (that means me). :D + + +Author +------ + +If you have anything to ask, please find me from one of the following addresses: + +CoolBasic's forums: http://www.coolbasic.com/phpBB3/viewtopic.php?f=18&t=1766 (the topic is in the English side. If you speak Finnish, you can also find me from the Finnish side, but I have currently no plans on creating a topic there for cbAStar since the English side should serve it alone well enough) + +Send PM (Personal Message) to me on the CB Forums: http://www.coolbasic.com/phpBB3/memberlist.php?mode=viewprofile&u=92 (my nick name there is 'Jare') + +My email: jare1@mbnet.fi (Not yet! Send me email only if you haven't found answer from the forums stated above. Thanks.) + +P.S. Sorry for my bad English :-) + +With regards, Jarkko Linnanvirta \ No newline at end of file diff --git a/cbAStar.CB b/cbAStar.CB new file mode 100644 index 0000000..eb14c7a --- /dev/null +++ b/cbAStar.CB @@ -0,0 +1,681 @@ +//cbAStar +//Version 1.0 | 2009-08-04 +//Made by: Jarkko 'Jare' Linnanvirta 2009 +//This program is based on the following great article: http://www.policyalmanac.org/games/aStarTutorial.htm +//License: Read cbAStar Documentation.txt file for the license. + +//Constant words +Const CBASTAR_DIRECTION_EAST = 0 +Const CBASTAR_DIRECTION_NORTH_EAST = 1 +Const CBASTAR_DIRECTION_NORTH = 2 +Const CBASTAR_DIRECTION_NORTH_WEST = 3 +Const CBASTAR_DIRECTION_WEST = 4 +Const CBASTAR_DIRECTION_SOUTH_WEST = 5 +Const CBASTAR_DIRECTION_SOUTH = 6 +Const CBASTAR_DIRECTION_SOUTH_EAST = 7 +Const CBASTAR_DEFAULT = "Default" + +//**************************************\\ +//BEGIN OF THE LIBRARY'S MODIFIABLE PART\\ + +//Pathfinding Rules +'[1 means TRUE/ON and 0 means FALSE/OFF.] +Const CBASTAR_RULE_ACCEPT_COLLIDE_WALL_CORNERS = 0 'A While moving diagonally, can be moved If path goes beside a wall corner? +Const CBASTAR_RULE_MOVE_BETWEEN_WALL_CORNERS = 0 'B While moving diagonally, can be moved If path goes between two walls corners? +Const CBASTAR_RULE_MOVE_HORIZONTALLY = 1 'C Can be moved If only X coordinate is increasing Or decreasing? +Const CBASTAR_RULE_MOVE_VERTICALLY = 1 'D Can be moved If only Y coordinate is increasing Or decreasing? +Const CBASTAR_RULE_MOVE_DIAGONALLY = 1 'E Can be moved so that X And Y coordinates increase Or decrease at the same Time? +Const CBASTAR_RULE_USE_SINGLE_NODE_PENALTY = 0 'F * Avoid nodes that have separately marked extra penalty? Set To 1 only If you are using this feature And you know what you are doing. Keeping this ON without reason consumes futile memory. +Const CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY = 0 'G * A node can be configured so that it will have extra penalty If it was approached from a specified direction. +Const CBASTAR_RULE_USE_MOVE_DIRECTION_PENALTY = 0 'H * Give penalty always when moving To specified directions? +Const CBASTAR_RULE_USE_TURN_PENALTY = 1 'I Give penalty when changing direction? +' Character * means that the rule is an Advanced Rule. See more about them below. + +//ILLEGAL RULE SETS: +' A = 1 AND E = 0 (will not make any sense, though does not actually make anything broken) +' A = 0 AND B = 1 (no sense. Actual behaviour would be like B = 0) +' C = 0 AND D = 0 AND E = 0 (will make the algorithm to not find any paths) +' F = 0 AND G = 1 (WILL CAUSE MEMORY ACCESS VIOLATION or some other error because it will not reserve enough memory.) +'[Prevent using any of the rule sets described above!] + +//ABOUT ADVANCED RULES (marked above with * ) +'Advanced rules are used to make cbAStar prefer paths that are better for the moving character/object because of a more demanding environment including +'for example gravity, influence made by enemies, or something else subject in a special location or direction. +'Using advanced rules requires more configuration and will also consume more system memory. + +//Adjustment for some rules +Dim CbAStarMoveDirectionPenalty(0) As Short +If CBASTAR_RULE_USE_MOVE_DIRECTION_PENALTY Then + 'Configure how much each move direction will have penalty + ReDim CbAStarMoveDirectionPenalty(7) + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_EAST) = 0 + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_NORTH_EAST) = 0 + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_NORTH) = 0 + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_NORTH_WEST) = 0 + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_WEST) = 0 + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_SOUTH_WEST) = 0 + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_SOUTH) = 0 + CbAStarMoveDirectionPenalty(CBASTAR_DIRECTION_SOUTH_EAST) = 0 +EndIf +If CBASTAR_RULE_USE_TURN_PENALTY Then + 'Configure how much To get penalty when changing direction + Global CbAStarTurnPenalty : CbAStarTurnPenalty = 5 +EndIf + + +//END OF THE LIBRARY'S MODIFIABLE PART\\ +//************************************\\ +'The license ALLOWS you to freely modify also the rest of this file (and distribute it after modifications), but if you are new to this library, it is NOT RECOMMENDED. + +//Specifications +Const CBASTAR_VERSION = 1.0 + +//Global Variables +Global CbAStarMapWidth, CbAStarMapHeight +Global CbAStarMapDepth 'Depth is used To know what features will be added To cbAStar's map array And what size it will be. +Global CbAStarStartX, CbAStarStartY +Global CbAStarEndX, CbAStarEndY +Global CbAStarIsCalculating +Global FollowPathX, FollowPathY + +//Types +Type CbAStarOpenList + Field id + Field node + Field node_parent +EndType + +Type CbAStarClosedList + Field id + Field node + Field node_parent +EndType + +Type CbAStarNode + Field node +EndType + +//Arrays +Dim CbAStarMap(1,1,1) +If CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then + //Resize the array For more features If needed + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + CbAStarMapDepth = 4 + 'ReDim CbAStarMap(CBASTAR_MAP_W,CBASTAR_MAP_H,4) + Else + CbAStarMapDepth = 2 + 'ReDim CbAStarMap(CBASTAR_MAP_W,CBASTAR_MAP_H,2) + EndIf +Else + CbAStarMapDepth = 1 +EndIf +'Last dimension: 0 = node pointer (integer); 1 = is obstacle? (boolean); 2 = single node penalty (RULE F); 3 = penalty direction {0,1,2,3,4,5,6 OR 7} (RULE G); 4 = penalty direction amount (RULE G) + + +//Functions + +Function cbAStarInitialize(map_width, map_height) + CbAStarMapWidth = map_width + CbAStarMapHeight= map_height + ReDim CbAStarMap(CbAStarMapWidth-1,CbAStarMapHeight-1,CbAStarMapDepth) +EndFunction + +Function cbAStarInitializeUsingTilemap() + cbAStarInitialize(MapWidth(),MapHeight()) + 'Copy Tilemap's collision floor To CbAStarMap's obstacle floor + For x = 0 To CbAStarMapWidth-1 + For y = 0 To CbAStarMapHeight-1 + CbAStarMap(x,y,1) = GetMap2(2,x,y) + Next y + Next x +EndFunction + +Function CalculatePath(start_x,start_y, end_x,end_y, mode=0) + //Main Function To calculate a path. + //Return values: + // Positive integer = Ponter to a MemBlock containing the calculated path. + // 0 (False) = Path calculation is Not yet ready (this can be returned only If MODE is set To 1). + // -1 (Negative 1) = There is no possible path between the start point And the End point. + //mode = 0 (calculate whole path at once), 1 (calculate only one node And continue when Function is called again) + + 'Setup common variables + If Not cbAStarIsCalculating Then + CbAStarStartX = start_x : CbAStarStartY = start_y + CbAStarEndX = end_x : CbAStarEndY = end_y + EndIf + + Repeat + If CbAStarIsCalculating Then + 'Continue previous calculation + + 'Find a node with lowest path score + node = FindNodeWithLowestPathScore() + Else + 'Start New calculation + CbAStarIsCalculating = True + + 'Create starting node + node = CreateNode(start_x,start_y) + AddNodeToOpenList(node,0) + EndIf + + If node <> False Then + ol.CbAStarOpenList = ConvertToType(PeekInt(node,10)) + AddNodeToClosedList(node,ol\node_parent) + node_x = PeekShort(node,0) + node_y = PeekShort(node,2) + CheckAdjacentNodes(node_x,node_y) + EndIf + + is_path_found = (node_x = CbAStarEndX And node_y = CbAStarEndY) + is_path_impossible = IsOpenListEmpty() + Until is_path_found Or is_path_impossible Or mode=1 + + 'Return found path + If is_path_found Then + path = GatherPath() + ReversePath(path) + ResetPath() + Return path + EndIf + + 'Return error If path cannot be found + If is_path_impossible Then + ResetPath() + Return -1 + EndIf + + 'Return zero If path is still being calculated + Return 0 + +EndFunction + +Function CreateNode(x,y) + node = MakeMEMBlock(14) + PokeShort node, 0, x + PokeShort node, 2, y + PokeByte node, 4, CbAStarMap(x,y,1) 'Is this node an obstacle? + CbAStarMap(x,y,0) = node + n.CbAStarNode = New(CbAStarNode) + n\node = node + Return node +EndFunction + +Function AddNodeToOpenList(node, node_parent) + list = PeekByte(node,5) + If list = 1 Then Return CheckIfNodeCouldGetBetterParent(node, node_parent) + If list Then Return False 'Do Not add the node If it already belongs To the open Or closed list. + ol.CbAStarOpenList = New(CbAStarOpenList) + ol\id = ConvertToInteger(ol) + ol\node = node + ol\node_parent = node_parent + PokeByte node, 5, 1 + PokeInt node, 10, ol\id + + 'Calculate node scores + If node_parent Then 'Start node does Not have a parent, so skip it (it will have score values 0 [zero], but it does Not matter) + node_x = PeekShort(node, 0) + node_y = PeekShort(node, 2) + node_parent_x = PeekShort(node_parent, 0) + node_parent_y = PeekShort(node_parent, 2) + node_parent_g_cost = PeekShort(node_parent, 6) + PokeShort node, 6, CalculateGCost(node_x,node_y, node_parent_x,node_parent_y, node_parent_g_cost) + PokeShort node, 8, CalculateHCost(node_x,node_y, CbAStarEndX,CbAStarEndY) + EndIf +EndFunction + +Function AddNodeToClosedList(node, node_parent) + list = PeekByte(node,5) + If list = 2 Then Return False 'Don Not add the node If it already belongs To the closed list. + If list = 1 Then RemoveNodeFromOpenList(node) + cl.CbAStarClosedList= New(CbAStarClosedList) + cl\id = ConvertToInteger(cl) + cl\node = node + cl\node_parent = node_parent + PokeByte node, 5, 2 + PokeInt node, 10, cl\id +EndFunction + +Function RemoveNodeFromOpenList(node) + ol.CbAStarOpenList = ConvertToType(PeekInt(node,10)) + Delete ol + PokeByte node, 5, False +EndFunction + +Function CheckAdjacentNodes(center_x,center_y) + node_parent = CbAStarMap(center_x,center_y,0) + + For x = Max(center_x-1,0) To Min(center_x+1,CbAStarMapWidth-1) + + For y = Max(center_y-1,0) To Min(center_y+1,CbAStarMapHeight-1) + + If x <> center_x Or y <> center_y Then + 'This section goes through all adjacent nodes + + 'Check out rules: + If Not CbAStarMap(x,y,1) Then 'Do Not move into obstacles + + If CBASTAR_RULE_MOVE_HORIZONTALLY Or (x = center_x Or y <> center_y) Then 'Do Not move horizontally If denied + + If CBASTAR_RULE_MOVE_VERTICALLY Or (y = center_y Or x <> center_x) Then 'Do Not move vertically If denied + + If CBASTAR_RULE_MOVE_DIAGONALLY Or (x=center_x Xor y=center_y) Then 'Do Not move diagonally If denied + + If Not IsWallCornerBetweenNodes(center_x,center_y, x,y) Then 'Do Not collide wall corners If denied + 'If the node is allowed For walking, add it To the open list + + 'Ensure that the node details exist + node = CbAStarMap(x,y,0) + If Not node Then node = CreateNode(x,y) + + 'Add node To the open list + AddNodeToOpenList(node, node_parent) + + EndIf + + EndIf + + EndIf + + EndIf + + EndIf + + EndIf + + Next y + + Next x + +EndFunction + +Function CalculateGCost(x,y, parent_x,parent_y, parent_g_cost) + 'Note! Given coordinates must be adjacent To Each other! + If x<>parent_x And y<>parent_y Then g_cost = 14+parent_g_cost Else g_cost = 10+parent_g_cost + If CBASTAR_RULE_USE_MOVE_DIRECTION_PENALTY Then + direction = CoordinateChangeToDirection(x-parent_x,y-parent_y) + g_cost = g_cost + CbAStarMoveDirectionPenalty(direction) + EndIf + If CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then + g_cost = g_cost + CbAStarMap(x,y,2) + EndIf + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + direction = CoordinateChangeToDirection(x-parent_x,y-parent_y) + If CbAStarMap(x,y,3) = direction Then g_cost = g_cost + CbAStarMap(x,y,4) + EndIf + If CBASTAR_RULE_USE_TURN_PENALTY Then + node_parent = CbAStarMap(parent_x,parent_y,0) + cl.CbAStarClosedList= ConvertToType(PeekInt(node_parent,10)) + node_grand_parent = cl\node_parent + If node_grand_parent Then + grand_parent_x = PeekShort(node_grand_parent,0) + grand_parent_y = PeekShort(node_grand_parent,2) + direction_to_parent = CoordinateChangeToDirection(parent_x - grand_parent_x, parent_y- grand_parent_y) + direction_to_current= CoordinateChangeToDirection(x - parent_x, y - parent_y) + If direction_to_parent <> direction_to_current Then g_cost + CbAStarTurnPenalty + EndIf + EndIf + Return g_cost +EndFunction + +Function CalculateHCost(x,y, end_x,end_y) + Return (Abs(x-end_x)+Abs(y-end_y))*10 +EndFunction + +Function FindNodeWithLowestPathScore() + lowest_f = -1 + lowest_f_node = False + For ol.CbAStarOpenList = Each CbAStarOpenList + g = PeekShort(ol\node,6) + h = PeekShort(ol\node,8) + f = g+h + If f < lowest_f Or lowest_f = -1 Then + lowest_f = f + lowest_f_node = ol\node + EndIf + Next ol + Return lowest_f_node +EndFunction + +Function CheckIfNodeCouldGetBetterParent(node, node_parent_candidate) + 'Note! This Function works only when a node is in open list! + If PeekByte(node,5) <> 1 Then Return False + If Not node_parent_candidate Then Return False + list = PeekByte(node, 5) + ol.CbAStarOpenList = ConvertToType(PeekInt(node,10)) + node_parent = ol\node_parent + If node_parent Then + node_parent_g = PeekShort(node_parent, 6) + node_parent_candidate_g = PeekShort(node_parent_candidate, 6) + If node_parent_candidate_g < node_parent_g Then + ol\node_parent = node_parent_candidate + node_x = PeekShort(node,0) + node_y = PeekShort(node,2) + node_parent_candidate_x = PeekShort(node_parent_candidate,0) + node_parent_candidate_y = PeekShort(node_parent_candidate,2) + PokeShort node, 6, CalculateGCost(node_x,node_y, node_parent_candidate_x,node_parent_candidate_y, node_parent_candidate_g) + EndIf + EndIf +EndFunction + +Function IsWallCornerBetweenNodes(node1_x,node1_y, node2_x,node2_y) + wall1_x = node1_x + wall1_y = node2_y + wall2_x = node2_x + wall2_y = node1_y + is_wall1 = CbAStarMap(wall1_x,wall1_y,1) + is_wall2 = CbAStarMap(wall2_x,wall2_y,1) + If CBASTAR_RULE_MOVE_BETWEEN_WALL_CORNERS Then + If CBASTAR_RULE_ACCEPT_COLLIDE_WALL_CORNERS Then + Return False + Else + Return is_wall1 Or is_wall2 + EndIf + Else + If CBASTAR_RULE_ACCEPT_COLLIDE_WALL_CORNERS Then + Return is_wall1 And is_wall2 + Else + Return is_wall1 Or is_wall2 + EndIf + EndIf +EndFunction + +Function IsOpenListEmpty() + ol.CbAStarOpenList = Last(CbAStarOpenList) + Return ol = NULL +EndFunction + +Function ResetPath() + For ol.CbAStarOpenList = Each CbAStarOpenList + Delete ol + Next ol + For cl.CbAStarClosedList = Each CbAStarClosedList + Delete cl + Next cl + For n.CbAStarNode = Each CbAstarNode + node_x = PeekShort(n\node,0) + node_y = PeekShort(n\node,2) + CbAStarMap(node_x,node_y,0) = False + DeleteMEMBlock(n\node) + Delete n + Next n + CbAStarIsCalculating = False +EndFunction + +Function GatherPath() + node = CbAStarMap(CbAStarEndX,CbAStarEndY,0) + path = MakeMEMBlock(1) + size = 0 + Repeat + If node Then + size + 4 + ResizeMEMBlock path,size + node_x = PeekShort(node,0) + node_y = PeekShort(node,2) + PokeShort path, size-4, node_x + PokeShort path, size-2, node_y + cl.CbAStarClosedList= ConvertToType(PeekInt(node,10)) + node = cl\node_parent + EndIf + Until (node_x=CbAStarStartX And node_y=CbAStarStartY) Or node=False + Return path +EndFunction + +Function ReversePath(path) + size = MEMBlockSize(path) + For i = 0 To size/2-4 Step 4 + temp_int = PeekInt(path,size-i-4) + PokeInt path, size-i-4, PeekInt(path,i) + PokeInt path, i, temp_int + Next i +EndFunction + +Function CoordinateChangeToDirection(x,y) + 'Parameter values must be from -1 To 1 + If x = 1 And y = 0 Then Return 0 + If x = 1 And y = -1 Then Return 1 + If x = 0 And y = -1 Then Return 2 + If x = -1 And y = -1 Then Return 3 + If x = -1 And y = 0 Then Return 4 + If x = -1 And y = 1 Then Return 5 + If x = 0 And y = 1 Then Return 6 + If x = 1 And y = 1 Then Return 7 +EndFunction + +Function DirectionToCoordinateChange(direction) + Select direction + Case 0 + Return "1 0" + Case 1 + Return "1 -1" + Case 2 + Return "0 -1" + Case 3 + Return "-1 -1" + Case 4 + Return "-1 0" + Case 5 + Return "-1 1" + Case 6 + Return "0 1" + Case 7 + Return "1 1" + EndSelect +EndFunction + +Function SetAStarObstacle(x,y, obstacle=1) + If obstacle < 0 Or obstacle > 1 Then Return False + If x < 0 Or x > CbAStarMapWidth Then Return False + If y < 0 Or y > CbAStarMapHeight Then Return False + CbAStarMap(x,y,1) = obstacle +EndFunction + +Function SetAStarObstacleLine(x1,y1, x2,y2, obstacle=1) + If obstacle < 0 Or obstacle > 1 Then Return False + If x1 < 0 Or x1 > CbAStarMapWidth Then Return False + If y1 < 0 Or y1 > CbAStarMapHeight Then Return False + If x2 < 0 Or x2 > CbAStarMapWidth Then Return False + If y2 < 0 Or y2 > CbAStarMapHeight Then Return False + x_difference = Abs(x1-x2) + y_difference = Abs(y1-y2) + maxi = Max(x_difference,y_difference) + For i# = 0 To maxi + pros# = i/maxi + x = x1 + (x2-x1) * pros + y = y1 + (y2-y1) * pros + CbAStarMap(x,y,1) = obstacle + Next i# +EndFunction + +Function SetAStarObstacleBox(x,y, w,h, filled=0, obstacle=1) + If obstacle < 0 Or obstacle > 1 Then Return False + If filled < 0 Or filled > 1 Then Return False + If x < 0 Or x > CbAStarMapWidth Then Return False + If y < 0 Or y > CbAStarMapHeight Then Return False + If w < 0 Then Return False + If h < 0 Then Return False + If x+w > CbAStarMapWidth Then w = CbAStarMapWidth - x + If y+h > CbAStarMapHeight Then h = CbAStarMapHeight- y + If filled Then + For xx = x To x+w-1 + For yy = y To y+h-1 + CbAStarMap(xx,yy,1) = obstacle + Next yy + Next xx + Else + SetAStarObstacleLine(x, y, x+w-1, y, obstacle) + SetAStarObstacleLine(x, y+h-1, x+w-1, y+h-1, obstacle) + SetAStarObstacleLine(x, y, x, y+h-1, obstacle) + SetAStarObstacleLine(x+w-1, y, x+w-1, y+h-1, obstacle) + EndIf +EndFunction + +Function SetAStarPenalty(x,y, node_penalty$, node_penalty_direction$="Default", node_penalty_direction_value$="Default") + If node_penalty_direction <> CBASTAR_DEFAULT And (node_penalty_direction < 0 Or node_penalty_direction > 7) Then Return False + If Not CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then Return False + If x < 0 Or x > CbAStarMapWidth-1 Then Return False + If y < 0 Or y > CbAStarMapHeight-1 Then Return False + If node_penalty <> CBASTAR_DEFAULT Then CbAStarMap(x,y,2) = Int(node_penalty) + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + If node_penalty_direction <> CBASTAR_DEFAULT Then CbAStarMap(x,y,3) = Int(node_penalty_direction) + If node_penalty_direction_value <> CBASTAR_DEFAULT Then CbAStarMap(x,y,4) = Int(node_penalty_direction_value) + EndIf +EndFunction + +Function SetAStarPenaltyLine(x1,y1, x2,y2, node_penalty$, node_penalty_direction$="Default", node_penalty_direction_value$="Default") + If node_penalty_direction <> CBASTAR_DEFAULT And (node_penalty_direction < 0 Or node_penalty_direction > 7) Then Return False + If Not CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then Return False + If x1 < 0 Or x1 > CbAStarMapWidth Then Return False + If y1 < 0 Or y1 > CbAStarMapHeight Then Return False + If x2 < 0 Or x2 > CbAStarMapWidth Then Return False + If y2 < 0 Or y2 > CbAStarMapHeight Then Return False + x_difference = Abs(x1-x2) + y_difference = Abs(y1-y2) + maxi = Max(x_difference,y_difference) + For i# = 0 To maxi + pros# = i/maxi + x = x1 + (x2-x1) * pros + y = y1 + (y2-y1) * pros + If node_penalty <> CBASTAR_DEFAULT Then CbAStarMap(x,y,2) = Int(node_penalty) + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + If node_penalty_direction <> CBASTAR_DEFAULT Then CbAStarMap(x,y,3) = Int(node_penalty_direction) + If node_penalty_direction_value <> CBASTAR_DEFAULT Then CbAStarMap(x,y,4) = Int(node_penalty_direction_value) + EndIf + Next i# +EndFunction + +Function SetAStarPenaltyBox(x,y, w,h, filled, node_penalty$, node_penalty_direction$="Default", node_penalty_direction_value$="Default") + If node_penalty_direction <> CBASTAR_DEFAULT And (node_penalty_direction < 0 Or node_penalty_direction > 7) Then Return False + If Not CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then Return False + If filled < 0 Or filled > 1 Then Return False + If x < 0 Or x > CbAStarMapWidth Then Return False + If y < 0 Or y > CbAStarMapHeight Then Return False + If w < 0 Then Return False + If h < 0 Then Return False + If x+w > CbAStarMapWidth Then w = CbAStarMapWidth - x + If y+h > CbAStarMapHeight Then h = CbAStarMapHeight- y + If filled Then + For xx = x To x+w-1 + For yy = y To y+h-1 + If node_penalty <> CBASTAR_DEFAULT Then CbAStarMap(xx,yy,2) = Int(node_penalty) + If CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY Then + If node_penalty_direction <> CBASTAR_DEFAULT Then CbAStarMap(xx,yy,3) = Int(node_penalty_direction) + If node_penalty_direction_value <> CBASTAR_DEFAULT Then CbAStarMap(xx,yy,4) = Int(node_penalty_direction_value) + EndIf + Next yy + Next xx + Else + SetAStarPenaltyLine(x, y, x+w-1, y, node_penalty$, node_penalty_direction$="Default", node_penalty_direction_value$="Default") + SetAStarPenaltyLine(x, y+h-1, x+w-1, y+h-1, node_penalty$, node_penalty_direction$="Default", node_penalty_direction_value$="Default") + SetAStarPenaltyLine(x, y, x, y+h-1, node_penalty$, node_penalty_direction$="Default", node_penalty_direction_value$="Default") + SetAStarPenaltyLine(x+w-1, y, x+w-1, y+h-1, node_penalty$, node_penalty_direction$="Default", node_penalty_direction_value$="Default") + EndIf +EndFunction + +Function GetAStarObstacle(x,y) + Return CbAstarMap(x,y,1) +EndFunction + +Function GetAStarPenalty(x,y, penalty_type=0) + //penalty_type can be: + // 0 = get single node penalty + // 1 = get single node direction penalty (the direction) + // 2 = get single node direction penalty (the penalty amount) + If Not CBASTAR_RULE_USE_SINGLE_NODE_PENALTY Then Return False + If False = CBASTAR_RULE_USE_NODE_DIRECTION_PENALTY And penalty_type>0 Then Return False + Return CbAStarMap(x,y,2+penalty_type) +EndFunction + +Function SaveAStarMap(file_path$) + //Note! If file in file_path$ already exists, it will be overwritten! + f = OpenToWrite(file_path) + WriteByte f, 1 'Map format version + WriteShort f, CBAStarMapWidth + WriteShort f, CBAStarMapHeight + WriteByte f, CbAStarMapDepth + For x = 0 To CBAStarMapWidth-1 + For y = 0 To CBAStarMapHeight-1 + WriteByte f, CbAStarMap(x,y,1) + If CbAStarMapDepth > 1 Then WriteShort f, CbAStarMap(x,y,2) + If CbAStarMapDepth = 4 Then + WriteByte f, CbAStarMap(x,y,3) + WriteShort f, CbAStarMap(x,y,4) + EndIf + Next y + Next x + CloseFile f +EndFunction + +Function LoadAStarMap(file_path$, compatibility_mode=0) + //Note! It is possible that a map that is being loaded has MORE Or LESS properties declared in it than what cbAStar IS CURRENTLY configured To use. + // A map can be loaded even If it has different amount of properties, but in some cases it may cause strange behaviour in the program's those parts + // that are outside this library but uses this library. This is why this Function's Default behaviour is To verify that the map file which is being + // loaded contais equal level of properties that cbAStar is currently configured To use (see RULES F And G!). If the map file contains different level + // of properties, the loading will fail And the Function returns False. If you do want To load maps that have different level of properties, it can + // be done by setting the optional compatibility_mode parameter To ON ( = 1). But If you do Not need the compatibility feature, please keep it False. + //Return values: + // Integer 1 = The map was loaded correctly + // Integer 2 = The map was loaded correctly, but the loaded map needed To be convertet To meet the current cbAStar configuration (only when + // compatibility_mode is set To ON). + // 0 (False) = The map cannot be loaded because the file does Not exists Or the map properties is different than cbAStar current configuration (And + // compatibility_mode is set To OFF). False can also be returned If the map file is in unsupported format (For example a newer cbAStar version + // has been written it). + If Not FileExists(file_path) Then Return False + f = OpenToRead(file_path) + map_version = ReadByte(f) + If map_version <> 1 Then Return False + map_width = ReadShort(f) + map_height = ReadShort(f) + map_depth = ReadByte(f) + If map_depth <> CbAStarMapDepth And compatibility_mode=OFF Then Return False + CbAStarInitialize(map_width,map_height) + For x = 0 To CBAStarMapWidth-1 + For y = 0 To CBAStarMapHeight-1 + CbAStarMap(x,y,1) = ReadByte(f) + If map_depth > 1 Then single_node_penalty = ReadShort(f) + If CbAStarMapDepth > 1 Then CbAStarMap(x,y,2) = single_node_penalty + If map_depth = 4 Then + single_node_penalty_direction = ReadByte(f) + single_node_penalty_direction_value = ReadShort(f) + EndIf + If CbAStarMapDepth = 4 Then + CbAStarMap(x,y,3) = single_node_penalty_direction + CbAStarMap(x,y,4) = single_node_penalty_direction_value + EndIf + Next y + Next x + CloseFile f + Return 1 + (map_depth <> CbAStarMapDepth) +EndFunction + +Function FollowPath(path) + //Return values: + // True = Reading Next node succeeded + // False = Path does Not have anymore nodes + path_size = MEMBlockSize(path) + + 'This Function stores extra information To the given path. + 'First check out, does this information already exists? + If path_size Mod 4 <> 0 Then + 'Yes, the information exists - so we can find out where we Left Last Time when followed this path. + node_index = PeekShort(path,path_size-2) + If node_index*4 > path_size-6 Then Return False + Else + 'No, there is no extrainfo - so start To follow this path from the First node. + node_index = 0 + 'However, the MemBlock needs To be resized so that it can have stored the extra info into it at the End of this Function. + path_size + 2 + ResizeMEMBlock path, path_size + EndIf + + read_position = node_index*4 + FollowPathX = PeekShort(path, read_position) + FollowPathY = PeekShort(path, read_position+2) + PokeShort path, path_size-2, node_index+1 + Return True +EndFunction + +Function ResetFollowPath(path) + path_size = MEMBlockSize(path) + If path_size Mod 4 <> 0 Then ResizeMEMBlock path, path_size-2 +EndFunction diff --git a/cbAStar_Logo.png b/cbAStar_Logo.png new file mode 100644 index 0000000..8d19b73 Binary files /dev/null and b/cbAStar_Logo.png differ