pathfinding
Version:
Comprehensive pathfinding library for grid based games
246 lines (211 loc) • 6.67 kB
JavaScript
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);
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);
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;