@screeps/pathfinding
Version:
Comprehensive pathfinding library for grid based games
167 lines (138 loc) • 5.88 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 {integer} opt.weight Weight to apply to the heuristic to allow for suboptimal paths,
* in order to speed up the search.
*/
function AStarFinder(opt) {
opt = opt || {};
this.allowDiagonal = opt.allowDiagonal;
this.dontCrossCorners = opt.dontCrossCorners;
this.heuristic = opt.heuristic || Heuristic.manhattan;
this.weight = opt.weight || 1;
this.maxOpsLimit = opt.maxOpsLimit;
this.diagonalMovement = opt.diagonalMovement;
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.<[number, number]>} The path, including both start and
* end positions.
*/
AStarFinder.prototype.findPath = function(startX, startY, endX, endY, grid) {
var openList = new Heap(function(nodeA, nodeB) {
return nodeA.f - nodeB.f;
}),
startNode = grid.getNodeAt(startX, startY),
endNode = endX != -999 ? grid.getNodeAt(endX, endY) : null,
heuristic = this.heuristic,
diagonalMovement = this.diagonalMovement,
weight = this.weight,
abs = Math.abs, SQRT2 = Math.SQRT2,
node, neighbors, neighbor, i, l, x, y, ng,
closestNode = startNode,
ops = 0,
nearTargetNode,
diagonalDirs = [[-1,-1],[1,-1],[1,1],[-1,1]];
// set the `g` and `f` value of the start node to be 0
startNode.g = 0;
startNode.f = 0;
startNode.h = endNode ? weight * heuristic(abs(startX - endX), abs(startY - endY)) : 0;
// push the start node into the open list
openList.push(startNode);
startNode.opened = true;
// while the open list is not empty
while (!openList.empty()) {
// pop the position of node which has the minimum `f` value.
node = openList.pop();
node.closed = true;
// if reached the end position, construct the path and return it
if (node === endNode || node.weight == 999) {
return Util.backtrace(node);
}
ops++;
if(this.maxOpsLimit && ops > this.maxOpsLimit)
break;
/*if(!endNode) {
nearTargetNode = null;
for(i=0; i<diagonalDirs.length; i++) {
x = node.x + diagonalDirs[i][0];
y = node.y + diagonalDirs[i][1];
if (grid.isInside(x, y) && grid.nodes[y][x].weight == 999) {
nearTargetNode = grid.nodes[y][x];
break;
}
}
if(nearTargetNode) {
var backtrace = Util.backtrace(node);
backtrace.push([nearTargetNode.x, nearTargetNode.y]);
return backtrace;
}
}*/
// 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;
}
x = neighbor.x;
y = neighbor.y;
// get the distance between current node and the neighbor
// and calculate the next g score
ng = node.g + neighbor.weight * ((x - node.x === 0 || y - node.y === 0) ? 1 : 1.00000001);
// 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.h < closestNode.h || (neighbor.h === closestNode.h && neighbor.g < closestNode.g)) {
closestNode = neighbor;
}
if (!neighbor.opened) {
openList.push(neighbor);
neighbor.opened = true;
} 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
openList.updateItem(neighbor);
}
}
} // end for each neighbor
} // end while not open list empty
return Util.backtrace(closestNode);
// fail to find the path
return [];
};
module.exports = AStarFinder;