slides/asset/common/Array2D.js (1,418 lines of code) (raw):
// Array2D.js 0.0.5
// Copyright (c) 2014 Matthew Trost
// Array2D.js may be freely distributed under the MIT license.
(function() {
// Baseline setup
// ==============
// Establish the root object, `window` in the browser, or `exports` on the server.
var root = this;
// Save the previous value of the `Array2D` variable.
var previousArray2D = root.Array2D;
// Create a safe reference to the Array2D object for use below.
var Array2D = function() {
if (!(this instanceof Array2D)) return new Array2D();
};
// Export the Array2D object for Node.js, with backwards-compatibility for the
// old `require()` API. If we're in the browser, add `Array2D` as a global object.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = Array2D;
}
exports.Array2D = Array2D;
} else {
root.Array2D = Array2D;
}
// Current version.
Array2D.VERSION = '0.0.5';
// Run Array2D.js in *noConflict* mode, returning the `Array2D` variable to its
// previous owner. Returns a reference to the Array2D object.
Array2D.noConflict = function() {
root.Array2D = previousArray2D;
return this;
};
// Private utilities
// =================
// Return T/F if the passed `thing` is an array.
function isArray(thing) {
return Object.prototype.toString.call(thing) === '[object Array]';
}
// Return T/F if the passed `thing` is `null`.
function isNull(thing) {
return thing === null;
}
// Return T/F if the passed `thing` is `undefined`.
function isUndefined(thing) {
return thing === undefined;
}
// Return T/F if the passed `thing` is `null` or `undefined`.
function isBlank(thing) {
return isNull(thing) || isUndefined(thing);
}
// Return T/F if the passed `thing` is neither `null` nor `undefined`.
function isPresent(thing) {
return !isBlank(thing);
}
// Return T/F if the passed thing is not `undefined`.
function isExistent(thing) {
return !isUndefined(thing);
}
// Clone the given (flat) array.
function cloneArray(array) {
var clone = [];
for (var i = 0, l = array.length; i < l; i++) {
clone[i] = array[i];
}
return clone;
}
// Constants / enums
// =================
Array2D.AXES = {
X: 1,
Y: 2
};
Array2D.BEARINGS = {
NORTH: 1,
NORTHWEST: 2,
NORTHEAST: 3,
SOUTH: 4,
SOUTHWEST: 5,
SOUTHEAST: 6,
EAST: 7,
WEST: 8
};
Array2D.BOUNDARIES = {
UPPER: 1,
LOWER: 2,
LEFT: 3,
RIGHT: 4
};
Array2D.CORNERS = {
TOP_LEFT: 1,
TOP_RIGHT: 2,
BOTTOM_LEFT: 3,
BOTTOM_RIGHT: 4
};
Array2D.CROOKS = {
UPPER_LEFT: 1,
UPPER_RIGHT: 2,
LOWER_LEFT: 3,
LOWER_RIGHT: 4
};
Array2D.DIRECTIONS = {
UP: 1,
DOWN: 2,
LEFT: 3,
RIGHT: 4
};
Array2D.EDGES = {
TOP: 1,
BOTTOM: 2,
LEFT: 3,
RIGHT: 4
};
Array2D.QUADRANTS = {
I: 1,
II: 2,
III: 3,
IV: 4
};
// Basic functions
// ===============
// Return the value of the cell at (`r`,`c`).
Array2D.get = function(grid, r, c) {
if (!isArray(grid[r])) {
return undefined;
}
return grid[r][c];
};
// Return a grid with the cell at (`r`,`c`) set to `value`.
Array2D.set = function(grid, r, c, value) {
var clone = Array2D.clone(grid);
if(!isArray(clone[r])) {
clone[r] = [];
}
clone[r][c] = value;
return clone;
};
// Return the width of the grid.
Array2D.width = function(grid) {
return Array2D.widest(grid).length;
};
// Return the height of the grid.
Array2D.height = function(grid) {
return Array2D.tallest(grid).length;
};
// Return the dimensions of the grid (width and height),
// iterating over the grid in a single pass. Faster than
// calling `width` and `height` individually.
Array2D.dimensions = function(grid) {
var h = 0;
var w = 0;
for (var i = 0, rs = grid.length; i < rs; i++) {
var l = grid[i].length;
if (l > 0) h = i + 1; // The last row with any content is the longest
if (l > w) w = l; // Check if the previous max width is beaten
}
return [w, h];
}
// Return the area of the grid.
Array2D.area = function(grid) {
var width = Array2D.width(grid);
var height = Array2D.height(grid);
return width * height;
};
// Return the number of present cells in the grid.
Array2D.cells = function(grid) {
var count = 0;
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
if (isExistent(cell)) {
count++
}
}
}
return count;
};
// Essentials
// ==========
// Crop a subgrid of the given dimensions from the grid, but exclude
// anything that would fall outside of the grid's bounds.
Array2D.crop = function(grid, r, c, w, h) {
var out = [];
var width = Array2D.width(grid);
var height = Array2D.height(grid);
for (var i = 0; i < h; i++) {
var ro = r + i; // Offset row
// Skip any out-of-bounds cells.
if (ro < height && ro >= 0) {
out.push([]);
for (var j = 0; j < w; j++) {
var co = c + j; // Offset column
// Skip any out-of-bounds cells.
if (co < width && co >= 0) {
var last = out[out.length - 1];
var cell = grid[ro][co];
last.push(cell);
}
}
}
}
return out;
};
// Harvest a subgrid of the given dimensions from the grid. If access
// goes outside of the grid's bounds, set those overlap cells to `null`.
Array2D.harvest = function(grid, r, c, w, h) {
var out = [];
var width = Array2D.width(grid);
var height = Array2D.height(grid);
for (var i = 0; i < h; i++) {
out[i] = [];
for (var j = 0; j < w; j++) {
var ro = r + i; // Offset row
var co = c + j; // Offset column
// Set to `null` any out-of-bounds cell.
if (ro >= height || ro < 0) {
out[i][j] = null;
}
// Set to `null` any out-of-bounds cell.
else if (co >= width || co < 0) {
out[i][j] = null;
}
else {
var cell = grid[ro][co];
out[i][j] = cell;
}
}
}
return out;
};
// Rotate the grid one quarter-turn in the given `direction`.
Array2D.rotate = function(grid, direction) {
if (direction === Array2D.DIRECTIONS.LEFT) return Array2D.lrotate(grid);
if (direction === Array2D.DIRECTIONS.RIGHT) return Array2D.rrotate(grid);
throw("Array2D.js: Invalid direction provided for `rotate`");
};
// Rotate the grid to the left one quarter-turn.
Array2D.lrotate = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.vflip(transposed);
};
// Rotate the grid to the right one quarter-turn.
Array2D.rrotate = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.hflip(transposed);
};
// Flip the grid about the given axis `axis`.
Array2D.flip = function(grid, axis) {
if (axis === Array2D.AXES.X) return Array2D.vflip(grid);
if (axis === Array2D.AXES.Y) return Array2D.hflip(grid);
throw("Array2D.js: Invalid axis provided for `flip`");
};
// Flip the grid vertically, i.e., about its x-axis.
Array2D.vflip = function(grid) {
var out = [];
for (var i = 0, l = grid.length; i < l; i++) {
var opp = i - l + 1;
out[i] = grid[Math.abs(opp)];
}
return out;
};
// Flip the grid horizontally, i.e., about its y-axis.
Array2D.hflip = function(grid) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var opp = j - l2 + 1;
out[i][j] = grid[i][Math.abs(opp)];
}
}
return out;
};
// Pan the array in the given direction, the given number of steps.
Array2D.pan = function(grid, direction, steps) {
switch (direction) {
case Array2D.DIRECTIONS.LEFT: return Array2D.lpan(grid, steps);
case Array2D.DIRECTIONS.RIGHT: return Array2D.rpan(grid, steps);
case Array2D.DIRECTIONS.UP: return Array2D.upan(grid, steps);
case Array2D.DIRECTIONS.DOWN: return Array2D.dpan(grid, steps);
default:
throw("Array2D.js: Invalid direction provided for `pan`");
}
};
// Pan the array up by the number of steps.
Array2D.upan = function(grid, steps) {
var panned = Array2D.clone(grid);
steps || (steps = 1);
while (steps > 0) {
var last = panned.pop();
panned.unshift(last);
steps--;
}
return panned;
};
// Pan the array left by the number of steps.
Array2D.lpan = function(grid, steps) {
var transposed = Array2D.transpose(grid);
var shifted = Array2D.upan(transposed, steps);
return Array2D.transpose(shifted);
};
// Pan the array down by the number of steps.
Array2D.dpan = function(grid, steps) {
var panned = Array2D.clone(grid);
steps || (steps = 1);
while (steps > 0) {
var first = panned.shift();
panned.push(first);
steps--;
}
return panned;
};
// Pan the array right by the number of steps.
Array2D.rpan = function(grid, steps) {
var transposed = Array2D.transpose(grid);
var shifted = Array2D.dpan(transposed, steps);
return Array2D.transpose(shifted);
};
// Slide performs a pan, but in the reverse direction specified.
Array2D.slide = function(grid, direction, steps) {
switch (direction) {
case Array2D.DIRECTIONS.LEFT: return Array2D.lslide(grid, steps);
case Array2D.DIRECTIONS.RIGHT: return Array2D.rslide(grid, steps);
case Array2D.DIRECTIONS.UP: return Array2D.uslide(grid, steps);
case Array2D.DIRECTIONS.DOWN: return Array2D.dslide(grid, steps);
default:
throw("Array2D.js: Invalid direction provided for `slide`");
}
};
// Slide the grid right, the number of steps.
Array2D.rslide = function(grid, steps) {
return Array2D.lpan(grid, steps);
};
// Slide the grid left, the number of steps.
Array2D.lslide = function(grid, steps) {
return Array2D.rpan(grid, steps);
};
// Slide the grid down, the number of steps.
Array2D.dslide = function(grid, steps) {
return Array2D.upan(grid, steps);
};
// Slide the grid up, the number of steps.
Array2D.uslide = function(grid, steps) {
return Array2D.dpan(grid, steps);
};
// Return a new grid with the elements transposed (flipped about
// their main diagonal).
Array2D.transpose = function(grid) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
if (!out[j]) out[j] = [];
out[j][i] = row[j];
}
}
return out;
};
// Return a new grid with the elements transposed about their
// *secondary* diagonal.
Array2D.antitranspose = function(grid) {
var rotated = Array2D.rrotate(grid);
return Array2D.vflip(rotated);
};
// Fill the entire grid with a value.
Array2D.fill = function(grid, value) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
out[i] = [];
for (var j = 0, l2 = row.length; j < l2; j++) {
out[i][j] = value;
}
}
return out;
};
// Fill an area within the grid with a value.
Array2D.fillArea = function(grid, r, c, w, h, value) {
var built = Array2D.build(w, h, value);
return Array2D.paste(grid, built, r, c);
};
// Add padding to the given `side` of the grid, the specified
// number of `times`.
Array2D.pad = function(grid, side, times, value) {
switch (side) {
case Array2D.EDGES.TOP: return Array2D.upad(grid, times, value);
case Array2D.EDGES.BOTTOM: return Array2D.dpad(grid, times, value);
case Array2D.EDGES.LEFT: return Array2D.lpad(grid, times, value);
case Array2D.EDGES.RIGHT: return Array2D.rpad(grid, times, value);
default:
throw("Array2D.js: Invalid side provided for `pad`");
}
};
// Add padding to the top of the grid.
Array2D.upad = function(grid, times, value) {
var out = [];
var d = Array2D.dimensions(grid);
var w = d[0];
var h = d[1];
for (var i = -times; i < h; i++) {
var r = i + times;
out[r] = [];
for (var j = 0; j < w; j++) {
// We're in the original grid.
if (i > -1) {
out[r][j] = grid[i][j];
}
// We're in the 'padding' zone.
else {
if (!isUndefined(value)) {
out[r][j] = value;
}
else {
out[r][j] = null;
}
}
}
}
return out;
};
// Add padding to the bottom of the grid.
Array2D.dpad = function(grid, times, value) {
var out = [];
var d = Array2D.dimensions(grid);
var w = d[0];
var h = d[1];
for (var i = 0; i < h + times; i++) {
out[i] = [];
for (var j = 0; j < w; j++) {
// We're in the original grid.
if (i < h) {
out[i][j] = grid[i][j];
}
// We're in the 'padding' zone.
else {
if (!isUndefined(value)) {
out[i][j] = value;
}
else {
out[i][j] = null;
}
}
}
}
return out;
};
// Add padding to the grid's left side
Array2D.lpad = function(grid, times, value) {
var out = [];
var d = Array2D.dimensions(grid);
var w = d[0];
var h = d[1];
for (var i = 0; i < h; i++) {
out[i] = [];
for (var j = -times; j < w; j++) {
var c = j + times;
// We're in the original grid.
if (j > -1) {
out[i][c] = grid[i][j];
}
// We're in the 'padding' zone.
else {
if (!isUndefined(value)) {
out[i][c] = value;
}
else {
out[i][c] = null;
}
}
}
}
return out;
};
// Add padding to the grid's right side
Array2D.rpad = function(grid, times, value) {
var out = [];
var d = Array2D.dimensions(grid);
var w = d[0];
var h = d[1];
for (var i = 0; i < h; i++) {
out[i] = [];
for (var j = 0; j < w + times; j++) {
// We're in the original grid.
if (j < w) {
out[i][j] = grid[i][j];
}
// We're in the 'padding' zone.
else {
if (!isUndefined(value)) {
out[i][j] = value;
}
else {
out[i][j] = null;
}
}
}
}
return out;
};
// Trim rows/columns off the specified side of the grid.
Array2D.trim = function(grid, side, num) {
switch (side) {
case Array2D.EDGES.TOP: return Array2D.utrim(grid, num);
case Array2D.EDGES.BOTTOM: return Array2D.dtrim(grid, num);
case Array2D.EDGES.LEFT: return Array2D.ltrim(grid, num);
case Array2D.EDGES.RIGHT: return Array2D.rtrim(grid, num);
default:
throw("Array2D.js: Invalid edge provided for `trim`");
}
};
// Trim rows off the top of the grid.
Array2D.utrim = function(grid, num) {
var out = [];
num || (num = 1);
for (var i = num, l = grid.length; i < l; i++) {
out[i - num] = cloneArray(grid[i]);
}
return out;
};
// Trim rows off the bottom of the grid.
Array2D.dtrim = function(grid, num) {
var out = [];
num || (num = 1);
for (var i = 0, l = grid.length - num; i < l; i++) {
out[i] = cloneArray(grid[i]);
}
return out;
};
// Trim columns off the left side of the grid.
Array2D.ltrim = function(grid, num) {
var out = [];
num || (num = 1);
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = num, l2 = row.length; j < l2; j++) {
out[i][j - num] = row[j];
}
}
return out;
};
// Trim columns off the right side of the grid.
Array2D.rtrim = function(grid, num) {
var out = [];
num || (num = 1);
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = 0, l2 = row.length - num; j < l2; j++) {
out[i][j] = row[j];
}
}
return out;
};
// Stitch the second grid to the given edge of the first.
Array2D.stitch = function(grid1, grid2, edge) {
switch (edge) {
case Array2D.EDGES.TOP: return Array2D.ustitch(grid1, grid2);
case Array2D.EDGES.BOTTOM: return Array2D.dstitch(grid1, grid2);
case Array2D.EDGES.LEFT: return Array2D.lstitch(grid1, grid2);
case Array2D.EDGES.RIGHT: return Array2D.rstitch(grid1, grid2);
default:
throw("Array2D.js: Invalid edge provided for `stitch`");
}
};
// Stitch the second grid to the top of the first.
Array2D.ustitch = function(grid1, grid2) {
var h = Array2D.dimensions(grid2)[1];
return Array2D.glue(grid1, grid2, -h, 0);
};
// Stitch the second grid to the bottom of the first.
Array2D.dstitch = function(grid1, grid2) {
var h = Array2D.dimensions(grid1)[1];
return Array2D.glue(grid1, grid2, h, 0);
};
// Stitch the second grid to the left side of the first.
Array2D.lstitch = function(grid1, grid2) {
var w = Array2D.dimensions(grid2)[0];
return Array2D.glue(grid1, grid2, 0, -w);
};
// Sticth the second grid to the right side of the first.
Array2D.rstitch = function(grid1, grid2) {
var w = Array2D.dimensions(grid1)[0];
return Array2D.glue(grid1, grid2, 0, w);
};
// Paste the contents of the second grid onto the first.
Array2D.paste = function(grid1, grid2, sr, sc) {
var out = [];
for (var i = 0, l1 = grid1.length; i < l1; i++) {
out[i] = [];
var rlen = grid1[i].length;
var tr = i - sr;
for (var j = 0; j < rlen; j++) {
var tc = j - sc;
if (isArray(grid2[tr]) &&
!isUndefined(grid2[tr][tc]) &&
i >= sr &&
j >= sc &&
tr < l1 &&
tc < rlen) {
out[i][j] = grid2[tr][tc];
}
else {
out[i][j] = grid1[i][j];
}
}
}
return out;
};
// Paste the contents of the second grid onto the first,
// but allow for overlap, and pad any extra cells with `null`
// so the resulting grid is rectangular.
Array2D.glue = function(grid1, grid2, r, c) {
var d1 = Array2D.dimensions(grid1);
var d2 = Array2D.dimensions(grid2);
var mw = (d1[0] > d2[0]) ? d1[0] : d2[0]; // Greater width
var mh = (d1[1] > d2[1]) ? d1[1] : d2[1]; // Greater height
var w = Math.abs(c) + mw; // Width of new grid
var h = Math.abs(r) + mh; // Height of new grid
var n = Array2D.build(w, h); // A blank array
var r1 = (r < 0) ? -r : 0;
var c1 = (c < 0) ? -c : 0;
var o = Array2D.paste(n, grid1, r1, c1);
var r2 = (r > 0) ? r : 0;
var c2 = (c > 0) ? c : 0;
var p = Array2D.paste(o, grid2, r2, c2);
return p;
};
// Shuffle (randomize) the grid, preserving the dimensions.
Array2D.shuffle = function(grid) {
// Ensure the row-lengths are preserved
var rowLens = [];
for (var i = 0, l = grid.length; i < l; i++) {
rowLens.push(grid[i].length);
}
// Fisher-Yates shuffle
var shuffled = Array2D.flatten(grid);
for (var i = shuffled.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = shuffled[i];
shuffled[i] = shuffled[j];
shuffled[j] = t;
}
// Push the shuffled elements into a new grid
var out = []
for (var i = 0, l = rowLens.length; i < l; i++) {
var row = [];
var rowLen = rowLens[i];
while (rowLen--) {
row.push(shuffled.pop());
}
out.push(row);
}
return out;
};
// Return a tidied-up clone of the grid, that is, a rectangular
// grid with no `undefined` cells.
Array2D.tidy = function(grid) {
var out = [];
var width = Array2D.width(grid);
var height = Array2D.height(grid);
for (var i = 0, l1 = width; i < l1; i++) {
out[i] = [];
for (var j = 0, l2 = height; j < l2; j++) {
var previous = Array2D.get(grid, i, j);
if (isUndefined(previous)) {
out[i][j] = null;
}
else {
out[i][j] = previous;
}
}
}
return out;
};
// Construction / casting
// ======================
// Return a clone of the grid.
Array2D.clone = function(grid) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
out[i][j] = cell;
}
}
return out;
};
// Initialize a new grid of the given dimensions (w,h)
Array2D.build = function(w, h, value) {
var out = [];
if (isUndefined(value)) {
value = null;
}
for (var i = 0, l1 = h; i < l1; i++) {
out[i] = [];
for (var j = 0, l2 = w; j < l2; j++) {
out[i][j] = value;
}
}
return out;
};
// Initialize a new grid of the given dimensions (w,h),
// using the passed function to initialize each cell.
Array2D.buildWith = function(w, h, fn) {
var out = [];
for (var i = 0, l1 = h; i < l1; i++) {
out[i] = [];
for (var j = 0, l2 = w; j < l2; j++) {
if (fn) {
out[i][j] = fn(i, j, out);
}
else {
out[i][j] = null;
}
}
}
return out;
};
// Serialize the grid to a string.
Array2D.serialize = function(grid) {
return JSON.stringify(grid);
};
// Convert all the cells of the grid to `null`.
Array2D.nullify = function(grid) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
if (isExistent(cell)) {
out[i][j] = null;
}
}
}
return out;
};
// Return a new grid with the cells converted to integers,
// using `parseInt`.
Array2D.integerize = function(grid) {
var out = []
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
out[i][j] = parseInt(cell);
}
}
return out;
};
// Return a new grid with the cells converted to strings, using
// the `String` constructor.
Array2D.stringize = function(grid) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
out[i][j] = String(cell);
}
}
return out;
};
// Inspection / comparison / analysis
// ==================================
// Determine whether the passed object is a grid (an array of arrays).
Array2D.check = function(o) {
if (!isArray(o)) return false;
if (!isArray(o[0])) return false;
return true;
};
// Determine whether the grid is ragged (has rows of
// differing lengths).
Array2D.ragged = function(grid) {
var widest = Array2D.widest(grid);
var thinnest = Array2D.thinnest(grid);
return widest.length !== thinnest.length;
};
// Determine whether the grid is rectangular (all its rows
// have the same length).
Array2D.rectangular = function(grid) {
return !Array2D.ragged(grid);
};
// Return true if the grid has no cells.
Array2D.empty = function(grid) {
if (grid.length < 1) return true;
if (grid.length === 1 && grid[0].length < 1) return true;
return false;
};
// Return true if all of the grid's cells are `null` or `undefined`.
Array2D.blank = function(grid) {
var blank = true;
var empty = Array2D.empty(grid);
if (empty) return true;
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
if (!isBlank(cell)) {
blank = false;
}
}
}
return blank;
};
// Determine whether the grid has only one cell.
Array2D.singular = function(grid) {
var width = Array2D.width(grid);
var height = Array2D.height(grid);
return width === 1 && height === 1;
};
// Determine whether the grid has more than one cell.
Array2D.multitudinous = function(grid) {
return !Array2D.singular(grid);
};
// Determine whether the grid has any `null` or `undefined` cells.
Array2D.sparse = function(grid) {
var sparse = false;
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
if (isBlank(cell)) {
sparse = true;
}
}
}
return sparse;
};
// Determine if the grid is dense, i.e., without any `null` or
// `undefined` cells.
Array2D.dense = function(grid) {
return !Array2D.sparse(grid);
};
// Determine whether both grids' cells are all strictly equal.
Array2D.same = function(grid1, grid2) {
var w1 = Array2D.width(grid1);
var h1 = Array2D.height(grid1);
var w2 = Array2D.width(grid2);
var h2 = Array2D.height(grid2);
if (w1 !== w2) return false;
if (h1 !== h2) return false;
for (var i = 0; i < w1; i++) {
for (var j = 0; j < w2; j++) {
if (grid1[i][j] !== grid2[i][j]) {
return false;
}
}
}
return true;
};
// Determine whether both grids' cells are not strictly equal.
Array2D.different = function(grid1, grid2) {
return !Array2D.same(grid1, grid2);
};
// Return the coordinates of cells that are different between
// the two grids.
Array2D.diff = function(grid1, grid2) {
var diffs = [];
var d1 = Array2D.dimensions(grid1);
var d2 = Array2D.dimensions(grid2);
var w = (d1[0] > d2[0]) ? d1[0] : d2[0];
var h = (d1[1] > d2[1]) ? d2[1] : d2[1];
for (var i = 0; i < h; i++) {
var row1 = grid1[i];
var row2 = grid2[i];
var row1isArray = isArray(row1);
var row2isArray = isArray(row2);
for (var j = 0; j < w; j++) {
if (row1isArray && row2isArray) {
var cell1 = row1[j];
var cell2 = row2[j];
if (cell1 !== cell2) {
diffs.push([i, j]);
}
}
else {
diffs.push([i, j]);
}
}
}
return diffs;
};
// Return true if the grid contains the value.
Array2D.contains = function(grid, value) {
var contains = false;
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
if (cell === value) {
contains = true;
}
}
}
return contains;
};
// Detect whether the first grid contains the second grid.
Array2D.includes = function(grid1, grid2) {
// Dimensions cache.
var d1 = Array2D.dimensions(grid1);
var d2 = Array2D.dimensions(grid2);
var w1 = d1[0];
var h1 = d1[1];
var w2 = d2[0];
var h2 = d2[1];
// Size conditions under which we don't bother checking.
if (w2 < 1) return false;
if (h2 < 1) return false;
if (w2 > w1) return false;
if (h2 > h1) return false;
var first = grid2[0][0];
var starters = [];
// Start by checking each cell of the outer grid.
for (var i = 0; i < grid1.length; i++) {
for (var j = 0; j < grid1[i].length; j++) {
var cell1 = grid1[i][j];
// If the first cell is a match, proceed.
if (cell1 === first) {
starters.push([i, j]);
}
}
}
// If no initial matches, no point checking the rest.
var startersLen = starters.length;
if (startersLen < 1) return false;
// Check whether the comparee is present in the grid.
for (var x = 0; x < startersLen; x++) {
// Starting coordinates in the *outer* grid.
var sr = starters[x][0];
var sc = starters[x][1];
// Assume a match for this starting point, then invalidate.
var match = true;
// Loop over the inner grid, comparing each cell.
for (var i = 0; i < grid2.length; i++) {
var row1 = grid1[i + sr];
var row2 = grid2[i];
// Fail early if we've already overstepped the bounds.
if (!isArray(row1)) break;
if (!isArray(row2)) break;
for (var j = 0; j < grid2[i].length; j++) {
var cell1 = row1[j + sc];
var cell2 = row2[j];
if (cell1 !== cell2) match = false;
}
}
// Return as soon as we find our first match.
if (match === true) {
return true;
}
}
// If we got this far, we never found a match.
return false;
};
// Detect whether the grid is symmetrical, when reflected
// around the given axis.
Array2D.symmetrical = function(grid, axis) {
switch (axis) {
case Array2D.AXES.Y: return Array2D.hsymmetrical(grid);
case Array2D.AXES.X: return Array2D.vsymmetrical(grid);
default:
throw("Array2D.js: Invalid axis given for `symmetrical`");
}
};
// Determine whether the grid is horizontally symmetrical
Array2D.hsymmetrical = function(grid) {
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
var opposite = row[l2 - 1 - j];
if (cell !== opposite) {
return false;
}
}
}
return true;
};
// Determine whether the grid is vertically symmetrical
Array2D.vsymmetrical = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.hsymmetrical(transposed);
};
// Iteration / collection
// ======================
// Iterate over each cell in the grid, passing the cell to the
// iterator function.
Array2D.eachCell = function(grid, iterator) {
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
iterator(cell, i, j, grid);
}
}
};
// Iterate over every nth cell in the grid.
Array2D.nthCell = function(grid, n, s, iterator) {
var x = 0;
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
var isPastStart = x >= s;
var isAtNth = ((x - s) % n) === 0
if (isPastStart && isAtNth) {
iterator(cell, i, j, grid);
}
x += 1;
}
}
};
// Iterate over each row in the grid, passing the row-array to
// the iterator function.
Array2D.eachRow = function(grid, iterator) {
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
iterator(cloneArray(row), i, grid);
}
};
// Iterate over each column in the grid, passing the column-array
// to the iterator function.
Array2D.eachColumn = function(grid, iterator) {
var transposed = Array2D.transpose(grid);
for (var i = 0, l1 = transposed.length; i < l1; i++) {
var row = transposed[i];
iterator(cloneArray(row), i, grid);
}
};
// Iterate over every cell in the given area.
Array2D.forArea = function(grid, r, c, w, h, iterator) {
var cropped = Array2D.crop(grid, r, c, w, h);
for (var i = 0, l1 = cropped.length; i < l1; i++) {
var row = cropped[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
iterator(cell, i, j, grid);
}
}
};
// Iterate over every cell in the given row.
Array2D.forRow = function(grid, r, iterator) {
var row = Array2D.row(grid, r);
for (var i = 0, l = row.length; i < l; i++) {
iterator(row[i], r, i, grid);
}
};
// Iterate over every cell in the given column.
Array2D.forColumn = function(grid, c, iterator) {
var column = Array2D.column(grid, c);
for (var i = 0, l = column.length; i < l; i++) {
iterator(column[i], i, c, grid);
}
};
// Flatten the grid to an array in row-major order.
Array2D.flatten = function(grid) {
var flattened = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
flattened.push(row[j]);
}
}
return flattened;
};
// Same as flatten, but in column-major order.
Array2D.squash = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.flatten(transposed);
};
// Remap the grid to a new grid by returning a new value for each cell.
Array2D.map = function(grid, iterator) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
out[i] = [];
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
var result;
if (iterator) {
result = iterator(cell, i, j, grid);
}
else {
result = cell;
}
out[i][j] = result;
}
}
return out;
};
// Reduce the grid to a flat array by reducing each
// row to a single value.
Array2D.reduce = function(grid, iterator) {
var reduced = [];
for (var i = 0, l = grid.length; i < l; i++) {
reduced[i] = iterator(grid[i], i, grid);
}
return reduced;
};
// Similar to reduce, but column-by-column.
Array2D.boildown = function(grid, iterator) {
var transposed = Array2D.transpose(grid);
return Array2D.reduce(transposed, iterator);
};
// Rows / columns
// ==============
// Return the row of the given row-coordinate.
Array2D.row = function(grid, r) {
return cloneArray(grid[r]);
};
// Return the column of the given column-coordinate.
Array2D.column = function(grid, c) {
var transposed = Array2D.transpose(grid);
return Array2D.row(transposed, c);
};
// Return the top row of the grid.
Array2D.top = function(grid) {
return cloneArray(grid[0]);
};
// Return the bottom row of the grid.
Array2D.bottom = function(grid) {
return cloneArray(grid[grid.length - 1]);
};
// Return the left column of the grid.
Array2D.left = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.top(grid);
};
// Return the right column of the grid.
Array2D.right = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.bottom(grid);
};
// Return the longest row of the grid.
Array2D.widest = function(grid) {
var widest = grid[0];
for (var i = 0, l = grid.length; i < l; i++) {
var row = grid[i];
if (row.length > widest.length) {
widest = row;
}
}
return cloneArray(widest);
};
// Return the shortest row of the grid.
Array2D.thinnest = function(grid) {
var thinnest = grid[0];
for (var i = 0, l = grid.length; i < l; i++) {
var row = grid[i];
if (row.length < thinnest.length) {
thinnest = row;
}
}
return cloneArray(thinnest);
};
// Return the tallest column of the grid.
Array2D.tallest = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.widest(transposed);
};
// Return the shortest column of the grid.
Array2D.shortest = function(grid) {
var transposed = Array2D.transpose(grid);
return Array2D.thinnest(transposed);
};
// Set a row to the given array.
Array2D.setRow = function(grid, r, array) {
var out = [];
for (var i = 0, l = grid.length; i < l; i++) {
if (i === r) {
out[i] = cloneArray(array);
}
else {
out[i] = cloneArray(grid[i]);
}
}
return out;
};
// Set a column to the given array.
Array2D.setColumn = function(grid, c, array) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
out[i] = [];
for (var j = 0, l2 = row.length; j < l2; j++) {
if (j === c) {
out[i][j] = array[j];
}
else {
out[i][j] = row[j];
}
}
}
return out;
};
// Fill a row with the given value.
Array2D.fillRow = function(grid, r, value) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
out[i] = [];
for (var j = 0, l2 = row.length; j < l2; j++) {
if (i === r) {
out[i][j] = value;
}
else {
out[i][j] = row[j];
}
}
}
return out;
};
// Fill a column with the given value.
Array2D.fillColumn = function(grid, c, value) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
out[i] = [];
for (var j = 0, l2 = row.length; j < l2; j++) {
if (j === c) {
out[i][j] = value;
}
else {
out[i][j] = row[j];
}
}
}
return out;
};
// Insert a row (array).
Array2D.spliceRow = function(grid, r, array) {
var out = [];
for (var i = 0, l = grid.length; i < l; i++) {
if (i === r) {
out.push(array);
}
out.push(grid[i]);
}
return out;
};
// Insert a column (array).
Array2D.spliceColumn = function(grid, c, array) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
out[i] = [];
for (var j = 0, l2 = row.length; j < l2; j++) {
if (j === c) {
out[i].push(array[j]);
}
out[i].push(row[j]);
}
}
return out;
};
// Delete a row.
Array2D.deleteRow = function(grid, r) {
var out = [];
for (var i = 0, l = grid.length; i < l; i++) {
if (i !== r) {
out.push(grid[i]);
}
}
return out;
};
// Delete a column.
Array2D.deleteColumn = function(grid, c) {
var out = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
out[i] = [];
for (var j = 0, l2 = row.length; j < l2; j++) {
if (j !== c) {
out[i].push(row[j]);
}
}
}
return out;
};
// Cells
// =====
// Determine whether the coordinate cell exists (is not
// `undefined`).
Array2D.exists = function(grid, r, c) {
return !isUndefined(Array2D.get(grid, r, c));
};
// Determine whether the coordinate is occupied (not `null` or
// `undefined`).
Array2D.occupied = function(grid, r, c) {
return isPresent(Array2D.get(grid, r, c));
};
// Return T/F whether the given cell is within the grid's area.
Array2D.inBounds = function(grid, r, c) {
if (r < 0 || c < 0) return false;
if (!isArray(grid[r])) return false;
if (c > grid[r].length - 1) return false;
return true;
};
// Copy one cell value over another coordinate.
Array2D.copy = function(grid, r1, c1, r2, c2) {
var cell = Array2D.get(grid, r1, c1);
return Array2D.set(grid, r2, c2, cell);
};
// Move one cell value to another coordinate, nullifying the first.
Array2D.move = function(grid, r1, c1, r2, c2) {
var cell = Array2D.get(grid, r1, c1);
var copied = Array2D.set(grid, r2, c2, cell);
return Array2D.set(copied, r1, c1, null);
};
// Swap the contents of two cells.
Array2D.swap = function(grid, r1, c1, r2, c2) {
var cell1 = Array2D.get(grid, r1, c1);
var cell2 = Array2D.get(grid, r2, c2);
var first = Array2D.set(grid, r2, c2, cell1);
return Array2D.set(first, r1, c1, cell2);
};
// Location / relationships
// ========================
// Determine whether the coordinate is on an edge.
Array2D.edge = function(grid, r, c) {
if (r === 0) return true;
if (c === 0) return true;
var width = Array2D.width(grid);
var height = Array2D.height(grid);
if (r === height - 1) return true;
if (c === width - 1) return true;
return false;
};
// Return the list of edges that the coordinate is on.
Array2D.edges = function(grid, r, c) {
var edges = [];
if (r === 0) edges.push(Array2D.EDGES.TOP);
if (c === 0) edges.push(Array2D.EDGES.LEFT);
var width = Array2D.width(grid);
var height = Array2D.height(grid);
if (r === height - 1) edges.push(Array2D.EDGES.BOTTOM);
if (c === width - 1) edges.push(Array2D.EDGES.RIGHT);
return edges;
};
// Determine whether the coordinate is on a corner.
Array2D.corner = function(grid, r, c) {
if (r === 0 && c === 0) return true;
var width = Array2D.width(grid);
var height = Array2D.height(grid);
if (r === 0 && c === width - 1) return true;
if (r === height - 1 && c === width - 1) return true;
if (r === height - 1 && c === 0) return true;
return false;
};
// Return the list of corners that the coordinate is on.
Array2D.corners = function(grid, r, c) {
var corners = [];
if (r === 0 && c === 0) corners.push(Array2D.CORNERS.TOP_LEFT);
var width = Array2D.width(grid);
var height = Array2D.height(grid);
if (r === 0 && c === width - 1) corners.push(Array2D.CORNERS.TOP_RIGHT);
if (r === height - 1 && c === width - 1) corners.push(Array2D.CORNERS.BOTTOM_RIGHT);
if (r === height - 1 && c === 0) corners.push(Array2D.CORNERS.BOTTOM_LEFT);
return corners;
};
// Determine whether the given cell is on a grid boundary, i.e.,
// the first/last cell in its row/column. If you need to detect
// edge-ness of a cell in a ragged grid, prefer this function.
Array2D.boundary = function(grid, r, c) {
if (r === 0) return true;
if (c === 0) return true;
var row = Array2D.row(grid, r);
var right = row.length - 1;
if (c === right) return true;
var col = Array2D.column(grid, c);
var bottom = col.length - 1;
if (r === bottom) return true;
return false;
};
// Return a list of boundaries that the cell is on. If you need to
// detect edges of a cell in a ragged grid, prefer this function.
Array2D.boundaries = function(grid, r, c) {
var boundaries = [];
if (r === 0) boundaries.push(Array2D.BOUNDARIES.UPPER);
if (c === 0) boundaries.push(Array2D.BOUNDARIES.LEFT);
var row = Array2D.row(grid, r);
var right = row.length - 1;
if (c === right) boundaries.push(Array2D.BOUNDARIES.RIGHT);
var col = Array2D.column(grid, c);
var bottom = col.length - 1;
if (r === bottom) boundaries.push(Array2D.BOUNDARIES.LOWER);
return boundaries;
};
// Detect whether the cell is on a 'crook', i.e. the first or last
// cell in a row *and* the first or last cell in a column. If you need to
// detect corner-ness of a cell in a ragged grid, prefer this function.
Array2D.crook = function(grid, r, c) {
if (r === 0 && c === 0) return true;
var row = Array2D.row(grid, r);
var right = row.length - 1;
var col = Array2D.column(grid, c);
var bottom = col.length - 1;
if (r === 0 && c === bottom) return true;
if (r === right && c === 0) return true;
if (r === right && c === bottom) return true;
return false;
};
// Return a list of 'crooks' that the cell is on. If you need to
// detect corners of a cell in a ragged grid, prefer this function.
Array2D.crooks = function(grid, r, c) {
var crooks = [];
var row = Array2D.row(grid, r);
var right = row.length - 1;
var col = Array2D.column(grid, c);
var bottom = col.length - 1;
if (r === 0 && c === 0) crooks.push(Array2D.CROOKS.UPPER_LEFT);
if (r === 0 && c === bottom) crooks.push(Array2D.CROOKS.LOWER_LEFt);
if (r === right && c === 0) crooks.push(Array2D.CROOKS.UPPER_RIGHT);
if (r === right && c === bottom) crooks.push(Array2D.CROOKS.LOWER_RIGHT);
return crooks;
};
// Determine whether the given coordinate is at the grid's center.
Array2D.center = function(grid, r, c) {
var width = Array2D.width(grid);
var height = Array2D.height(grid);
if (width % 2 === 0) return false;
if (height % 2 === 0) return false;
if (Math.floor(height / 2) !== r) return false;
if (Math.floor(width / 2) !== c) return false;
return true;
};
// Determine whether the given coordinate is interior (not on an
// edge or a corner).
Array2D.interior = function(grid, r, c) {
if (r === 0) return false;
if (c === 0) return false;
var width = Array2D.width(grid);
var height = Array2D.height(grid);
if (width < 3) return false; // 2xH grids have no interior
if (height < 3) return false; // Wx2 grids have no interior
if (r >= height - 1) return false;
if (c >= width - 1) return false;
return true;
};
// Return a list of all the quadrants the given cell is in.
Array2D.quadrants = function(grid, r, c) {
var quadrants = [];
var width = Array2D.width(grid);
var height = Array2D.height(grid);
var midcolumn = Math.floor(width / 2);
var midrow = Math.floor(height / 2);
if (r <= midrow && c > midcolumn) quadrants.push(Array2D.QUADRANTS.I);
if (r <= midrow && c <= midcolumn) quadrants.push(Array2D.QUADRANTS.II);
if (r > midrow && c <= midcolumn) quadrants.push(Array2D.QUADRANTS.III);
if (r > midrow && c > midcolumn) quadrants.push(Array2D.QUADRANTS.IV);
return quadrants;
};
// Return an array of all orthogonal cells to the coordinate.
Array2D.orthogonals = function(grid, r, c) {
var orthogonals = [];
orthogonals[0] = Array2D.get(grid, r - 1, c); // North
orthogonals[1] = Array2D.get(grid, r, c - 1); // West
orthogonals[2] = Array2D.get(grid, r, c + 1); // East
orthogonals[3] = Array2D.get(grid, r + 1, c); // South
return orthogonals;
};
// Return an array of all diagonal cells to the coordinate.
Array2D.diagonals = function(grid, r, c) {
var diagonals = [];
diagonals[0] = Array2D.get(grid, r - 1, c - 1); // Northwest
diagonals[1] = Array2D.get(grid, r - 1, c + 1); // Northeast
diagonals[2] = Array2D.get(grid, r + 1, c - 1); // Southwest
diagonals[3] = Array2D.get(grid, r + 1, c + 1); // Southeast
return diagonals;
};
// Return an array of all orthogonal and diagonal neighbors of the cell.
Array2D.neighbors = function(grid, r, c) {
var orthogonals = Array2D.orthogonals(grid, r, c);
var diagonals = Array2D.diagonals(grid, r, c);
var neighbors = [];
neighbors[0] = diagonals[0]; // Northwest
neighbors[1] = orthogonals[0]; // North
neighbors[2] = diagonals[1]; // Northeast
neighbors[3] = orthogonals[1]; // West
neighbors[4] = orthogonals[2]; // East
neighbors[5] = diagonals[2]; // Southwest
neighbors[6] = orthogonals[3]; // South
neighbors[7] = diagonals[3]; // Southeast
return neighbors;
};
// Return a subgrid representing all cells in the neighborhood of
// the given row-column coordinate.
Array2D.neighborhood = function(grid, r, c) {
var cell = Array2D.get(grid, r, c);
var neighbors = Array2D.neighbors(grid, r, c);
return [
[neighbors[0], neighbors[1], neighbors[2]],
[neighbors[3], cell, neighbors[4]],
[neighbors[5], neighbors[6], neighbors[7]]
];
};
// Return the Euclidean distance bewteen the two cell coordinates.
Array2D.euclidean = function(grid, r1, c1, r2, c2) {
return Math.sqrt(Math.pow(r2 - r1, 2) + Math.pow(c2 - c1, 2));
};
// Return the Chebyshev distance bewteen the two cell coordinates.
Array2D.chebyshev = function(grid, r1, c1, r2, c2) {
var v = Math.abs(r2 - r1);
var h = Math.abs(c2 - c1);
return (v > h) ? v : h;
};
// Return the Manhattan distance bewteen the two cell coordinates.
Array2D.manhattan = function(grid, r1, c1, r2, c2) {
return Math.abs(r2 - r1) + Math.abs(c2 - c1);
};
// Coordinates
// ===========
// Return the coordinates of every cell that the `finder` function
// returns truthy.
Array2D.find = function(grid, finder) {
var found = [];
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
if (finder(cell, i, j, grid)) {
found.push([i, j]);
}
}
}
return found;
};
// Return groups of coordinates, where groups are coordinates of
// all _orthogonally_ adjacent cells that return truthy for the `finder`
// function.
Array2D.contiguous = function(grid, finder, countDiagonals) {
var contiguous = [];
var checked = [];
// Dimensions are used for bounds-checking below.
var dimensions = Array2D.dimensions(grid);
var w = dimensions[0];
var h = dimensions[1];
// Iterate over the whole grid, checking for contiguous groups.
for (var i = 0; i < h; i++) {
for (var j = 0; j < w; j++) {
_findContiguous(grid[i][j], i, j, grid, w, h, contiguous, checked, finder, countDiagonals);
}
}
return contiguous;
};
// PRIVATE - RECURSIVE check for contiguous cells
function _findContiguous(cell, r, c, grid, w, h, contiguous, checked, finder, countDiagonals, group) {
if (!_hasChecked(checked, r, c)) {
checked[r] || (checked[r] = []);
checked[r][c] = true; // Avoid repeat checks
// No need to check out-of-bounds cells
if (c > -1 && c < w && r > -1 && r < h) {
// A truthy return value is a match
if (finder(cell, r, c, grid)) {
// Spawn a new group
if (!group) {
group = [];
// Push group into the collection
contiguous.push(group);
}
// Add cell's coordinate to the group
group.push([r, c]);
// Direction cache
var up = r - 1;
var down = r + 1;
var left = c - 1;
var right = c + 1;
// Orthogonal neighbors
if (up > -1 && up < h) _findContiguous(grid[up][c], up, c, grid, w, h, contiguous, checked, finder, countDiagonals, group);
if (down > -1 && down < h) _findContiguous(grid[down][c], down, c, grid, w, h, contiguous, checked, finder, countDiagonals, group);
if (left > -1 && left < w) _findContiguous(grid[r][left], r, left, grid, w, h, contiguous, checked, finder, countDiagonals, group);
if (right > -1 && right < w) _findContiguous(grid[r][right], r, right, grid, w, h, contiguous, checked, finder, countDiagonals, group);
// Diagonal neighbors (if desired)
if (countDiagonals) {
if (up > -1 && up < h && left > -1 && left < w) _findContiguous(grid[up][left], up, left, grid, w, h, contiguous, checked, finder, countDiagonals, group);
if (up > -1 && up < h && right > -1 && right < w) _findContiguous(grid[up][right], up, right, grid, w, h, contiguous, checked, finder, countDiagonals, group);
if (down > -1 && down < h && left > -1 && left < w) _findContiguous(grid[down][left], down, left, grid, w, h, contiguous, checked, finder, countDiagonals, group);
if (down > -1 && down < h && right > -1 && right < w) _findContiguous(grid[down][right], down, right, grid, w, h, contiguous, checked, finder, countDiagonals, group);
}
} else { /* The cell did not match; skip. */ }
} else { /* The cell was out-of-bounds; skip. */ }
} else { /* The cell was already checked; skip. */ }
}
// Return groups of coordinates, where groups are coordinates of
// all adjacent cells that return truthy for the `finder`
// function.
Array2D.touching = function(grid, finder) {
return Array2D.contiguous(grid, finder, true);
};
// PRIVATE - T/F if a coordinate has been checked.
function _hasChecked(checked, r, c) {
return checked[r] && checked[r][c] === true;
}
// Return coordinates of all surrounding cells
Array2D.surrounds = function(grid, r, c, allowOutOfBounds) {
var surrounds = [];
var d = Array2D.dimensions(grid);
var w = d[0];
var h = d[1];
var right = w - 1;
var bottm = h - 1;
if ((r > 0 && c > 0) || allowOutOfBounds) surrounds.push([r - 1, c - 1]); // nw
if ((r > 0) || allowOutOfBounds) surrounds.push([r - 1, c]); // n
if ((r > 0 && c < right) || allowOutOfBounds) surrounds.push([r - 1, c + 1]); // ne
if ((c > 0) || allowOutOfBounds) surrounds.push([r, c - 1]); // w
if ((c < right) || allowOutOfBounds) surrounds.push([r, c + 1]); // e
if ((r < bottm && c > 0) || allowOutOfBounds) surrounds.push([r + 1, c - 1]); // sw
if ((r < bottm) || allowOutOfBounds) surrounds.push([r + 1, c]); // s
if ((r < bottm && c < right) || allowOutOfBounds) surrounds.push([r + 1, c + 1]); // se
return surrounds;
};
// Import / export
// ===============
// Convert the given array (flat) into the standard grid format.
Array2D.fromArray = function(arr, rows, columns) {
var out = [];
for (var i = 0; i < rows; i++) {
out[i] = [];
for (var j = 0; j < columns; j++) {
out[i][j] = arr[i * columns + j];
}
}
return out;
};
// Convert the canvas pixel data into an Array2D-formatted grid.
Array2D.fromCanvas = function(canvas) {
var context = canvas.getContext('2d');
var image = context.getImageData(0, 0, canvas.width, canvas.width);
var width = image.width;
var height = image.height;
var data = image.data;
var colors = [];
for (var i = 0, l = data.length; i < l; i += 4) {
var r = data[i]
var g = data[i+1];
var b = data[i+2];
var a = data[i+3];
var color = [r,g,b,a];
colors.push(color);
}
return Array2D.fromArray(colors, height, width);
};
// Paint the grid data to the given canvas, running each cell
// through a `converter` function to produce a _rgba_ color array.
// That is, on output, every cell needs to look something like this:
// `[255,255,255,255]`
Array2D.toCanvas = function(grid, canvas, converter) {
var context = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
var image = context.createImageData(width, height);
var data = image.data;
var colors;
for (var i = 0, l1 = grid.length; i < l1; i++) {
var row = grid[i];
for (var j = 0, l2 = row.length; j < l2; j++) {
var cell = row[j];
colors = (converter) ? converter(cell, i, j, grid) : cell;
var idx = (i * width + j) * 4;
data[idx + 0] = colors[0];
data[idx + 1] = colors[1];
data[idx + 2] = colors[2];
data[idx + 3] = colors[3];
}
}
context.putImageData(image, 0, 0);
};
}.call(this));