Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / C#

Fast A-Star (2D) Implementation for C#

Rate me:
Please Sign up or sign in to vote.
4.80/5 (30 votes)
18 Oct 2010MIT5 min read 212.2K   10.3K   60   53
A functional, generic a-star solver with good performance

Update Notice

I have successfully ported this algorithm to C++ and it is coming with a C++ .NET wrapper. I never could imagine that C++ would increase the performance about one order of magnitudes. But in this very case, the C++ algorithm can work much more efficient, since it isn't generic or uses any other flexibility the C# version provides. For my tasks, the C++ algorithm is all I need so far. I will post it here as soon as it is more tested and I've also added A* instancing, issuing multiple paths queries at the cost of one, in common scenarios. For example the C++ algorithm completes in 230ms for a 1024x1024 grid, where the C# version takes about 2 seconds...

Introduction

This article won't explain how A-Star works, since there are tons of papers out there describing it, you will find one of them here. Instead, it provides you with a straightforward, fast and generic A-Star solver for 2D grids. The sample application just generates a 512x512 sample grid and computes a diagonal path through it, showing the computation time and dumping a PNG with the results into "dump.png".

In this article, I don't want to talk much, but just show you the easy steps to take advantage of A-Star in your application...

Background

I am currently working on a realtime strategy game. Till today, I used the A-Star solver of Gustavo Franco.

But now I recognized that a simple A-Star solver won't do the job in my case. I need a cooperative A-star solver for all those units on the map. The problem is that I couldn't find any implementation out there, so started with my own, based on a university paper. If it is ready, I also plan to post it on CodeProject, but this article is focused on spatial A-star. Gustavo's solver seems to have serious bugs like wallbreaks and suboptimal paths. My first Implementation also had serious bugs, but not only in my code. It seems to be impossible to find working PriorityQueues out there. I tried nearly all of them and almost all are bugged. This is really frustrating when you are relying on such components and looking for the bug in your own code instead!

The algorithm I am going to describe here is as fast as the one of Gustavo (at least with my measures on large grids). But I directly used the Wikipedia pseudo code and translated it into C#. Instead of optimizing the A-Star itself, I just optimized the Queue and Set operations used in pseudo code. This leads to a far better understandable implementation without significant performance losses. The following shows the results of the attached demo application:

dump.png

The pathfinding for this very example completes in 400ms on a "Core i5 Mobile" (which is really slow compared to a usual quad core).

But so far about the background.

Using the Code

When I saw most of the A-Star solvers out there I always thought, why is this so complicated?! After all, I never wrote an A-Star solver myself, till today, but it was rather easy... To take advantage of it, you first need to provide it with a 2D-grid structure (two-dimensional array). This array should be composed of a user defined class deriving from IPathNode:

C#
public class MyPathNode : IPathNode<Object>
{
    public Int32 X { get; set; }
    public Int32 Y { get; set; }
    public Boolean IsWall {get; set;}

    public bool IsWalkable(Object unused)
    {
        return !IsWall;
    }
}

IsWalkable() is called by the search method. The return value is expected to be consistent during search but can change between searches without performance penalties! X, Y and IsWall are not required by IPathNode, we just use them for our convenience. The generic argument for IPathNode denotes the parameter type of IsWalkable (for performance reasons, I go the generic way). You may pass this parameter to every search invocation. This is the first point where my algorithm is very flexible. For example, you could make a "wall" walkable for a tank, but unbreakable for a soldier, without any changes to internal data structures or even using a second path finder instance...

Next you'll have to initialize the search space, the following should be self-explaining:

C#
// setup grid with walls
MyPathNode[,] grid = new MyPathNode[Width, Height];

for (int x = 0; x < Width; x++)
{
    for (int y = 0; y < Height; y++)
    {
        Boolean isWall = ((y % 2) != 0) && (rnd.Next(0, 10) != 8);

        grid[x, y] = new MyPathNode()
        {
            IsWall = isWall,
            X = x,
            Y = y,
        };
    }
}  

The next thing is to create a solver instance. It is important to do this only once, because the initialization is very memory intensive. The second generic parameter denotes the IsWalkable() argument type.

C#
SpatialAStar<MyPathNode, Object> aStar = new SpatialAStar<MyPathNode, Object>(grid); 

This is all setup you need so far. Since optimization, the A-star solver consumes about 50 MB of memory for a 1024x1024 grid. In times of cheap RAM this should be acceptable, but feel free to provide us with further optimizations! The next good thing is that the algorithm scales almost linear. This means when finding a path which goes over the entire grid, let's say 512x512, takes 300ms to complete, it will take around 800ms to complete on a 1024x1024 grid.

Finally, to invoke the search, just call:

C#
LinkedList<MyPathNode> path = aStar.Search(new Point(0, 0),
               new Point(Width - 2, Height - 2), null);

where the first parameter is the starting node and the second, the desired end node of the path. If no path is found, null is returned. The third parameter is directly passed to IsWalkable().

Customizations

By default, the solver is using the Euclidian metric for heuristic and exact distance. To overwrite this behavior, you need to create a subclass of the solver, for example to use the manhatten metric:

C#
public class MySolver<TPathNode, TUserContext> : SettlersEngine.SpatialAStar<TPathNode, 
	TUserContext> where TPathNode : SettlersEngine.IPathNode<TUserContext>
{
    protected override Double Heuristic(PathNode inStart, PathNode inEnd)
    {
        return Math.Abs(inStart.X - inEnd.X) + Math.Abs(inStart.Y - inEnd.Y);
    }

    protected override Double NeighborDistance(PathNode inStart, PathNode inEnd)
    {
        return Heuristic(inStart, inEnd);
    }

    public MySolver(TPathNode[,] inGrid)
        : base(inGrid)
    {
    }
} 

Have fun with this algorithm and let me now about improvements and potential bugs, since I need it in my own gaming engine!

The PriorityQueue was taken from here with some bug fixes and modifications.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer SecurityRevolutions
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: My vote of 3 Pin
Christoph Husse14-Oct-10 2:58
Christoph Husse14-Oct-10 2:58 
GeneralRe: My vote of 3 Pin
Jason Vogel14-Oct-10 3:49
Jason Vogel14-Oct-10 3:49 
GeneralRe: My vote of 3 Pin
Christoph Husse14-Oct-10 3:58
Christoph Husse14-Oct-10 3:58 
GeneralRe: My vote of 3 Pin
Jason Vogel14-Oct-10 4:11
Jason Vogel14-Oct-10 4:11 
GeneralRe: My vote of 3 Pin
Christoph Husse14-Oct-10 4:18
Christoph Husse14-Oct-10 4:18 
GeneralRe: My vote of 3 Pin
Jason Vogel14-Oct-10 4:46
Jason Vogel14-Oct-10 4:46 
GeneralRe: My vote of 3 Pin
Steve Maier14-Oct-10 7:22
professionalSteve Maier14-Oct-10 7:22 
GeneralRe: My vote of 3 Pin
Michael Demeersseman14-Oct-10 4:39
Michael Demeersseman14-Oct-10 4:39 
I cannot disagree more. it's not because you don't need it it's not interesting. They call it curiousity, exploring the unknown and it's fun and very interesting! even if you don't need it.

thx for sharing. vote of 5 !
GeneralRe: My vote of 3 Pin
Christoph Husse14-Oct-10 4:49
Christoph Husse14-Oct-10 4:49 
GeneralRe: My vote of 3 Pin
Eddy Vluggen14-Oct-10 4:58
professionalEddy Vluggen14-Oct-10 4:58 
GeneralRe: My vote of amused... Pin
CalvinHobbies14-Oct-10 7:07
CalvinHobbies14-Oct-10 7:07 
GeneralRe: My vote of 3 Pin
Corey Fournier18-Oct-10 5:38
Corey Fournier18-Oct-10 5:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.