GithubHelp home page GithubHelp logo

valantonini / astar Goto Github PK

View Code? Open in Web Editor NEW
122.0 3.0 23.0 174 KB

A 2D A Star (A*) pathfinding implementation in C# focused on ease of use

License: MIT License

C# 100.00%
csharp algorithms pathfinding game-development

astar's Introduction

A 2D A* (A Star) algorithm for C#

Travis (.com) branch NuGet

The world is represented by a WorldGrid that is essentially a matrix of the C# short data type. A value of 0 indicates the cell is closed / blocked. Any other number indicates the cell is open and traversable. It is recommended to use 1 for open cells as numbers greater and less than 0 may be used to apply penalty or priority to movement through those nodes in the future.

The WorldGrid can be indexed via either:

  1. The provided Position struct where a row represents the vertical axis and column the horizontal axis (Similar to indexing into a matrix Prc).

  2. The C# Point struct that operates like a cartesian co-ordinate system where X represents the horizontal axis and Y represents the vertical axis (Pxy).

Paths can be found using either Positions (matrix indexing) or Points (cartesian indexing).

A Go version is also in to works

Example usage

PathingExample

   var pathfinderOptions = new PathFinderOptions { 
      PunishChangeDirection = true,
      UseDiagonals = false, 
   };

   var tiles = new short[,] {
      { 1, 0, 1 },
      { 1, 0, 1 },
      { 1, 1, 1 },
   };

    var worldGrid = new WorldGrid(tiles);
    var pathfinder = new PathFinder(worldGrid, pathfinderOptions);
    
    // The following are equivalent:
    
    // matrix indexing
    Position[] path = pathfinder.FindPath(new Position(0, 0), new Position(0, 2));
    
    // point indexing
    Point[] path = pathfinder.FindPath(new Point(0, 0), new Point(2, 0));

Options

  • Allowing / restricting diagonal movement
  • A choice of heuristic (Manhattan, MaxDxDy, Euclidean, Diagonal shortcut)
  • The option to punish direction changes.
  • A search limit to short circuit the search

FAQ

q. why doesn't this algorithm always find the shortest path?

a. A* optimises speed over accuracy. Because the algorithm relies on a heuristic to determine the distances from start and finish, it won't necessarily produce the shortest path to the target.

Changes from 1.1.0 to 1.3.0

  • Introduced path weighting to favour or penalize cells. This is off by default and can be opted into using the new options. See this blog post for more info
var level = @"1111115
              1511151
              1155511
              1111111";
var world = Helper.ConvertStringToPathfinderGrid(level);
var opts = new PathFinderOptions { Weighting = Weighting.Positive };
var pathfinder = new PathFinder(world, opts);

Changes from 1.0.0 to 1.1.0

  • Reimplemented the punish change direction to perform more consistently

Changes from 0.1.x to 1.0.0

  • The world is now represented by a WorldGrid that uses shorts internally instead of bytes
  • If no path is found, the algorithm now reports an empty array instead of null
  • Moved out of the AStar.Core namespace into simply AStar
  • Replaced former Point class with the new Position class that uses Row / Column instead of X / Y to avoid confusion with cartesian co-ordinates
  • Implemented support for Point class indexing and pathing which represent a traditional cartesian co-ordinate system
  • Changed path from List to Array and changed type from PathFinderNode to Position or Point
  • Reversed the order of the returned path to start at the start node
  • Rationalised and dropped buggy options (heavy diagonals)

astar's People

Contributors

valantonini avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

astar's Issues

weight of nodes

Hi, first of all congratulations for this great implementation and pretty object oriented definition of A* algorithm.

On the other hand I have found a little issue. The algorithm don't use the weight of nodes.

By example with these matrix:

short[,] matrix2 = {
{8, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{2, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 9}
};

short[,] matrix2 = {
{8, 2, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 9}
};

The result for origin and target 8 and 9 is the same using Manhattan heuristic (tried with another heuristics and the same).
0,0 1,0 2,0 3,0 4,0 4,1 4,2 4,3 4,4 4,5 4,6 4,7 4,8 4,9

Is possible a little change to use too the weight of the nodes?

Thanks so much!!

Xavier.

code only works for square grids

There's a bug in your code. The calculation of a single location value based on x,y is done wrongly.

var newLocation = (newLocationY << _gridYLog2) + newLocationX;

should be

var newLocation = (newLocationY << _gridXLog2) + newLocationX;

etc.

If the matrix is square, gridYLog2 and gridXlog2 will be equal, and thus the code will work.

Non serializable

Greetings.

Can you consider marking your classes as serializable? I instantiate the grid and pathfinder inside the game map object, so when I want to save the map with a binary serializer I'm unable to do so due to the classes not being marked as such.

It would simplify things massively if you can make it serializable, rather than me trying to concoct some kind of proxy and store pathfinder for each map separately and regenerating them after map loading or switching.

Regards.

Entity blocking itself

Hey!

There's a small issue with using the library. I'm not sure how to elegantly solve it without some hacks...

I have a grid (WorldGrid) which stores not just terrain data (e.g. which tiles are walkable/blocked) but also takes into account all entities on the map and block those cells as well, so entities don't walk to top of each other. I think this is pretty normal implementation for most games.

But here's the problem. Since the entity is actually standing in the cell it can't build a patch because the cell it is standing in is also considered blocked... I can obviously add some hacks where the entity first marks the cell it is standing on and the target cell as walkable, but it isn't exactly a good way to address it.

Maybe you can just add two simple options along the lines of:

private static readonly PathFinderOptions PathfinderOptions = new()
{
    IgnoreStartCellBlock = true,
    IgnoreEndCellBlock = true,
};

Or something of this nature. Basically so that:

  1. Entities don't block itself from moving by the virtue of existing in the cell.
  2. and can still build a path to another cell even if that cell is occupied by an entity (e.g. when monster builds a path to the player cell, even though this cell is blocked by the player).

Obviously if there's a better solution that I have missed please let me know :)

Weights are ignored

Expected

A* algorithm respects weights:

Given a weighted graph, a source node and a goal node, the algorithm [A*] finds the shortest path (with respect to the given weights) from source to goal.

Given a grid with weighted tiles around walls:
image

An agent should avoid hugging walls.

Actual

Agent ignores weights (positive or negative) and hugs walls:
image

FindPath mismatching x for y? Incorrect path returned

First of all thank you for this library. It's super simple to use and is exactly what I'm looking for, for my game. However I think I found a bug.

I have a 10x10 grid and I want to find the path from 1,1 to 1,8 (x = 1, y = 1), (x = 1, y = 8) but the path found is not correct in my opinion.

Here is the code which is based off the code in your tests:

var level = @"XXXXXXXXXX
              XOOXOOOOOX
              XOOXOOOOOX
              XOOOOOOOOX
              XXXOOOOOXX
              XOOOOOXOOX
              XOOOOOXOOX
              XOOOOOXOOX
              XOOXOOOOOX
              XXXXXXXXXX";

var _grid = new byte[10, 10];
var splitLevel = level.Split('\n')
                      .Select(x => x.Trim())
                      .ToList();

for (var x = 0; x < splitLevel.Count; x++)
{
    for (var y = 0; y < splitLevel[x].Length; y++)
    {
        if (splitLevel[x][y] != 'X')
        {
            _grid[x, y] = 1;
        }
    }
}

var pathfinder = new PathFinder(_grid, new PathFinderOptions
{                
    Diagonals = false,
    HeavyDiagonals = false
});
var path = pathfinder.FindPath(new Point(1, 1), new Point(1, 8));
Helper.Print(_grid, path);

The path found is not expected. The end path node is x = 8, y = 1.

0 0 0 0 0 0 0 0 0 0
0 * * 0 1 1 1 1 * 0
0 1 * 0 1 1 1 1 * 0
0 1 * * * * * * * 0
0 0 0 1 1 1 1 1 0 0
0 1 1 1 1 1 0 1 1 0
0 1 1 1 1 1 0 1 1 0
0 1 1 1 1 1 0 1 1 0
0 1 1 0 1 1 1 1 1 0
0 0 0 0 0 0 0 0 0 0

Path expected is x = 1, y = 8:

If I reverse the X,Y in the FindPath function I get the results expected:

pathfinder.FindPath(new Point(1, 1), new Point(8,1));

Expected path:

0 0 0 0 0 0 0 0 0 0
0 * 1 0 1 1 1 1 1 0
0 * 1 0 1 1 1 1 1 0
0 * * * 1 1 1 1 1 0
0 0 0 * 1 1 1 1 0 0
0 * * * 1 1 0 1 1 0
0 * 1 1 1 1 0 1 1 0
0 * 1 1 1 1 0 1 1 0
0 * 1 0 1 1 1 1 1 0
0 0 0 0 0 0 0 0 0 0

Am I missing something or is x and y reversed in the FindPath function? My thought is that dealing with a 2D array map it should be treated as Y = row, column = X, eg: map[y, x] in C# code.

Here is my mental model of how a grid works in C# as a 2D array where grid rows are Y and grid columns are X (eg. grid[y,x])

image

Ref:

public List<PathFinderNode> FindPath(Point start, Point end)

I see grid being used like this _mCalcGrid[start.X, start.Y] when I think it should be used like _mCalcGrid[start.Y, start.X]

There are other references that use it like grid[x,y]

Wait, PrintPath is doing what I expect, iterating over rows and columns eg grid[y,x] but your test code is grid[x,y]. The flipping of x and y is totally confusing me.

public static string PrintPath(byte[,] grid, List<PathFinderNode> path)
{
    var s = new StringBuilder();

    for (var row = 0; row < grid.GetLength(0); row++)
    {
        for (var column = 0; column < grid.GetLength(1); column++)
        {
            if (path.Any(n => n.X == row && n.Y == column))
            {
                s.Append("*");
            }
            else
            {
                s.Append(grid[row, column]);
            }
            s.Append(' ');
        }
        s.Append(Environment.NewLine);
    }
    return s.ToString();
}

Found it!!! X and Y are backwards here:

image

I am coming off the heels of the Point class defined by Microsoft where X and Y opposite

image

Not the path I expected

Hi!

Really enjoying this library but I do have a question.

Since 'punish changing direction' is on, I expected the black path, but got the white overlay instead:

image

Other then that really happy with it, and will probably keep using it anyway :) Just wondering if you happen to know an explanation.

Documentation update

var tiles = new short[] { { 1, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, };

For
var tiles = new short[,] { { 1, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, };

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.