Tanis Logo
>Home  >Blog  >News  >Products  >Articles  >Music  >Wiki  >Links

Path finding in C#

Show Printer Friendly Page

Written by Sune Trudslev
© Copyright 2003 Tanis. All rights reserved.
Article URL: http://www.tanis.dk/Articles/CSharpPathfind

This article has been viewed 7464 times.

Linking to this article is encouraged, but reproduction in whole or in part either on the WWW or other media is prohibited under appropriate copyright laws. Please ask for permission first.

Index

1. Introduction
2. The A* algorithm
3. Heuristics
4. Data structures
5. Extensibility
6. The actual A* algorithm
7. Creating your own AStarNode class
8. Conclusion
9. Update
10. More information

Article

    Introduction

    As I was working on my own little game project, I searched the net for implementations of the A* path finding algorithm in C#, but I could not find any.

    There were many, in pretty much every other language, but they either used very language specific features (like templates in C++) or were implemented in a way that just didn't seem very extensible, so I decided to write my own in C#.

    I will give a brief introduction to the inner workings or the A* algorithm spiced up with some code samples from the actual implementation. I will also spend a little time explaining how to make your own AStarNode class.

    The A* algorithm

    There are a few choices in path finding when it comes to algorithms. A* is probably the most popular one, because it is quite flexible.

    A* can be used to solve a myriad of different problems, like:
    • Combinatory puzzles like Rubric's Cube or 8-puzzles.
    • Route finding.
    • Robot navigation.
    • And, of course, path finding in video games, which is what we are going to use it for
    The reason the A* algorithm works so well, is that it favors points that are close to the starting point, and points that are close to the goal as well.

    Heuristics

    As mentioned earlier, the A* algorithm both favors the distance from the start state (this is called the Cost in the program, traditionally this is called g()) and the estimated distance to the goal (this is called GoalEstimate in the program, traditionally this is called h()).

    In my test of the A* class, I let the character move in 8 possible directions on a 2-dimensional map, so I will be using the "Diagonal Distance" approach.
    Many different mathematical formulas can be used as heuristic functions when doing an A* search, it all depends on how your character moves.

    This article discusses which formula is best used for which movement.

    Data structures

    Internally the A* algorithm has two lists, the OPEN list, which holds all the nodes that are possible ways to the goal, this is also known as the frontier, and the CLOSED list, which holds all the nodes we have expanded.

    Many different data structures are possibly good choices for these lists, but to keep complexity down, I have implemented an "always sorted" array list, that I call Heap, it implements both the IList and IClonable interfaces, even though they are not really used in this project.

    The two most important methods relating to the A* search:
    /// <summary>
    /// Returns the topmost object on the list and removes it from the list
    /// </summary>
    /// <returns>Returns the topmost object on the list</returns>
    public object Pop();

    /// <summary>
    /// Pushes an object on list. It will be inserted at the right spot.
    /// </summary>
    /// <param name="Object">Object to add to the list</param>
    public void Push(object Object);

    I am considering tuning this part in a later article, possible using a binary heap as the data structure. More on this later.

    Extensibility

    The class has been designed to be very extensible. No code in the actual AStarNode class does any calculations relating to coordinates and such, so it should be possible to use this class to solve any A* problem, not just relating to 2-dimensional grids.

    Later I will explain how to implement your own child of the AStarNode class.

    The actual A* algorithm

    The implementation of the A* main loop is pretty straight forward. I will go through it step-by-step and explain what is going on.

    First we assign the starting node and the goal node to private variables. These will be used through-out the entire pathfinding. Then we add the starting node as our initial search state:
    FStartNode = AStartNode;
    FGoalNode = AGoalNode;

    FOpenList.Add(FStartNode);

    Then we start our main loop. We will keep looping as long as there are still nodes on our heap:
    while(FOpenList.Count > 0)
    {

    Then we get the node with the lowest TotalCost off the OPEN list. Since the heap is always sorted according to the IComparable interface implementation of the AStarNode class, this happens just by popping the value off the heap:
    // Get the node with the lowest TotalCost
    AStarNode NodeCurrent = (AStarNode)FOpenList.Pop();

    If the current node is the solution we will now copy the completed path to the Solution list:
    // If the node is the goal copy the path to the solution array
    if(NodeCurrent.IsGoal()) {
      while(NodeCurrent != null) {
        FSolution.Insert(0,NodeCurrent);
        NodeCurrent = NodeCurrent.Parent;
      }
      break;
    }

    Then we ask the AStarNode descendant to give us all possible successors of the current node (except the parent of the current node of course):
    // Get successors to the current node
    NodeCurrent.GetSuccessors(FSuccessors);

    Then we loop through the successors and see if they are worth anything:
    foreach(AStarNode NodeSuccessor in FSuccessors)
    {

    First we see if the successor node is already on the OPEN list, if it is and the TotalCost is higher than the existing one, we will discard this successor node:
    // Test if the currect successor node is on the open list, if it is and
    // the TotalCost is higher, we will throw away the current successor.
    AStarNode NodeOpen = null;
    if(FOpenList.Contains(NodeSuccessor))
      NodeOpen = (AStarNode)FOpenList[FOpenList.IndexOf(NodeSuccessor)];
    if((NodeOpen != null) && (NodeSuccessor.TotalCost > NodeOpen.TotalCost))
      continue;

    Then we do the exact same thing on the CLOSED list:
    // Test if the currect successor node is on the closed list, if it is and
    // the TotalCost is higher, we will throw away the current successor.
    AStarNode NodeClosed = null;
    if(FClosedList.Contains(NodeSuccessor))
      NodeClosed = (AStarNode)FClosedList[FClosedList.IndexOf(NodeSuccessor)];
    if((NodeClosed != null) && (NodeSuccessor.TotalCost > NodeOpen.TotalCost))
      continue;

    Then we remove the instances found on the OPEN and CLOSED list:
    // Remove the old successor from the open list
    FOpenList.Remove(NodeSuccessor);

    // Remove the old successor from the closed list
    FClosedList.Remove(NodeSuccessor);

    Then we add the current successor to the OPEN list and end the loop:
      // Add the current successor to the open list
      FOpenList.Push(NodeSuccessor);
    }

    Then last, but not least, we will add the current node to the closed list and end the loop:
      // Add the current node to the closed list
      FClosedList.Add(NodeCurrent);
    }

    This concludes the main loop in the A* algorithm. Note, that there are circumstances where this implementation can enter an end-less loop. If, for example, the goal is unreachable. It is easy to implement ways to exit the loop, but I will leave the refinement of the implementation for a later time.

    Creating your own AStarNode class

    To create your own AStarNode descendant, you need to override all the virtual members of the AStarNode class:
    public virtual bool IsSameState(AStarNode ANode);
    public virtual void Calculate();
    public virtual void GetSuccessors(ArrayList ASuccessors);
    public virtual void PrintNodeInfo();

    The implementation of the IsSameState() method must check all state information (i.e. coordinates), if it's the same state it should return true, otherwise false.

    The implementation of the Calculate() method must calculate the GoalEstimate (the heuristic) according the state information and the GoalNode.

    The implementation of the GetSuccessors() method must place all possible successor of the node itself on the ASuccessors ArrayList.

    The implementation of the PrintNodeInfo() method should print whatever information is available about the state. The current code calls it from the private method PrintNodeList() on the AStar class to print information about the lists. You can place calls to PrintNodeList() strategic places in the code if you want to see what is going on when the code is path finding.

    Conclusion

    With this class you should be able to implement A* path finding to your own C# projects. The implementation allows for any kind of A* search to be done.

    Other people have implemented solving of i.e. 8-puzzles using the same generic algorithm.

    Update

    November 16th, 2005: Code update to fix a bug with removing the correct nodes from the open and closed lists.

    More information

    This article was inspired by other peoples work:And for a very interesting graphical look:

    Files

    This article has a file attached:
     AStar.zip - Source code to AStar class and test files.
    Featured Product
    Davina's GGS Timer
    Davina's GGS Timer automatically tracks the time between GGS gains in Ultima Online.

    Version: 1.25
    Released: December 1st, 2003
    Downloads: 278
    Need Help?
    Need help with one of our products? Have a look in our Support Forums. Maybe someone else already found a solution.

    Think you found a bug? Go to our bug site and report it.

    © Copyright 1998-2009 Tanis. All rights reserved.