main
Ogoun 5 months ago
parent 79403479bc
commit aff5c617c5

Binary file not shown.

Binary file not shown.

@ -0,0 +1,18 @@
module.exports = {
'Heap' : require('heap'),
'Node' : require('./core/Node'),
'Grid' : require('./core/Grid'),
'Util' : require('./core/Util'),
'DiagonalMovement' : require('./core/DiagonalMovement'),
'Heuristic' : require('./core/Heuristic'),
'AStarFinder' : require('./finders/AStarFinder'),
'BestFirstFinder' : require('./finders/BestFirstFinder'),
'BreadthFirstFinder' : require('./finders/BreadthFirstFinder'),
'DijkstraFinder' : require('./finders/DijkstraFinder'),
'BiAStarFinder' : require('./finders/BiAStarFinder'),
'BiBestFirstFinder' : require('./finders/BiBestFirstFinder'),
'BiBreadthFirstFinder' : require('./finders/BiBreadthFirstFinder'),
'BiDijkstraFinder' : require('./finders/BiDijkstraFinder'),
'IDAStarFinder' : require('./finders/IDAStarFinder'),
'JumpPointFinder' : require('./finders/JumpPointFinder'),
};

@ -0,0 +1,9 @@
/**
* ____ _ _ _____ _ _ _ _
* | _ \ __ _| |_| |__ | ___(_)_ __ __| (_)_ __ __ _ (_)___
* | |_) / _` | __| '_ \| |_ | | '_ \ / _` | | '_ \ / _` | | / __|
* | __/ (_| | |_| | | | _| | | | | | (_| | | | | | (_| |_ | \__ \
* |_| \__,_|\__|_| |_|_| |_|_| |_|\__,_|_|_| |_|\__, (_)/ |___/
* |___/ |__/
* https://github.com/qiao/PathFinding.js
*/

@ -0,0 +1,8 @@
var DiagonalMovement = {
Always: 1,
Never: 2,
IfAtMostOneObstacle: 3,
OnlyWhenNoObstacles: 4
};
module.exports = DiagonalMovement;

@ -0,0 +1,247 @@
var Node = require('./Node');
var DiagonalMovement = require('./DiagonalMovement');
/**
* The Grid class, which serves as the encapsulation of the layout of the nodes.
* @constructor
* @param {number|Array.<Array.<(number|boolean)>>} width_or_matrix Number of columns of the grid, or matrix
* @param {number} height Number of rows of the grid.
* @param {Array.<Array.<(number|boolean)>>} [matrix] - A 0-1 matrix
* representing the walkable status of the nodes(0 or false for walkable).
* If the matrix is not supplied, all the nodes will be walkable. */
function Grid(width_or_matrix, height, matrix) {
var width;
if (typeof width_or_matrix !== 'object') {
width = width_or_matrix;
} else {
height = width_or_matrix.length;
width = width_or_matrix[0].length;
matrix = width_or_matrix;
}
/**
* The number of columns of the grid.
* @type number
*/
this.width = width;
/**
* The number of rows of the grid.
* @type number
*/
this.height = height;
/**
* A 2D array of nodes.
*/
this.nodes = this._buildNodes(width, height, matrix);
}
/**
* Build and return the nodes.
* @private
* @param {number} width
* @param {number} height
* @param {Array.<Array.<number|boolean>>} [matrix] - A 0-1 matrix representing
* the walkable status of the nodes.
* @see Grid
*/
Grid.prototype._buildNodes = function(width, height, matrix) {
var i, j,
nodes = new Array(height),
row;
for (i = 0; i < height; ++i) {
nodes[i] = new Array(width);
for (j = 0; j < width; ++j) {
nodes[i][j] = new Node(j, i);
}
}
if (matrix === undefined) {
return nodes;
}
if (matrix.length !== height || matrix[0].length !== width) {
throw new Error('Matrix size does not fit');
}
for (i = 0; i < height; ++i) {
for (j = 0; j < width; ++j) {
if (matrix[i][j]) {
// 0, false, null will be walkable
// while others will be un-walkable
nodes[i][j].walkable = false;
}
}
}
return nodes;
};
Grid.prototype.getNodeAt = function(x, y) {
return this.nodes[y][x];
};
/**
* Determine whether the node at the given position is walkable.
* (Also returns false if the position is outside the grid.)
* @param {number} x - The x coordinate of the node.
* @param {number} y - The y coordinate of the node.
* @return {boolean} - The walkability of the node.
*/
Grid.prototype.isWalkableAt = function(x, y) {
return this.isInside(x, y) && this.nodes[y][x].walkable;
};
/**
* Determine whether the position is inside the grid.
* XXX: `grid.isInside(x, y)` is wierd to read.
* It should be `(x, y) is inside grid`, but I failed to find a better
* name for this method.
* @param {number} x
* @param {number} y
* @return {boolean}
*/
Grid.prototype.isInside = function(x, y) {
return (x >= 0 && x < this.width) && (y >= 0 && y < this.height);
};
/**
* Set whether the node on the given position is walkable.
* NOTE: throws exception if the coordinate is not inside the grid.
* @param {number} x - The x coordinate of the node.
* @param {number} y - The y coordinate of the node.
* @param {boolean} walkable - Whether the position is walkable.
*/
Grid.prototype.setWalkableAt = function(x, y, walkable) {
this.nodes[y][x].walkable = walkable;
};
/**
* Get the neighbors of the given node.
*
* offsets diagonalOffsets:
* +---+---+---+ +---+---+---+
* | | 0 | | | 0 | | 1 |
* +---+---+---+ +---+---+---+
* | 3 | | 1 | | | | |
* +---+---+---+ +---+---+---+
* | | 2 | | | 3 | | 2 |
* +---+---+---+ +---+---+---+
*
* When allowDiagonal is true, if offsets[i] is valid, then
* diagonalOffsets[i] and
* diagonalOffsets[(i + 1) % 4] is valid.
* @param {Node} node
* @param {DiagonalMovement} diagonalMovement
*/
Grid.prototype.getNeighbors = function(node, diagonalMovement) {
var x = node.x,
y = node.y,
neighbors = [],
s0 = false, d0 = false,
s1 = false, d1 = false,
s2 = false, d2 = false,
s3 = false, d3 = false,
nodes = this.nodes;
// ↑
if (this.isWalkableAt(x, y - 1)) {
neighbors.push(nodes[y - 1][x]);
s0 = true;
}
// →
if (this.isWalkableAt(x + 1, y)) {
neighbors.push(nodes[y][x + 1]);
s1 = true;
}
// ↓
if (this.isWalkableAt(x, y + 1)) {
neighbors.push(nodes[y + 1][x]);
s2 = true;
}
// ←
if (this.isWalkableAt(x - 1, y)) {
neighbors.push(nodes[y][x - 1]);
s3 = true;
}
if (diagonalMovement === DiagonalMovement.Never) {
return neighbors;
}
if (diagonalMovement === DiagonalMovement.OnlyWhenNoObstacles) {
d0 = s3 && s0;
d1 = s0 && s1;
d2 = s1 && s2;
d3 = s2 && s3;
} else if (diagonalMovement === DiagonalMovement.IfAtMostOneObstacle) {
d0 = s3 || s0;
d1 = s0 || s1;
d2 = s1 || s2;
d3 = s2 || s3;
} else if (diagonalMovement === DiagonalMovement.Always) {
d0 = true;
d1 = true;
d2 = true;
d3 = true;
} else {
throw new Error('Incorrect value of diagonalMovement');
}
// ↖
if (d0 && this.isWalkableAt(x - 1, y - 1)) {
neighbors.push(nodes[y - 1][x - 1]);
}
// ↗
if (d1 && this.isWalkableAt(x + 1, y - 1)) {
neighbors.push(nodes[y - 1][x + 1]);
}
// ↘
if (d2 && this.isWalkableAt(x + 1, y + 1)) {
neighbors.push(nodes[y + 1][x + 1]);
}
// ↙
if (d3 && this.isWalkableAt(x - 1, y + 1)) {
neighbors.push(nodes[y + 1][x - 1]);
}
return neighbors;
};
/**
* Get a clone of this grid.
* @return {Grid} Cloned grid.
*/
Grid.prototype.clone = function() {
var i, j,
width = this.width,
height = this.height,
thisNodes = this.nodes,
newGrid = new Grid(width, height),
newNodes = new Array(height),
row;
for (i = 0; i < height; ++i) {
newNodes[i] = new Array(width);
for (j = 0; j < width; ++j) {
newNodes[i][j] = new Node(j, i, thisNodes[i][j].walkable);
}
}
newGrid.nodes = newNodes;
return newGrid;
};
module.exports = Grid;

@ -0,0 +1,48 @@
/**
* @namespace PF.Heuristic
* @description A collection of heuristic functions.
*/
module.exports = {
/**
* Manhattan distance.
* @param {number} dx - Difference in x.
* @param {number} dy - Difference in y.
* @return {number} dx + dy
*/
manhattan: function(dx, dy) {
return dx + dy;
},
/**
* Euclidean distance.
* @param {number} dx - Difference in x.
* @param {number} dy - Difference in y.
* @return {number} sqrt(dx * dx + dy * dy)
*/
euclidean: function(dx, dy) {
return Math.sqrt(dx * dx + dy * dy);
},
/**
* Octile distance.
* @param {number} dx - Difference in x.
* @param {number} dy - Difference in y.
* @return {number} sqrt(dx * dx + dy * dy) for grids
*/
octile: function(dx, dy) {
var F = Math.SQRT2 - 1;
return (dx < dy) ? F * dx + dy : F * dy + dx;
},
/**
* Chebyshev distance.
* @param {number} dx - Difference in x.
* @param {number} dy - Difference in y.
* @return {number} max(dx, dy)
*/
chebyshev: function(dx, dy) {
return Math.max(dx, dy);
}
};

@ -0,0 +1,28 @@
/**
* A node in grid.
* This class holds some basic information about a node and custom
* attributes may be added, depending on the algorithms' needs.
* @constructor
* @param {number} x - The x coordinate of the node on the grid.
* @param {number} y - The y coordinate of the node on the grid.
* @param {boolean} [walkable] - Whether this node is walkable.
*/
function Node(x, y, walkable) {
/**
* The x coordinate of the node on the grid.
* @type number
*/
this.x = x;
/**
* The y coordinate of the node on the grid.
* @type number
*/
this.y = y;
/**
* Whether this node can be walked through.
* @type boolean
*/
this.walkable = (walkable === undefined ? true : walkable);
}
module.exports = Node;

@ -0,0 +1,246 @@
/**
* Backtrace according to the parent records and return the path.
* (including both start and end nodes)
* @param {Node} node End node
* @return {Array.<Array.<number>>} the path
*/
function backtrace(node) {
var path = [[node.x, node.y]];
while (node.parent) {
node = node.parent;
path.push([node.x, node.y]);
}
return path.reverse();
}
exports.backtrace = backtrace;
/**
* Backtrace from start and end node, and return the path.
* (including both start and end nodes)
* @param {Node}
* @param {Node}
*/
function biBacktrace(nodeA, nodeB) {
var pathA = backtrace(nodeA),
pathB = backtrace(nodeB);
return pathA.concat(pathB.reverse());
}
exports.biBacktrace = biBacktrace;
/**
* Compute the length of the path.
* @param {Array.<Array.<number>>} path The path
* @return {number} The length of the path
*/
function pathLength(path) {
var i, sum = 0, a, b, dx, dy;
for (i = 1; i < path.length; ++i) {
a = path[i - 1];
b = path[i];
dx = a[0] - b[0];
dy = a[1] - b[1];
sum += Math.sqrt(dx * dx + dy * dy);
}
return sum;
}
exports.pathLength = pathLength;
/**
* Given the start and end coordinates, return all the coordinates lying
* on the line formed by these coordinates, based on Bresenham's algorithm.
* http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Simplification
* @param {number} x0 Start x coordinate
* @param {number} y0 Start y coordinate
* @param {number} x1 End x coordinate
* @param {number} y1 End y coordinate
* @return {Array.<Array.<number>>} The coordinates on the line
*/
function interpolate(x0, y0, x1, y1) {
var abs = Math.abs,
line = [],
sx, sy, dx, dy, err, e2;
dx = abs(x1 - x0);
dy = abs(y1 - y0);
sx = (x0 < x1) ? 1 : -1;
sy = (y0 < y1) ? 1 : -1;
err = dx - dy;
while (true) {
line.push([x0, y0]);
if (x0 === x1 && y0 === y1) {
break;
}
e2 = 2 * err;
if (e2 > -dy) {
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx) {
err = err + dx;
y0 = y0 + sy;
}
}
return line;
}
exports.interpolate = interpolate;
/**
* Given a compressed path, return a new path that has all the segments
* in it interpolated.
* @param {Array.<Array.<number>>} path The path
* @return {Array.<Array.<number>>} expanded path
*/
function expandPath(path) {
var expanded = [],
len = path.length,
coord0, coord1,
interpolated,
interpolatedLen,
i, j;
if (len < 2) {
return expanded;
}
for (i = 0; i < len - 1; ++i) {
coord0 = path[i];
coord1 = path[i + 1];
interpolated = interpolate(coord0[0], coord0[1], coord1[0], coord1[1]);
interpolatedLen = interpolated.length;
for (j = 0; j < interpolatedLen - 1; ++j) {
expanded.push(interpolated[j]);
}
}
expanded.push(path[len - 1]);
return expanded;
}
exports.expandPath = expandPath;
/**
* Smoothen the give path.
* The original path will not be modified; a new path will be returned.
* @param {PF.Grid} grid
* @param {Array.<Array.<number>>} path The path
*/
function smoothenPath(grid, path) {
var len = path.length,
x0 = path[0][0], // path start x
y0 = path[0][1], // path start y
x1 = path[len - 1][0], // path end x
y1 = path[len - 1][1], // path end y
sx, sy, // current start coordinate
ex, ey, // current end coordinate
newPath,
i, j, coord, line, testCoord, blocked;
sx = x0;
sy = y0;
newPath = [[sx, sy]];
for (i = 2; i < len; ++i) {
coord = path[i];
ex = coord[0];
ey = coord[1];
line = interpolate(sx, sy, ex, ey);
blocked = false;
for (j = 1; j < line.length; ++j) {
testCoord = line[j];
if (!grid.isWalkableAt(testCoord[0], testCoord[1])) {
blocked = true;
break;
}
}
if (blocked) {
lastValidCoord = path[i - 1];
newPath.push(lastValidCoord);
sx = lastValidCoord[0];
sy = lastValidCoord[1];
}
}
newPath.push([x1, y1]);
return newPath;
}
exports.smoothenPath = smoothenPath;
/**
* Compress a path, remove redundant nodes without altering the shape
* The original path is not modified
* @param {Array.<Array.<number>>} path The path
* @return {Array.<Array.<number>>} The compressed path
*/
function compressPath(path) {
// nothing to compress
if(path.length < 3) {
return path;
}
var compressed = [],
sx = path[0][0], // start x
sy = path[0][1], // start y
px = path[1][0], // second point x
py = path[1][1], // second point y
dx = px - sx, // direction between the two points
dy = py - sy, // direction between the two points
lx, ly,
ldx, ldy,
sq, i;
// normalize the direction
sq = Math.sqrt(dx*dx + dy*dy);
dx /= sq;
dy /= sq;
// start the new path
compressed.push([sx,sy]);
for(i = 2; i < path.length; i++) {
// store the last point
lx = px;
ly = py;
// store the last direction
ldx = dx;
ldy = dy;
// next point
px = path[i][0];
py = path[i][1];
// next direction
dx = px - lx;
dy = py - ly;
// normalize
sq = Math.sqrt(dx*dx + dy*dy);
dx /= sq;
dy /= sq;
// if the direction has changed, store the point
if ( dx !== ldx || dy !== ldy ) {
compressed.push([lx,ly]);
}
}
// store the last point
compressed.push([px,py]);
return compressed;
}
exports.compressPath = compressPath;

@ -0,0 +1,125 @@
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.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 = 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;
// set the `g` and `f` value of the start node to be 0
startNode.g = 0;
startNode.f = 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) {
return Util.backtrace(endNode);
}
// 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 + ((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) {
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
// fail to find the path
return [];
};
module.exports = AStarFinder;

@ -0,0 +1,26 @@
var AStarFinder = require('./AStarFinder');
/**
* Best-First-Search path-finder.
* @constructor
* @extends AStarFinder
* @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).
*/
function BestFirstFinder(opt) {
AStarFinder.call(this, opt);
var orig = this.heuristic;
this.heuristic = function(dx, dy) {
return orig(dx, dy) * 1000000;
};
}
BestFirstFinder.prototype = new AStarFinder();
BestFirstFinder.prototype.constructor = BestFirstFinder;
module.exports = BestFirstFinder;

@ -0,0 +1,177 @@
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 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.<[number, 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;

@ -0,0 +1,26 @@
var BiAStarFinder = require('./BiAStarFinder');
/**
* Bi-direcitional Best-First-Search path-finder.
* @constructor
* @extends BiAStarFinder
* @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).
*/
function BiBestFirstFinder(opt) {
BiAStarFinder.call(this, opt);
var orig = this.heuristic;
this.heuristic = function(dx, dy) {
return orig(dx, dy) * 1000000;
};
}
BiBestFirstFinder.prototype = new BiAStarFinder();
BiBestFirstFinder.prototype.constructor = BiBestFirstFinder;
module.exports = BiBestFirstFinder;

@ -0,0 +1,113 @@
var Util = require('../core/Util');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* Bi-directional Breadth-First-Search path finder.
* @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.
*/
function BiBreadthFirstFinder(opt) {
opt = opt || {};
this.allowDiagonal = opt.allowDiagonal;
this.dontCrossCorners = opt.dontCrossCorners;
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;
}
}
}
}
/**
* Find and return the the path.
* @return {Array.<[number, number]>} The path, including both start and
* end positions.
*/
BiBreadthFirstFinder.prototype.findPath = function(startX, startY, endX, endY, grid) {
var startNode = grid.getNodeAt(startX, startY),
endNode = grid.getNodeAt(endX, endY),
startOpenList = [], endOpenList = [],
neighbors, neighbor, node,
diagonalMovement = this.diagonalMovement,
BY_START = 0, BY_END = 1,
i, l;
// push the start and end nodes into the queues
startOpenList.push(startNode);
startNode.opened = true;
startNode.by = BY_START;
endOpenList.push(endNode);
endNode.opened = true;
endNode.by = BY_END;
// while both the queues are not empty
while (startOpenList.length && endOpenList.length) {
// expand start open list
node = startOpenList.shift();
node.closed = true;
neighbors = grid.getNeighbors(node, diagonalMovement);
for (i = 0, l = neighbors.length; i < l; ++i) {
neighbor = neighbors[i];
if (neighbor.closed) {
continue;
}
if (neighbor.opened) {
// if this node has been inspected by the reversed search,
// then a path is found.
if (neighbor.by === BY_END) {
return Util.biBacktrace(node, neighbor);
}
continue;
}
startOpenList.push(neighbor);
neighbor.parent = node;
neighbor.opened = true;
neighbor.by = BY_START;
}
// expand end open list
node = endOpenList.shift();
node.closed = true;
neighbors = grid.getNeighbors(node, diagonalMovement);
for (i = 0, l = neighbors.length; i < l; ++i) {
neighbor = neighbors[i];
if (neighbor.closed) {
continue;
}
if (neighbor.opened) {
if (neighbor.by === BY_START) {
return Util.biBacktrace(neighbor, node);
}
continue;
}
endOpenList.push(neighbor);
neighbor.parent = node;
neighbor.opened = true;
neighbor.by = BY_END;
}
}
// fail to find the path
return [];
};
module.exports = BiBreadthFirstFinder;

@ -0,0 +1,22 @@
var BiAStarFinder = require('./BiAStarFinder');
/**
* Bi-directional Dijkstra path-finder.
* @constructor
* @extends BiAStarFinder
* @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.
*/
function BiDijkstraFinder(opt) {
BiAStarFinder.call(this, opt);
this.heuristic = function(dx, dy) {
return 0;
};
}
BiDijkstraFinder.prototype = new BiAStarFinder();
BiDijkstraFinder.prototype.constructor = BiDijkstraFinder;
module.exports = BiDijkstraFinder;

@ -0,0 +1,77 @@
var Util = require('../core/Util');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* Breadth-First-Search path finder.
* @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.
*/
function BreadthFirstFinder(opt) {
opt = opt || {};
this.allowDiagonal = opt.allowDiagonal;
this.dontCrossCorners = opt.dontCrossCorners;
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;
}
}
}
}
/**
* Find and return the the path.
* @return {Array.<[number, number]>} The path, including both start and
* end positions.
*/
BreadthFirstFinder.prototype.findPath = function(startX, startY, endX, endY, grid) {
var openList = [],
diagonalMovement = this.diagonalMovement,
startNode = grid.getNodeAt(startX, startY),
endNode = grid.getNodeAt(endX, endY),
neighbors, neighbor, node, i, l;
// push the start pos into the queue
openList.push(startNode);
startNode.opened = true;
// while the queue is not empty
while (openList.length) {
// take the front node from the queue
node = openList.shift();
node.closed = true;
// reached the end position
if (node === endNode) {
return Util.backtrace(endNode);
}
neighbors = grid.getNeighbors(node, diagonalMovement);
for (i = 0, l = neighbors.length; i < l; ++i) {
neighbor = neighbors[i];
// skip this neighbor if it has been inspected before
if (neighbor.closed || neighbor.opened) {
continue;
}
openList.push(neighbor);
neighbor.opened = true;
neighbor.parent = node;
}
}
// fail to find the path
return [];
};
module.exports = BreadthFirstFinder;

@ -0,0 +1,22 @@
var AStarFinder = require('./AStarFinder');
/**
* Dijkstra path-finder.
* @constructor
* @extends AStarFinder
* @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.
*/
function DijkstraFinder(opt) {
AStarFinder.call(this, opt);
this.heuristic = function(dx, dy) {
return 0;
};
}
DijkstraFinder.prototype = new AStarFinder();
DijkstraFinder.prototype.constructor = DijkstraFinder;
module.exports = DijkstraFinder;

@ -0,0 +1,208 @@
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 {integer} opt.weight Weight to apply to the heuristic to allow for suboptimal paths,
* in order to speed up the search.
* @param {object} opt.trackRecursion Whether to track recursion for statistical purposes.
* @param {object} 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.<[number, 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.<[number, 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) {
//console.log("Iteration: " + j + ", search cut-off value: " + cutOff + ", nodes visited thus far: " + nodesVisited + ".");
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) {
//console.log("Finished at iteration: " + j + ", search cut-off value: " + cutOff + ", nodes visited: " + nodesVisited + ".");
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;

@ -0,0 +1,149 @@
/**
* @author imor / https://github.com/imor
*/
var JumpPointFinderBase = require('./JumpPointFinderBase');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* Path finder using the Jump Point Search algorithm which always moves
* diagonally irrespective of the number of obstacles.
*/
function JPFAlwaysMoveDiagonally(opt) {
JumpPointFinderBase.call(this, opt);
}
JPFAlwaysMoveDiagonally.prototype = new JumpPointFinderBase();
JPFAlwaysMoveDiagonally.prototype.constructor = JPFAlwaysMoveDiagonally;
/**
* Search recursively in the direction (parent -> child), stopping only when a
* jump point is found.
* @protected
* @return {Array.<[number, number]>} The x, y coordinate of the jump point
* found, or null if not found
*/
JPFAlwaysMoveDiagonally.prototype._jump = function(x, y, px, py) {
var grid = this.grid,
dx = x - px, dy = y - py;
if (!grid.isWalkableAt(x, y)) {
return null;
}
if(this.trackJumpRecursion === true) {
grid.getNodeAt(x, y).tested = true;
}
if (grid.getNodeAt(x, y) === this.endNode) {
return [x, y];
}
// check for forced neighbors
// along the diagonal
if (dx !== 0 && dy !== 0) {
if ((grid.isWalkableAt(x - dx, y + dy) && !grid.isWalkableAt(x - dx, y)) ||
(grid.isWalkableAt(x + dx, y - dy) && !grid.isWalkableAt(x, y - dy))) {
return [x, y];
}
// when moving diagonally, must check for vertical/horizontal jump points
if (this._jump(x + dx, y, x, y) || this._jump(x, y + dy, x, y)) {
return [x, y];
}
}
// horizontally/vertically
else {
if( dx !== 0 ) { // moving along x
if((grid.isWalkableAt(x + dx, y + 1) && !grid.isWalkableAt(x, y + 1)) ||
(grid.isWalkableAt(x + dx, y - 1) && !grid.isWalkableAt(x, y - 1))) {
return [x, y];
}
}
else {
if((grid.isWalkableAt(x + 1, y + dy) && !grid.isWalkableAt(x + 1, y)) ||
(grid.isWalkableAt(x - 1, y + dy) && !grid.isWalkableAt(x - 1, y))) {
return [x, y];
}
}
}
return this._jump(x + dx, y + dy, x, y);
};
/**
* Find the neighbors for the given node. If the node has a parent,
* prune the neighbors based on the jump point search algorithm, otherwise
* return all available neighbors.
* @return {Array.<[number, number]>} The neighbors found.
*/
JPFAlwaysMoveDiagonally.prototype._findNeighbors = function(node) {
var parent = node.parent,
x = node.x, y = node.y,
grid = this.grid,
px, py, nx, ny, dx, dy,
neighbors = [], neighborNodes, neighborNode, i, l;
// directed pruning: can ignore most neighbors, unless forced.
if (parent) {
px = parent.x;
py = parent.y;
// get the normalized direction of travel
dx = (x - px) / Math.max(Math.abs(x - px), 1);
dy = (y - py) / Math.max(Math.abs(y - py), 1);
// search diagonally
if (dx !== 0 && dy !== 0) {
if (grid.isWalkableAt(x, y + dy)) {
neighbors.push([x, y + dy]);
}
if (grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y]);
}
if (grid.isWalkableAt(x + dx, y + dy)) {
neighbors.push([x + dx, y + dy]);
}
if (!grid.isWalkableAt(x - dx, y)) {
neighbors.push([x - dx, y + dy]);
}
if (!grid.isWalkableAt(x, y - dy)) {
neighbors.push([x + dx, y - dy]);
}
}
// search horizontally/vertically
else {
if(dx === 0) {
if (grid.isWalkableAt(x, y + dy)) {
neighbors.push([x, y + dy]);
}
if (!grid.isWalkableAt(x + 1, y)) {
neighbors.push([x + 1, y + dy]);
}
if (!grid.isWalkableAt(x - 1, y)) {
neighbors.push([x - 1, y + dy]);
}
}
else {
if (grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y]);
}
if (!grid.isWalkableAt(x, y + 1)) {
neighbors.push([x + dx, y + 1]);
}
if (!grid.isWalkableAt(x, y - 1)) {
neighbors.push([x + dx, y - 1]);
}
}
}
}
// return all neighbors
else {
neighborNodes = grid.getNeighbors(node, DiagonalMovement.Always);
for (i = 0, l = neighborNodes.length; i < l; ++i) {
neighborNode = neighborNodes[i];
neighbors.push([neighborNode.x, neighborNode.y]);
}
}
return neighbors;
};
module.exports = JPFAlwaysMoveDiagonally;

@ -0,0 +1,155 @@
/**
* @author imor / https://github.com/imor
*/
var JumpPointFinderBase = require('./JumpPointFinderBase');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* Path finder using the Jump Point Search algorithm which moves
* diagonally only when there is at most one obstacle.
*/
function JPFMoveDiagonallyIfAtMostOneObstacle(opt) {
JumpPointFinderBase.call(this, opt);
}
JPFMoveDiagonallyIfAtMostOneObstacle.prototype = new JumpPointFinderBase();
JPFMoveDiagonallyIfAtMostOneObstacle.prototype.constructor = JPFMoveDiagonallyIfAtMostOneObstacle;
/**
* Search recursively in the direction (parent -> child), stopping only when a
* jump point is found.
* @protected
* @return {Array.<[number, number]>} The x, y coordinate of the jump point
* found, or null if not found
*/
JPFMoveDiagonallyIfAtMostOneObstacle.prototype._jump = function(x, y, px, py) {
var grid = this.grid,
dx = x - px, dy = y - py;
if (!grid.isWalkableAt(x, y)) {
return null;
}
if(this.trackJumpRecursion === true) {
grid.getNodeAt(x, y).tested = true;
}
if (grid.getNodeAt(x, y) === this.endNode) {
return [x, y];
}
// check for forced neighbors
// along the diagonal
if (dx !== 0 && dy !== 0) {
if ((grid.isWalkableAt(x - dx, y + dy) && !grid.isWalkableAt(x - dx, y)) ||
(grid.isWalkableAt(x + dx, y - dy) && !grid.isWalkableAt(x, y - dy))) {
return [x, y];
}
// when moving diagonally, must check for vertical/horizontal jump points
if (this._jump(x + dx, y, x, y) || this._jump(x, y + dy, x, y)) {
return [x, y];
}
}
// horizontally/vertically
else {
if( dx !== 0 ) { // moving along x
if((grid.isWalkableAt(x + dx, y + 1) && !grid.isWalkableAt(x, y + 1)) ||
(grid.isWalkableAt(x + dx, y - 1) && !grid.isWalkableAt(x, y - 1))) {
return [x, y];
}
}
else {
if((grid.isWalkableAt(x + 1, y + dy) && !grid.isWalkableAt(x + 1, y)) ||
(grid.isWalkableAt(x - 1, y + dy) && !grid.isWalkableAt(x - 1, y))) {
return [x, y];
}
}
}
// moving diagonally, must make sure one of the vertical/horizontal
// neighbors is open to allow the path
if (grid.isWalkableAt(x + dx, y) || grid.isWalkableAt(x, y + dy)) {
return this._jump(x + dx, y + dy, x, y);
} else {
return null;
}
};
/**
* Find the neighbors for the given node. If the node has a parent,
* prune the neighbors based on the jump point search algorithm, otherwise
* return all available neighbors.
* @return {Array.<[number, number]>} The neighbors found.
*/
JPFMoveDiagonallyIfAtMostOneObstacle.prototype._findNeighbors = function(node) {
var parent = node.parent,
x = node.x, y = node.y,
grid = this.grid,
px, py, nx, ny, dx, dy,
neighbors = [], neighborNodes, neighborNode, i, l;
// directed pruning: can ignore most neighbors, unless forced.
if (parent) {
px = parent.x;
py = parent.y;
// get the normalized direction of travel
dx = (x - px) / Math.max(Math.abs(x - px), 1);
dy = (y - py) / Math.max(Math.abs(y - py), 1);
// search diagonally
if (dx !== 0 && dy !== 0) {
if (grid.isWalkableAt(x, y + dy)) {
neighbors.push([x, y + dy]);
}
if (grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y]);
}
if (grid.isWalkableAt(x, y + dy) || grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y + dy]);
}
if (!grid.isWalkableAt(x - dx, y) && grid.isWalkableAt(x, y + dy)) {
neighbors.push([x - dx, y + dy]);
}
if (!grid.isWalkableAt(x, y - dy) && grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y - dy]);
}
}
// search horizontally/vertically
else {
if(dx === 0) {
if (grid.isWalkableAt(x, y + dy)) {
neighbors.push([x, y + dy]);
if (!grid.isWalkableAt(x + 1, y)) {
neighbors.push([x + 1, y + dy]);
}
if (!grid.isWalkableAt(x - 1, y)) {
neighbors.push([x - 1, y + dy]);
}
}
}
else {
if (grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y]);
if (!grid.isWalkableAt(x, y + 1)) {
neighbors.push([x + dx, y + 1]);
}
if (!grid.isWalkableAt(x, y - 1)) {
neighbors.push([x + dx, y - 1]);
}
}
}
}
}
// return all neighbors
else {
neighborNodes = grid.getNeighbors(node, DiagonalMovement.IfAtMostOneObstacle);
for (i = 0, l = neighborNodes.length; i < l; ++i) {
neighborNode = neighborNodes[i];
neighbors.push([neighborNode.x, neighborNode.y]);
}
}
return neighbors;
};
module.exports = JPFMoveDiagonallyIfAtMostOneObstacle;

@ -0,0 +1,174 @@
/**
* @author imor / https://github.com/imor
*/
var JumpPointFinderBase = require('./JumpPointFinderBase');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* Path finder using the Jump Point Search algorithm which moves
* diagonally only when there are no obstacles.
*/
function JPFMoveDiagonallyIfNoObstacles(opt) {
JumpPointFinderBase.call(this, opt);
}
JPFMoveDiagonallyIfNoObstacles.prototype = new JumpPointFinderBase();
JPFMoveDiagonallyIfNoObstacles.prototype.constructor = JPFMoveDiagonallyIfNoObstacles;
/**
* Search recursively in the direction (parent -> child), stopping only when a
* jump point is found.
* @protected
* @return {Array.<[number, number]>} The x, y coordinate of the jump point
* found, or null if not found
*/
JPFMoveDiagonallyIfNoObstacles.prototype._jump = function(x, y, px, py) {
var grid = this.grid,
dx = x - px, dy = y - py;
if (!grid.isWalkableAt(x, y)) {
return null;
}
if(this.trackJumpRecursion === true) {
grid.getNodeAt(x, y).tested = true;
}
if (grid.getNodeAt(x, y) === this.endNode) {
return [x, y];
}
// check for forced neighbors
// along the diagonal
if (dx !== 0 && dy !== 0) {
// if ((grid.isWalkableAt(x - dx, y + dy) && !grid.isWalkableAt(x - dx, y)) ||
// (grid.isWalkableAt(x + dx, y - dy) && !grid.isWalkableAt(x, y - dy))) {
// return [x, y];
// }
// when moving diagonally, must check for vertical/horizontal jump points
if (this._jump(x + dx, y, x, y) || this._jump(x, y + dy, x, y)) {
return [x, y];
}
}
// horizontally/vertically
else {
if (dx !== 0) {
if ((grid.isWalkableAt(x, y - 1) && !grid.isWalkableAt(x - dx, y - 1)) ||
(grid.isWalkableAt(x, y + 1) && !grid.isWalkableAt(x - dx, y + 1))) {
return [x, y];
}
}
else if (dy !== 0) {
if ((grid.isWalkableAt(x - 1, y) && !grid.isWalkableAt(x - 1, y - dy)) ||
(grid.isWalkableAt(x + 1, y) && !grid.isWalkableAt(x + 1, y - dy))) {
return [x, y];
}
// When moving vertically, must check for horizontal jump points
// if (this._jump(x + 1, y, x, y) || this._jump(x - 1, y, x, y)) {
// return [x, y];
// }
}
}
// moving diagonally, must make sure one of the vertical/horizontal
// neighbors is open to allow the path
if (grid.isWalkableAt(x + dx, y) && grid.isWalkableAt(x, y + dy)) {
return this._jump(x + dx, y + dy, x, y);
} else {
return null;
}
};
/**
* Find the neighbors for the given node. If the node has a parent,
* prune the neighbors based on the jump point search algorithm, otherwise
* return all available neighbors.
* @return {Array.<[number, number]>} The neighbors found.
*/
JPFMoveDiagonallyIfNoObstacles.prototype._findNeighbors = function(node) {
var parent = node.parent,
x = node.x, y = node.y,
grid = this.grid,
px, py, nx, ny, dx, dy,
neighbors = [], neighborNodes, neighborNode, i, l;
// directed pruning: can ignore most neighbors, unless forced.
if (parent) {
px = parent.x;
py = parent.y;
// get the normalized direction of travel
dx = (x - px) / Math.max(Math.abs(x - px), 1);
dy = (y - py) / Math.max(Math.abs(y - py), 1);
// search diagonally
if (dx !== 0 && dy !== 0) {
if (grid.isWalkableAt(x, y + dy)) {
neighbors.push([x, y + dy]);
}
if (grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y]);
}
if (grid.isWalkableAt(x, y + dy) && grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y + dy]);
}
}
// search horizontally/vertically
else {
var isNextWalkable;
if (dx !== 0) {
isNextWalkable = grid.isWalkableAt(x + dx, y);
var isTopWalkable = grid.isWalkableAt(x, y + 1);
var isBottomWalkable = grid.isWalkableAt(x, y - 1);
if (isNextWalkable) {
neighbors.push([x + dx, y]);
if (isTopWalkable) {
neighbors.push([x + dx, y + 1]);
}
if (isBottomWalkable) {
neighbors.push([x + dx, y - 1]);
}
}
if (isTopWalkable) {
neighbors.push([x, y + 1]);
}
if (isBottomWalkable) {
neighbors.push([x, y - 1]);
}
}
else if (dy !== 0) {
isNextWalkable = grid.isWalkableAt(x, y + dy);
var isRightWalkable = grid.isWalkableAt(x + 1, y);
var isLeftWalkable = grid.isWalkableAt(x - 1, y);
if (isNextWalkable) {
neighbors.push([x, y + dy]);
if (isRightWalkable) {
neighbors.push([x + 1, y + dy]);
}
if (isLeftWalkable) {
neighbors.push([x - 1, y + dy]);
}
}
if (isRightWalkable) {
neighbors.push([x + 1, y]);
}
if (isLeftWalkable) {
neighbors.push([x - 1, y]);
}
}
}
}
// return all neighbors
else {
neighborNodes = grid.getNeighbors(node, DiagonalMovement.OnlyWhenNoObstacles);
for (i = 0, l = neighborNodes.length; i < l; ++i) {
neighborNode = neighborNodes[i];
neighbors.push([neighborNode.x, neighborNode.y]);
}
}
return neighbors;
};
module.exports = JPFMoveDiagonallyIfNoObstacles;

@ -0,0 +1,120 @@
/**
* @author imor / https://github.com/imor
*/
var JumpPointFinderBase = require('./JumpPointFinderBase');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* Path finder using the Jump Point Search algorithm allowing only horizontal
* or vertical movements.
*/
function JPFNeverMoveDiagonally(opt) {
JumpPointFinderBase.call(this, opt);
}
JPFNeverMoveDiagonally.prototype = new JumpPointFinderBase();
JPFNeverMoveDiagonally.prototype.constructor = JPFNeverMoveDiagonally;
/**
* Search recursively in the direction (parent -> child), stopping only when a
* jump point is found.
* @protected
* @return {Array.<[number, number]>} The x, y coordinate of the jump point
* found, or null if not found
*/
JPFNeverMoveDiagonally.prototype._jump = function(x, y, px, py) {
var grid = this.grid,
dx = x - px, dy = y - py;
if (!grid.isWalkableAt(x, y)) {
return null;
}
if(this.trackJumpRecursion === true) {
grid.getNodeAt(x, y).tested = true;
}
if (grid.getNodeAt(x, y) === this.endNode) {
return [x, y];
}
if (dx !== 0) {
if ((grid.isWalkableAt(x, y - 1) && !grid.isWalkableAt(x - dx, y - 1)) ||
(grid.isWalkableAt(x, y + 1) && !grid.isWalkableAt(x - dx, y + 1))) {
return [x, y];
}
}
else if (dy !== 0) {
if ((grid.isWalkableAt(x - 1, y) && !grid.isWalkableAt(x - 1, y - dy)) ||
(grid.isWalkableAt(x + 1, y) && !grid.isWalkableAt(x + 1, y - dy))) {
return [x, y];
}
//When moving vertically, must check for horizontal jump points
if (this._jump(x + 1, y, x, y) || this._jump(x - 1, y, x, y)) {
return [x, y];
}
}
else {
throw new Error("Only horizontal and vertical movements are allowed");
}
return this._jump(x + dx, y + dy, x, y);
};
/**
* Find the neighbors for the given node. If the node has a parent,
* prune the neighbors based on the jump point search algorithm, otherwise
* return all available neighbors.
* @return {Array.<[number, number]>} The neighbors found.
*/
JPFNeverMoveDiagonally.prototype._findNeighbors = function(node) {
var parent = node.parent,
x = node.x, y = node.y,
grid = this.grid,
px, py, nx, ny, dx, dy,
neighbors = [], neighborNodes, neighborNode, i, l;
// directed pruning: can ignore most neighbors, unless forced.
if (parent) {
px = parent.x;
py = parent.y;
// get the normalized direction of travel
dx = (x - px) / Math.max(Math.abs(x - px), 1);
dy = (y - py) / Math.max(Math.abs(y - py), 1);
if (dx !== 0) {
if (grid.isWalkableAt(x, y - 1)) {
neighbors.push([x, y - 1]);
}
if (grid.isWalkableAt(x, y + 1)) {
neighbors.push([x, y + 1]);
}
if (grid.isWalkableAt(x + dx, y)) {
neighbors.push([x + dx, y]);
}
}
else if (dy !== 0) {
if (grid.isWalkableAt(x - 1, y)) {
neighbors.push([x - 1, y]);
}
if (grid.isWalkableAt(x + 1, y)) {
neighbors.push([x + 1, y]);
}
if (grid.isWalkableAt(x, y + dy)) {
neighbors.push([x, y + dy]);
}
}
}
// return all neighbors
else {
neighborNodes = grid.getNeighbors(node, DiagonalMovement.Never);
for (i = 0, l = neighborNodes.length; i < l; ++i) {
neighborNode = neighborNodes[i];
neighbors.push([neighborNode.x, neighborNode.y]);
}
}
return neighbors;
};
module.exports = JPFNeverMoveDiagonally;

@ -0,0 +1,31 @@
/**
* @author aniero / https://github.com/aniero
*/
var DiagonalMovement = require('../core/DiagonalMovement');
var JPFNeverMoveDiagonally = require('./JPFNeverMoveDiagonally');
var JPFAlwaysMoveDiagonally = require('./JPFAlwaysMoveDiagonally');
var JPFMoveDiagonallyIfNoObstacles = require('./JPFMoveDiagonallyIfNoObstacles');
var JPFMoveDiagonallyIfAtMostOneObstacle = require('./JPFMoveDiagonallyIfAtMostOneObstacle');
/**
* Path finder using the Jump Point Search algorithm
* @param {object} opt
* @param {function} opt.heuristic Heuristic function to estimate the distance
* (defaults to manhattan).
* @param {DiagonalMovement} opt.diagonalMovement Condition under which diagonal
* movement will be allowed.
*/
function JumpPointFinder(opt) {
opt = opt || {};
if (opt.diagonalMovement === DiagonalMovement.Never) {
return new JPFNeverMoveDiagonally(opt);
} else if (opt.diagonalMovement === DiagonalMovement.Always) {
return new JPFAlwaysMoveDiagonally(opt);
} else if (opt.diagonalMovement === DiagonalMovement.OnlyWhenNoObstacles) {
return new JPFMoveDiagonallyIfNoObstacles(opt);
} else {
return new JPFMoveDiagonallyIfAtMostOneObstacle(opt);
}
}
module.exports = JumpPointFinder;

@ -0,0 +1,114 @@
/**
* @author imor / https://github.com/imor
*/
var Heap = require('heap');
var Util = require('../core/Util');
var Heuristic = require('../core/Heuristic');
var DiagonalMovement = require('../core/DiagonalMovement');
/**
* Base class for the Jump Point Search algorithm
* @param {object} opt
* @param {function} opt.heuristic Heuristic function to estimate the distance
* (defaults to manhattan).
*/
function JumpPointFinderBase(opt) {
opt = opt || {};
this.heuristic = opt.heuristic || Heuristic.manhattan;
this.trackJumpRecursion = opt.trackJumpRecursion || false;
}
/**
* Find and return the path.
* @return {Array.<[number, number]>} The path, including both start and
* end positions.
*/
JumpPointFinderBase.prototype.findPath = function(startX, startY, endX, endY, grid) {
var openList = this.openList = new Heap(function(nodeA, nodeB) {
return nodeA.f - nodeB.f;
}),
startNode = this.startNode = grid.getNodeAt(startX, startY),
endNode = this.endNode = grid.getNodeAt(endX, endY), node;
this.grid = grid;
// set the `g` and `f` value of the start node to be 0
startNode.g = 0;
startNode.f = 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 (node === endNode) {
return Util.expandPath(Util.backtrace(endNode));
}
this._identifySuccessors(node);
}
// fail to find the path
return [];
};
/**
* Identify successors for the given node. Runs a jump point search in the
* direction of each available neighbor, adding any points found to the open
* list.
* @protected
*/
JumpPointFinderBase.prototype._identifySuccessors = function(node) {
var grid = this.grid,
heuristic = this.heuristic,
openList = this.openList,
endX = this.endNode.x,
endY = this.endNode.y,
neighbors, neighbor,
jumpPoint, i, l,
x = node.x, y = node.y,
jx, jy, dx, dy, d, ng, jumpNode,
abs = Math.abs, max = Math.max;
neighbors = this._findNeighbors(node);
for(i = 0, l = neighbors.length; i < l; ++i) {
neighbor = neighbors[i];
jumpPoint = this._jump(neighbor[0], neighbor[1], x, y);
if (jumpPoint) {
jx = jumpPoint[0];
jy = jumpPoint[1];
jumpNode = grid.getNodeAt(jx, jy);
if (jumpNode.closed) {
continue;
}
// include distance, as parent may not be immediately adjacent:
d = Heuristic.octile(abs(jx - x), abs(jy - y));
ng = node.g + d; // next `g` value
if (!jumpNode.opened || ng < jumpNode.g) {
jumpNode.g = ng;
jumpNode.h = jumpNode.h || heuristic(abs(jx - endX), abs(jy - endY));
jumpNode.f = jumpNode.g + jumpNode.h;
jumpNode.parent = node;
if (!jumpNode.opened) {
openList.push(jumpNode);
jumpNode.opened = true;
} else {
openList.updateItem(jumpNode);
}
}
}
}
};
module.exports = JumpPointFinderBase;
Loading…
Cancel
Save

Powered by TurnKey Linux.