GithubHelp home page GithubHelp logo

bgrins / javascript-astar Goto Github PK

View Code? Open in Web Editor NEW
1.3K 52.0 319.0 424 KB

A* Search / Pathfinding Algorithm in Javascript

Home Page: https://briangrinstead.com/blog/astar-search-algorithm-in-javascript-updated/

License: MIT License

JavaScript 77.81% HTML 5.41% CSS 16.78%

javascript-astar's Introduction

javascript-astar

An implementation of the A* Search Algorithm in JavaScript

See a demo at http://www.briangrinstead.com/files/astar/

Sample Usage

If you want just the A* search code (not the demo visualization), use code like this http://gist.github.com/581352

<script type='text/javascript' src='astar.js'></script>
<script type='text/javascript'>
	var graph = new Graph([
		[1,1,1,1],
		[0,1,1,0],
		[0,0,1,1]
	]);
	var start = graph.grid[0][0];
	var end = graph.grid[1][2];
	var result = astar.search(graph, start, end);
	// result is an array containing the shortest path
	var graphDiagonal = new Graph([
		[1,1,1,1],
		[0,1,1,0],
		[0,0,1,1]
	], { diagonal: true });
	
	var start = graphDiagonal.grid[0][0];
	var end = graphDiagonal.grid[1][2];
	var resultWithDiagonals = astar.search(graphDiagonal, start, end, { heuristic: astar.heuristics.diagonal });
	// Weight can easily be added by increasing the values within the graph, and where 0 is infinite (a wall)
	var graphWithWeight = new Graph([
		[1,1,2,30],
		[0,4,1.3,0],
		[0,0,5,1]
	]);
	var startWithWeight = graphWithWeight.grid[0][0];
	var endWithWeight = graphWithWeight.grid[1][2];
	var resultWithWeight = astar.search(graphWithWeight, startWithWeight, endWithWeight);
	// resultWithWeight is an array containing the shortest path taking into account the weight of a node
</script>

A few notes about weight values:

  1. A weight of 0 denotes a wall.
  2. A weight cannot be negative.
  3. A weight cannot be between 0 and 1 (exclusive).
  4. A weight can contain decimal values (greater than 1).

Original (slower) implementation

The original version of the algorithm used a list, and was a bit clearer but much slower. It was based off the original blog post. The code is available at: https://github.com/bgrins/javascript-astar/tree/0.0.1/original-implementation.

The newest version of the algorithm using a Binary Heap. It is quite faster than the original. http://www.briangrinstead.com/blog/astar-search-algorithm-in-javascript-updated Binary Heap taken from http://eloquentjavascript.net/appendix2.html (license: http://creativecommons.org/licenses/by/3.0/)

Running the test suite

Build Status

If you don't have grunt installed, follow the grunt getting started guide first.

Pull down the project, then run:

	npm install
	grunt

javascript-astar's People

Contributors

bgrins avatar dested avatar glutamatt avatar hankus avatar jdeniau avatar kornelski avatar leodutra avatar mannyc avatar taf2 avatar titi 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  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

javascript-astar's Issues

I don't get the shortest path

Here is my initial grid, startpoint, endpoint and astar path:
capture d ecran 2014-09-02 a 22 33 46

However, if I add an obstacle (4 blocks of the graph are turned to 0s), this is the path I get:
capture d ecran 2014-09-02 a 22 33 56

It is obviously a sub-optimal path (it shouldn't go all the way down before the obstacle), however A* is supposed to return the optimal path and I don't get how/why it fails there.

Tell me if you need more data.

Sub-optimal pathing

Hello,

I am making a game that uses your javascript-astar pathing library. It generally works very well, but once in a while I find that it does not provide the best path. For a long time I assumed it was my own code giving the issue, but today I sat down for an enormous amount of time and proved (I believe) that it is the library.

I did this by combing through the graph used to search for the best path. After adding up the weights, it can be seen that it is indeed choosing a sub-optimal path.

I have formatted the console output from Chrome (thanks, Vim!) in order to provide as much information as possible. Please note that I have removed any nodes that are not relevant to either path. It can be found here on Pastebin: http://pastebin.com/9dQM9aKc

The two screenshots below display the issue.

Chosen (sub-optimal) path, weight 7.55:
screen shot 2014-11-23 at 4 25 27 pm

Shorter (ignored) path, weight 6.9:
screen shot 2014-11-23 at 4 25 52 pm

To make the graph nodes easier to read, note that the chosen (longer) path is straight down, and so it consists of all of the nodes in the x=16 array. The other path consists of all the nodes from the x=17 array, plus the final node from x=16 (y=15).
Graph output:
http://pastebin.com/9dQM9aKc

Thanks! Please let me know what I can do to help.

Pathing randomly fails after first search. Pathfinder doesn't reset closed nodes after search is complete.

Subsequent searches on the same grid/graph will fail, since "closed" nodes are left over from the previous search.

Here is a fixed version of the "astar" method that saves off, and restores the closed nodes flags, before the path is returned.

`
var astar = {
/**
* Perform an A* Search on a graph given a start and end node.
* @param {Graph} graph
* @param {GridNode} start
* @param {GridNode} end
* @param {Object} [options]
* @param {bool} [options.closest] Specifies whether to return the
path to the closest node if the target is unreachable.
* @param {Function} [options.heuristic] Heuristic function (see
* astar.heuristics).
*/
search: function(graph, start, end, options) {
graph.cleanDirty();
options = options || {};
var heuristic = options.heuristic || astar.heuristics.manhattan,
closest = options.closest || false;

    var openHeap = getHeap(),
        closestNode = start; // set the start node to be the closest if required
    var closedList = [];
    start.h = heuristic(start, end);

    openHeap.push(start);

    while(openHeap.size() > 0) {

        // Grab the lowest f(x) to process next.  Heap keeps this sorted for us.
        var currentNode = openHeap.pop();

        // End case -- result has been found, return the traced path.
        if(currentNode === end) {
            while(closedList.length>0)closedList.pop().closed = false;
            return pathTo(currentNode);
        }

        // Normal case -- move currentNode from open to closed, process each of its neighbors.
        currentNode.closed = true;
        closedList.push(currentNode);

        // Find all neighbors for the current node.
        var neighbors = graph.neighbors(currentNode);

        for (var i = 0, il = neighbors.length; i < il; ++i) {
            var neighbor = neighbors[i];

            if (neighbor.closed || neighbor.isWall()) {
                // Not a valid node to process, skip to next neighbor.
                continue;
            }

            // The g score is the shortest distance from start to current node.
            // We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet.
            var gScore = currentNode.g + neighbor.getCost(currentNode),
                beenVisited = neighbor.visited;

            if (!beenVisited || gScore < neighbor.g) {

                // Found an optimal (so far) path to this node.  Take score for node to see how good it is.
                neighbor.visited = true;
                neighbor.parent = currentNode;
                neighbor.h = neighbor.h || heuristic(neighbor, end);
                neighbor.g = gScore;
                neighbor.f = neighbor.g + neighbor.h;
                graph.markDirty(neighbor);
                if (closest) {
                    // If the neighbour is closer than the current closestNode or if it's equally close but has
                    // a cheaper path than the current closest node then it becomes the closest node
                    if (neighbor.h < closestNode.h || (neighbor.h === closestNode.h && neighbor.g < closestNode.g)) {
                        closestNode = neighbor;
                    }
                }

                if (!beenVisited) {
                    // Pushing to heap will put it in proper place based on the 'f' value.
                    openHeap.push(neighbor);
                }
                else {
                    // Already seen the node, but since it has been rescored we need to reorder it in the heap
                    openHeap.rescoreElement(neighbor);
                }
            }
        }
    }
    while(closedList.length>0)closedList.pop().closed = false;

    if (closest) {
        return pathTo(closestNode);
    }

    // No result was found - empty array signifies failure to find path.
    return [];
},
// See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
heuristics: {
    manhattan: function(pos0, pos1) {
        var d1 = Math.abs(pos1.x - pos0.x);
        var d2 = Math.abs(pos1.y - pos0.y);
        return d1 + d2;
    },
    diagonal: function(pos0, pos1) {
        var D = 1;
        var D2 = Math.sqrt(2);
        var d1 = Math.abs(pos1.x - pos0.x);
        var d2 = Math.abs(pos1.y - pos0.y);
        return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2));
    }
},
cleanNode:function(node){
    node.f = 0;
    node.g = 0;
    node.h = 0;
    node.visited = false;
    node.closed = false;
    node.parent = null;
}

};
`

diagonal cost isn't taken into account

Hello

I've been using astar to make a game and I've discovered that diagonal movements are often favored although the route is longer.

From what I make out, weightings on diagonal movements should be slightly more than the given value as the diagonal distance across the sqaure is greater than up, down, left, right movements.

The solution (I think) would be to modify the node's cost to be: square root ( cost*cost + cost * cost).

However, I've not got this working correctly yet.

Does anyone have thoughts on this? Is it the correct thing to do? Is there a better solution?

Limiting number of squares moved

A lot of tactical strategy games have a “limited” movement area. For example, a character can only “step 6 squares” in any direction.

Is this possible to do with this fantastic piece of code?

help!!! Getting error in wechat game

I use the astar.js in browser is normal, but it will get errors in wechat game.
Graph is not defined ReferenceError: Graph is not defined

please help me!!!

[enhancement] Add missing bower.json.

Hey, maintainer(s) of bgrins/javascript-astar!

We at VersionEye are working hard to keep up the quality of the bower's registry.

We just finished our initial analysis of the quality of the Bower.io registry:

7530 - registered packages, 224 of them doesnt exists anymore;

We analysed 7306 existing packages and 1070 of them don't have bower.json on the master branch ( that's where a Bower client pulls a data ).

Sadly, your library bgrins/javascript-astar is one of them.

Can you spare 15 minutes to help us to make Bower better?

Just add a new file bower.json and change attributes.

{
  "name": "bgrins/javascript-astar",
  "version": "1.0.0",
  "main": "path/to/main.css",
  "description": "please add it",
  "license": "Eclipse",
  "ignore": [
    ".jshintrc",
    "**/*.txt"
  ],
  "dependencies": {
    "<dependency_name>": "<semantic_version>",
    "<dependency_name>": "<Local_folder>",
    "<dependency_name>": "<package>"
  },
  "devDependencies": {
    "<test-framework-name>": "<version>"
  }
}

Read more about bower.json on the official spefication and nodejs semver library has great examples of proper versioning.

NB! Please validate your bower.json with jsonlint before commiting your updates.

Thank you!

Timo,
twitter: @versioneye
email: [email protected]
VersionEye - no more legacy software!

Generating new map forgets diagonal path is selected

I was playing around with the Web version hosted by the author, and found that every time a new map is generated, the diagonal path check box has to be unselected and reselected for diagonal paths to work again.

Row-major order

It feels unconventional to store nodes in a column-major order: grid[x][y].

May I suggest a row-major order (grid[y][x]) instead, so we don't have to transpose the weight matrices?
Or maybe I'm the only one who finds this problematic?

fewest turns

is there a way to pass option which would set a penalty if the path changes directions in order to get a path with fewest turns? (I need to avoid the staircase effect)

Grid Array

Can you please inform everyone in the documentation that the x loop goes before the y loop because, It took me 3 days to realise that the array was in the wrong order and it was suggesting a wrong path

Publish to npm

You should definitely publish this to the npm registry.

Thanks for this, awesome work!

Reversed A Star?

Is it possible to reverse the algorithm to get the longest path?

The best way to deal with dynamic obstacles?

This isn't so much an issue as a question. D:

So, I'm using this in a game I'm working on (in Crafty.js) and have it working so that enemies follow the player. The problem is that they all choose almost the same path depending on where they are, resulting in them stacking on top of each other. I've tried recalculating the graph every time an enemy movies, and in turn recalculating the paths for each enemy. That somewhat worked, but ran pretty slow.

How would you suggest going about this?

Feature idea: Add weighting to graph nodes

Any interest in this feature? Basically, you can have a graph with different weights on locations. This would factor into the distance cost of a particular path and change the recommended path.

How what would the desired way to represent cost in the function call be?
Currently (1 is inaccessible, 0 is open):

var graph = new Graph([
        [0,0,0,0],
        [1,0,0,1],
        [1,1,0,0]
]);

Proposed (anything 0 is inaccessible, anything > 0 is a fixed cost based on the value - so a 2 node would simply add 2 onto the distance instead of 1):

var graph = new Graph([
        [0,0,0,0],
        [1,0,0,2],
        [1,3.5,0,0]
]);

Obviously this would break compatibility with the old implementation (since the meaning of walls change), but that could be documented for people updating.

Can't find a path after a few search

currentNode.closed = true
and didn't make it dirty so that when a new search begins, the function Graph.cleanDirty() may not reset the closed node, and finally it will result in no valid path

do a search and choose a nearby node for new target can easily reproduce it.

I added graph.makeDirty(currentNode) to fix it, but I didn't test it for a full round

Getting error in browser

astar.js:163 Uncaught TypeError: Cannot read property 'length' of undefined
at new Graph (astar.js:163)
at :7:13

New Graph is switching x and y values of my array items

I have an array which represents 49 rows of 91 columns, i.e:
0, 1, 2 .... 90
91, 92, 93 ...181
etc.
I've used a for loop to create temporary rows of 91 called rowArray and then pushed them into an array representing all 4459 cells called wholeArray. If I console.log whole array is looks to be in the same format as the astar demo requires:
0: (91) [1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0]
1: (91) [1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ...etc.
i.e the first array within the array represents the first row of my game grid.

However when I pass this array into new Graph and console.log the graph, I see that astar has switched the x and y values for some reason. Notice above that the first row of the inner array representing x locations (1, 1, 0, 1, 1, 1...) is now represented by an increasing y value (1, 1, 0, 1, 1, 1...). Can anyone suggest a fix for this please?

0: Array(91)
0: GridNode {x: 0, y: 0, weight: 1, f: 0, g: 0, …}
1: GridNode {x: 0, y: 1, weight: 1, f: 47, g: 1, …}
2: GridNode {x: 0, y: 2, weight: 0, f: 0, g: 0, …}
3: GridNode {x: 0, y: 3, weight: 1, f: 49, g: 5, …}
4: GridNode {x: 0, y: 4, weight: 1, f: 49, g: 6, …}
5: GridNode {x: 0, y: 5, weight: 1, f: 0, g: 0, …}
6: GridNode {x: 0, y: 6, weight: 0, f: 0, g: 0, …}
...

function pathFind(cellNumStart, cellNumEnd) { let wholeArray = []; let rowArray = []; let rowNumber = 0; for (let i = 0; i <= 4458; i++) { if (level1Array[i] == l11) {rowArray[rowNumber] = 1//grass } else if (level1Array[i] == l12 || level1Array[i] == l13) {rowArray[rowNumber] = 0//rock 3? } else if (level1Array[i] == l16) {rowArray[rowNumber] = 0//water 5? } else if (level1Array[i] == l17) {rowArray[rowNumber] = 0//deep water 0 } else {rowArray[rowNumber] = 0//tree 0 } if (rowNumber == 90) { wholeArray.push(rowArray); rowArray = []; rowNumber = -1; } rowNumber = rowNumber + 1; } let cellNumStartXY = convert91by49toXY(cellNumStart); let cellNumEndXY = convert91by49toXY(cellNumEnd); let x1 = cellNumStartXY[0] - 1; let y1 = cellNumStartXY[1] - 1; let x2 = cellNumEndXY[0] - 1; let y2 = cellNumEndXY[1] - 1; var graphWithWeight = new Graph( wholeArray ); console.log(wholeArray); console.log(graphWithWeight); var startWithWeight = graphWithWeight.grid[x1][y1]; var endWithWeight = graphWithWeight.grid[x2][y2]; var resultWithWeight = astar.search(graphWithWeight, startWithWeight, endWithWeight, closest); }

Note that the only slight oddity I can see is that my first array, when console logged, doesn't show commas between each of the inner arrays like in the demo code.

Replacing Binary Heap's indexOf

Hi,

I've chosen this implementation for a RTS game, optimized it for Mozilla's SpiderMonkey and got very promising results with weighting thousands of nodes in a matter of one digit millisecond. So even full map scans run in an acceptable time frame. IonMonkey compiles all hot function into native code and spends now most time inside the internal indexOf of the BinaryHeap.

Has there been an attempt to replace the content array with a key/value store to avoid continuously searching in an tens of thousands entries array?

how to design a heuristic function for non-grid unidirectional gragh?

We know A* is the most optimal algorithm for finding the shortest path, but I cannot see how any heuristic can work on a non lattice graph? This made me wonder whether or not A* is in fact capable of being used on an undirected or directed graph. If A* is capable of doing this, what kind of heuristic could one use? If A* is not, what is the current fastest algorithm for calculating the shortest path on a directed or undirected non lattice graph?

Update "dist" copies of code to latest?

Hello,

Just wanted to suggest updating the "astar-concat.js" and "astar-min.js" in the dist folder to the latest versions.

Took me longer than it probably should have to figure out why the minified copy of astar wasn't working with weighted graphs. (In hindsight the "updated x months" ago with a bit of a give away.)

Thanks

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.