pathfinding
Version:
Comprehensive pathfinding library for grid based games
182 lines (157 loc) • 6.86 kB
JavaScript
var Heap = require('heap');
var Util = require('../core/Util');
var Heuristic = require('../core/Heuristic');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* A* path-finder.
* based upon https://github.com/bgrins/javascript-astar
* @constructor
* @param {Object} opt
* @param {boolean} opt.allowDiagonal Whether diagonal movement is allowed.
* Deprecated, use diagonalMovement instead.
* @param {boolean} opt.dontCrossCorners Disallow diagonal movement touching
* block corners. Deprecated, use diagonalMovement instead.
* @param {DiagonalMovement} opt.diagonalMovement Allowed diagonal movement.
* @param {function} opt.heuristic Heuristic function to estimate the distance
* (defaults to manhattan).
* @param {number} opt.weight Weight to apply to the heuristic to allow for
* suboptimal paths, in order to speed up the search.
*/
function BiAStarFinder(opt) {
opt = opt || {};
this.allowDiagonal = opt.allowDiagonal;
this.dontCrossCorners = opt.dontCrossCorners;
this.diagonalMovement = opt.diagonalMovement;
this.heuristic = opt.heuristic || Heuristic.manhattan;
this.weight = opt.weight || 1;
if (!this.diagonalMovement) {
if (!this.allowDiagonal) {
this.diagonalMovement = DiagonalMovement.Never;
} else {
if (this.dontCrossCorners) {
this.diagonalMovement = DiagonalMovement.OnlyWhenNoObstacles;
} else {
this.diagonalMovement = DiagonalMovement.IfAtMostOneObstacle;
}
}
}
//When diagonal movement is allowed the manhattan heuristic is not admissible
//It should be octile instead
if (this.diagonalMovement === DiagonalMovement.Never) {
this.heuristic = opt.heuristic || Heuristic.manhattan;
} else {
this.heuristic = opt.heuristic || Heuristic.octile;
}
}
/**
* Find and return the the path.
* @return {Array<Array<number>>} The path, including both start and
* end positions.
*/
BiAStarFinder.prototype.findPath = function(startX, startY, endX, endY, grid) {
var cmp = function(nodeA, nodeB) {
return nodeA.f - nodeB.f;
},
startOpenList = new Heap(cmp),
endOpenList = new Heap(cmp),
startNode = grid.getNodeAt(startX, startY),
endNode = grid.getNodeAt(endX, endY),
heuristic = this.heuristic,
diagonalMovement = this.diagonalMovement,
weight = this.weight,
abs = Math.abs, SQRT2 = Math.SQRT2,
node, neighbors, neighbor, i, l, x, y, ng,
BY_START = 1, BY_END = 2;
// set the `g` and `f` value of the start node to be 0
// and push it into the start open list
startNode.g = 0;
startNode.f = 0;
startOpenList.push(startNode);
startNode.opened = BY_START;
// set the `g` and `f` value of the end node to be 0
// and push it into the open open list
endNode.g = 0;
endNode.f = 0;
endOpenList.push(endNode);
endNode.opened = BY_END;
// while both the open lists are not empty
while (!startOpenList.empty() && !endOpenList.empty()) {
// pop the position of start node which has the minimum `f` value.
node = startOpenList.pop();
node.closed = true;
// get neigbours of the current node
neighbors = grid.getNeighbors(node, diagonalMovement);
for (i = 0, l = neighbors.length; i < l; ++i) {
neighbor = neighbors[i];
if (neighbor.closed) {
continue;
}
if (neighbor.opened === BY_END) {
return Util.biBacktrace(node, neighbor);
}
x = neighbor.x;
y = neighbor.y;
// get the distance between current node and the neighbor
// and calculate the next g score
ng = node.g + ((x - node.x === 0 || y - node.y === 0) ? 1 : SQRT2);
// check if the neighbor has not been inspected yet, or
// can be reached with smaller cost from the current node
if (!neighbor.opened || ng < neighbor.g) {
neighbor.g = ng;
neighbor.h = neighbor.h ||
weight * heuristic(abs(x - endX), abs(y - endY));
neighbor.f = neighbor.g + neighbor.h;
neighbor.parent = node;
if (!neighbor.opened) {
startOpenList.push(neighbor);
neighbor.opened = BY_START;
} else {
// the neighbor can be reached with smaller cost.
// Since its f value has been updated, we have to
// update its position in the open list
startOpenList.updateItem(neighbor);
}
}
} // end for each neighbor
// pop the position of end node which has the minimum `f` value.
node = endOpenList.pop();
node.closed = true;
// get neigbours of the current node
neighbors = grid.getNeighbors(node, diagonalMovement);
for (i = 0, l = neighbors.length; i < l; ++i) {
neighbor = neighbors[i];
if (neighbor.closed) {
continue;
}
if (neighbor.opened === BY_START) {
return Util.biBacktrace(neighbor, node);
}
x = neighbor.x;
y = neighbor.y;
// get the distance between current node and the neighbor
// and calculate the next g score
ng = node.g + ((x - node.x === 0 || y - node.y === 0) ? 1 : SQRT2);
// check if the neighbor has not been inspected yet, or
// can be reached with smaller cost from the current node
if (!neighbor.opened || ng < neighbor.g) {
neighbor.g = ng;
neighbor.h = neighbor.h ||
weight * heuristic(abs(x - startX), abs(y - startY));
neighbor.f = neighbor.g + neighbor.h;
neighbor.parent = node;
if (!neighbor.opened) {
endOpenList.push(neighbor);
neighbor.opened = BY_END;
} else {
// the neighbor can be reached with smaller cost.
// Since its f value has been updated, we have to
// update its position in the open list
endOpenList.updateItem(neighbor);
}
}
} // end for each neighbor
} // end while not open list empty
// fail to find the path
return [];
};
module.exports = BiAStarFinder;