Created
April 14, 2017 03:12
-
-
Save MattMcFarland/d96d3133eb59ff248129f67a6eb4c39e to your computer and use it in GitHub Desktop.
A 2d grid library (with unit tests)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const assert = require('assert') | |
const transpose = (array) => array.reduce((p, n) => n.map((item, i) => [...(p[i] || []), n[i]]), []) | |
/** | |
* @class Grid | |
*/ | |
class Grid { | |
/** | |
* @static transpose | |
* @param grid - grid to transpose | |
* @returns new grid that is transposed. | |
*/ | |
static transpose (grid) { | |
let _grid = new Grid(grid.height, grid.width, undefined) | |
_grid.cells = transpose(grid.cells) | |
return _grid | |
} | |
/** | |
* @static fromArray | |
* @param array {Array[][]} 2D array[x][y] | |
* @returns new grid | |
*/ | |
static fromArray (arr) { | |
assert(arr.length, 'array must be 2D eg [][]') | |
assert(arr[0].length, 'array must be 2D eg [][]') | |
const width = arr.length | |
const height = arr[0].length | |
return new Grid(width, height, (x, y) => arr[x][y]) | |
} | |
/** | |
* @constructor | |
* @param width {Integer} x-axis size | |
* @param height {Integer} y-axis size | |
* @param fill {any|Function} set the value for every sell, if you pass a function it will pass x,y and accept its return value | |
*/ | |
constructor (width, height, fill) { | |
assert(Number.isInteger(width), 'argument must be an integer') | |
assert(Number.isInteger(height), 'argument must be an integer') | |
this.cells = [] | |
for (let x = 0; x < width; x++) { | |
for (let y = 0; y < height; y++) { | |
this.cells[x] = this.cells[x] || [] | |
this.cells[x][y] = typeof fill === 'function' ? fill(x, y) : fill | |
} | |
} | |
} | |
/** | |
* @property {Integer} length | |
*/ | |
get length () { | |
return this.width * this.height | |
} | |
/** | |
* @property {Integer} length | |
*/ | |
get width () { | |
return this.cells.length | |
} | |
/** | |
* @property {Integer} height | |
*/ | |
get height () { | |
return this.cells[0].length | |
} | |
/** | |
* @method setCell | |
* @param x {Integer} | |
* @param y {Integer} | |
* @returns {any} value | |
*/ | |
setCell (x, y, value) { | |
this.cells[x][y] = value | |
return this | |
} | |
mutate (fromX, fromY, toX, toY, cb) { | |
assert(Number.isInteger(fromX), 'argument must be an integer') | |
assert(Number.isInteger(fromY), 'argument must be an integer') | |
assert(Number.isInteger(toX), 'argument must be an integer') | |
assert(Number.isInteger(toY), 'argument must be an integer') | |
const x1 = Math.min(fromX, toX) | |
const y1 = Math.min(fromY, toY) | |
const x2 = Math.max(fromX, toX) | |
const y2 = Math.max(fromY, toY) | |
for (let y = y1; y <= y2; y++) { | |
for (let x = x1; x <= x2; x++) { | |
this.setCell(x, y, cb(this.cells[x][y], x, y)) | |
} | |
} | |
return this | |
} | |
/** | |
* @param cb {Function} callback function | |
*/ | |
forEach (cb) { | |
for (let x = 0; x <= this.width - 1; x++) { | |
for (let y = 0; y <= this.height - 1; y++) { | |
cb(this.cells[x][y], x, y) | |
} | |
} | |
return this | |
} | |
/** | |
* @returns a new array representing the 2d array from grid. | |
*/ | |
toArray () { | |
return [...this.cells] | |
} | |
toString (xDelimeter = ',', yDelimeter = '|') { | |
let str = '' | |
this.forEach((cell, x, y) => { | |
str += cell | |
if (x < this.width && y < this.height - 1) str += xDelimeter | |
if (y === this.height - 1 && x < this.width - 1) str += yDelimeter | |
}) | |
return str | |
} | |
map (cb) { | |
let _grid = new Grid(this.width, this.height, undefined) | |
this.forEach((cells, x, y) => { | |
_grid.cells[x][y] = cb(cells, x, y) | |
}) | |
return _grid | |
} | |
} | |
module.exports = Grid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Grid = require('../lib/Grid') | |
const test = require('tape') | |
test('Grid', (t) => { | |
t.test('props', (t) => { | |
const grid = new Grid(3, 3, 0) | |
t.assert(grid.length === 9, 'length') | |
t.assert(grid.width === 3, 'width') | |
t.assert(grid.height === 3, 'height') | |
t.end() | |
}) | |
t.test('forEach', (t) => { | |
const grid = new Grid(3, 3, 0) | |
grid.forEach((value, x, y) => { | |
t.assert(value === 0, `grid at ${x},${y} is zero`) | |
}) | |
t.end() | |
}) | |
t.test('map', (t) => { | |
const grid = new Grid(3, 3, 0) | |
const grid2 = grid.map(value => 1) | |
grid.forEach((value, x, y) => { | |
t.assert(value === 0, `original grid at ${x},${y} is still 0`) | |
}) | |
grid2.forEach((value, x, y) => { | |
t.assert(value === 1, `mapped grid at ${x},${y} is 1`) | |
}) | |
t.end() | |
}) | |
t.test('toString', (t) => { | |
const grid = new Grid(3, 3, 0).map((v, x, y) => x) | |
t.assert(grid.toString() === '0,0,0|1,1,1|2,2,2', 'default delimeter') | |
t.assert(grid.toString('-') === '0-0-0|1-1-1|2-2-2', 'x delimeter') | |
t.assert(grid.toString('-', '*') === '0-0-0*1-1-1*2-2-2', 'y delimeter') | |
t.end() | |
}) | |
t.test('toArray', (t) => { | |
const grid = new Grid(2, 3, 0).map((v, x, y) => y) | |
grid.toArray().forEach((row, x) => { | |
row.forEach((value, y) => { | |
t.assert(grid.cells[x][y] === value) | |
}) | |
}) | |
t.end() | |
}) | |
t.test('fromArray', (t) => { | |
const grid = new Grid(3, 2, 0).map((v, x, y) => y) | |
const gridA = grid.toArray() | |
const grid2 = Grid.fromArray(gridA) | |
t.assert(grid2.toString() === grid.toString()) | |
t.end() | |
}) | |
t.test('mutate', (t) => { | |
const grid = Grid.fromArray([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) | |
grid.mutate(0, 0, 2, 0, (cell) => 'x') | |
t.assert(grid.toString() === 'x,0,0|x,1,1|x,2,2', 'change first row') | |
grid.mutate(2, 0, 0, 0, (cell) => 'z') | |
t.assert(grid.toString() === 'z,0,0|z,1,1|z,2,2', 'iterates in reverse') | |
t.end() | |
}) | |
t.test('transpose', (t) => { | |
const _grid = new Grid(2, 4, 0).map((v, x, y) => x + y) | |
const grid = Grid.transpose(_grid) | |
t.assert(grid.width === _grid.height, 'height reflects transposition') | |
t.assert(grid.height === _grid.width, 'width reflects transposition') | |
t.assert(grid.cells[0][1] === _grid.cells[1][0], 'values transposed') | |
t.end() | |
}) | |
t.end() | |
}) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment