UNPKG

pathfinding

Version:

Comprehensive pathfinding library for grid based games

210 lines (173 loc) 7.24 kB
var Util = require('../core/Util'); var Heuristic = require('../core/Heuristic'); var Node = require('../core/Node'); var DiagonalMovement = require('../core/DiagonalMovement'); /** * Iterative Deeping A Star (IDA*) path-finder. * * Recursion based on: * http://www.apl.jhu.edu/~hall/AI-Programming/IDA-Star.html * * Path retracing based on: * V. Nageshwara Rao, Vipin Kumar and K. Ramesh * "A Parallel Implementation of Iterative-Deeping-A*", January 1987. * ftp://ftp.cs.utexas.edu/.snapshot/hourly.1/pub/AI-Lab/tech-reports/UT-AI-TR-87-46.pdf * * @author Gerard Meier (www.gerardmeier.com) * * @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. * @param {boolean} opt.trackRecursion Whether to track recursion for * statistical purposes. * @param {number} opt.timeLimit Maximum execution time. Use <= 0 for infinite. */ function IDAStarFinder(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; this.trackRecursion = opt.trackRecursion || false; this.timeLimit = opt.timeLimit || Infinity; // Default: no time limit. 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. When an empty array is returned, either * no path is possible, or the maximum execution time is reached. * * @return {Array<Array<number>>} The path, including both start and * end positions. */ IDAStarFinder.prototype.findPath = function(startX, startY, endX, endY, grid) { // Used for statistics: var nodesVisited = 0; // Execution time limitation: var startTime = new Date().getTime(); // Heuristic helper: var h = function(a, b) { return this.heuristic(Math.abs(b.x - a.x), Math.abs(b.y - a.y)); }.bind(this); // Step cost from a to b: var cost = function(a, b) { return (a.x === b.x || a.y === b.y) ? 1 : Math.SQRT2; }; /** * IDA* search implementation. * * @param {Node} The node currently expanding from. * @param {number} Cost to reach the given node. * @param {number} Maximum search depth (cut-off value). * @param {Array<Array<number>>} The found route. * @param {number} Recursion depth. * * @return {Object} either a number with the new optimal cut-off depth, * or a valid node instance, in which case a path was found. */ var search = function(node, g, cutoff, route, depth) { nodesVisited++; // Enforce timelimit: if (this.timeLimit > 0 && new Date().getTime() - startTime > this.timeLimit * 1000) { // Enforced as "path-not-found". return Infinity; } var f = g + h(node, end) * this.weight; // We've searched too deep for this iteration. if (f > cutoff) { return f; } if (node == end) { route[depth] = [node.x, node.y]; return node; } var min, t, k, neighbour; var neighbours = grid.getNeighbors(node, this.diagonalMovement); // Sort the neighbours, gives nicer paths. But, this deviates // from the original algorithm - so I left it out. //neighbours.sort(function(a, b){ // return h(a, end) - h(b, end); //}); /*jshint -W084 *///Disable warning: Expected a conditional expression and instead saw an assignment for (k = 0, min = Infinity; neighbour = neighbours[k]; ++k) { /*jshint +W084 *///Enable warning: Expected a conditional expression and instead saw an assignment if (this.trackRecursion) { // Retain a copy for visualisation. Due to recursion, this // node may be part of other paths too. neighbour.retainCount = neighbour.retainCount + 1 || 1; if(neighbour.tested !== true) { neighbour.tested = true; } } t = search(neighbour, g + cost(node, neighbour), cutoff, route, depth + 1); if (t instanceof Node) { route[depth] = [node.x, node.y]; // For a typical A* linked list, this would work: // neighbour.parent = node; return t; } // Decrement count, then determine whether it's actually closed. if (this.trackRecursion && (--neighbour.retainCount) === 0) { neighbour.tested = false; } if (t < min) { min = t; } } return min; }.bind(this); // Node instance lookups: var start = grid.getNodeAt(startX, startY); var end = grid.getNodeAt(endX, endY); // Initial search depth, given the typical heuristic contraints, // there should be no cheaper route possible. var cutOff = h(start, end); var j, route, t; // With an overflow protection. for (j = 0; true; ++j) { route = []; // Search till cut-off depth: t = search(start, 0, cutOff, route, 0); // Route not possible, or not found in time limit. if (t === Infinity) { return []; } // If t is a node, it's also the end node. Route is now // populated with a valid path to the end node. if (t instanceof Node) { return route; } // Try again, this time with a deeper cut-off. The t score // is the closest we got to the end node. cutOff = t; } // This _should_ never to be reached. return []; }; module.exports = IDAStarFinder;