UNPKG

@screeps/pathfinding

Version:

Comprehensive pathfinding library for grid based games

167 lines (138 loc) 5.88 kB
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;