14,032,416 members
alternative version

#### Stats

225.9K views
75 bookmarked
Posted 3 Jan 2004
Licenced

# Path finding in C#

, 3 Jan 2004
A generic implementation of the A* (AStar) path finding algorithm in C#.

## 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 of 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 Cube or 8-puzzles.
• Route finding.
• 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.

## 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>
/// Object to add to the list
/// <returns></returns>
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;

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` child 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(NodeOpen);

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

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
} ```

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.

Here's how it looks when you run it with one of the test maps:

## 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 successors 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` in 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 8-puzzles using the same generic algorithm.

And for a very interesting graphical look:

A list of licenses authors might use can be found here

## Share

Sune has been programming since 1985.

He has worked with both assembler and many different programming languages, such as: Basic, Fortran, Pascal, C, C++, Delphi, and lately C#.

Sune is married and has two small children and is currently working as a developer for a Danish company called Skygate, currenly working on a cheap flight search engine called Momondo.

Personal blog can be found here.

## You may also be interested in...

 First PrevNext
 Heuristic used doesn't work well with different movement weights Member 79100464-May-14 6:49 Member 7910046 4-May-14 6:49
 Fixed Code Here; Shortest Paths returned Dan DiCicco26-Aug-13 11:20 Dan DiCicco 26-Aug-13 11:20
 Re: Fixed Code Here; Shortest Paths returned Member 1076198120-Apr-14 10:33 Member 10761981 20-Apr-14 10:33
 Re: Fixed Code Here; Shortest Paths returned StudioWolfox27-Apr-14 0:43 StudioWolfox 27-Apr-14 0:43
 Re: Fixed Code Here; Shortest Paths returned Member 131754156-May-17 4:57 Member 13175415 6-May-17 4:57
 Question about full cost, opened and closed lists DmitrijNik14-Apr-13 0:05 DmitrijNik 14-Apr-13 0:05
 Hello. I'm a bit confused and I hope to get some assistance from you. So, why do you remove nodes from opened and closed lists if node's FULL cost greater than the FULL cost of the similar node in one of the lists? Imo, the full cost of the node is always greater than the full cost of the previous one. Am I right? Please, correct me. Can we remove nodes from opened/closed lists by it's Go-value that could be smaller than the one's in a lists?
 Existent combinatorial optimization component dark_sidus19-Aug-07 0:36 dark_sidus 19-Aug-07 0:36
 Sum nodes cost? stevenkong20-Apr-06 2:16 stevenkong 20-Apr-06 2:16
 Bug responsible for slow Wojciech Wawrzyniak4-Mar-06 11:32 Wojciech Wawrzyniak 4-Mar-06 11:32
 Closed List jcuglo7-Feb-06 6:58 jcuglo 7-Feb-06 6:58
 Re: question BuggyBrain2-Jun-06 1:47 BuggyBrain 2-Jun-06 1:47
 .Contains?? luketheduke6-Jan-06 2:01 luketheduke 6-Jan-06 2:01
 Re: .Contains?? DrGLaZ30-Apr-06 23:45 DrGLaZ 30-Apr-06 23:45
 Re: .Contains?? luketheduke1-May-06 4:33 luketheduke 1-May-06 4:33
 Re: .Contains?? [modified] Vecware8-Sep-08 12:39 Vecware 8-Sep-08 12:39
 small fix coyotesoftware13-Dec-05 9:24 coyotesoftware 13-Dec-05 9:24
 Question about IComparer Johnny Scanner31-Jul-05 20:13 Johnny Scanner 31-Jul-05 20:13
 Path Finding with Collision Avoidance sridevi swamy22-Jun-05 3:17 sridevi swamy 22-Jun-05 3:17
 Re: Path Finding with Collision Avoidance Johnny Scanner31-Jul-05 20:16 Johnny Scanner 31-Jul-05 20:16
 An issue for games... LynxRaven30-Mar-05 18:29 LynxRaven 30-Mar-05 18:29
 Re: An issue for games... Johnny Scanner31-Jul-05 20:21 Johnny Scanner 31-Jul-05 20:21
 Slow and lot of bugs... jplacombe28-Feb-05 4:43 jplacombe 28-Feb-05 4:43
 Bug Alex_V_X8-Feb-05 20:08 Alex_V_X 8-Feb-05 20:08
 Last Visit: 23-Apr-19 13:51     Last Update: 23-Apr-19 13:51 Refresh 12 Next »