A simple TypeScript Grid
. Written by Jon Wire for articles on jonwire.com.
This may evolve over time for use in a variety of articles and play. If you'd like this in library form as an NPM package, let me know in the comments.
A simple TypeScript Grid
. Written by Jon Wire for articles on jonwire.com.
This may evolve over time for use in a variety of articles and play. If you'd like this in library form as an NPM package, let me know in the comments.
function isInitializer<T>( | |
init: T | ((row: number, col: number) => T) | |
): init is (row: number, col: number) => T { | |
return typeof init === 'function'; | |
} | |
export class Grid<T> { | |
data: T[][]; | |
constructor( | |
public rows: number, | |
public cols: number, | |
init?: T | ((row: number, col: number) => T) | |
) { | |
this.data = new Array(rows); | |
for (let r = 0; r < rows; r++) { | |
this.data[r] = new Array(cols); | |
for (let c = 0; c < cols; c++) { | |
if (init) { | |
this.data[r][c] = | |
isInitializer(init) ? init(r, c) : init; | |
} | |
} | |
} | |
} | |
get(row: number, col: number): T | undefined { | |
return this.data[row]?.[col]; | |
} | |
set(row: number, col: number, value: T): void { | |
if (!this.data[row]) { | |
this.data[row] = []; | |
} | |
this.data[row][col] = value; | |
} | |
copy(): Grid<T> { | |
return this.map((value) => value); | |
} | |
map<NT>( | |
fn: (value: T, row: number, col: number, self: Grid<T>) => NT | |
): Grid<NT> { | |
const newGrid = new Grid<NT>(this.rows, this.cols); | |
for (let r = 0; r < this.rows; r++) { | |
for (let c = 0; c < this.cols; c++) { | |
newGrid.set(r, c, fn(this.get(r, c)!, r, c, this)); | |
} | |
} | |
return newGrid; | |
} | |
/** | |
* A list of the 8 neighbor values of a cell in a grid. | |
* | |
* In order from top left (NW) to bottom right (SE): | |
* | |
* ``` | |
* [ | |
* NW, N, NE, | |
* W, E, | |
* SW, S, SE, | |
* ] | |
* ``` | |
* | |
* @param row | |
* @param col | |
* @returns | |
*/ | |
getNeighbors( | |
row: number, | |
col: number, | |
): T[] { | |
return [ | |
this.get(row - 1, col - 1), | |
this.get(row - 1, col), | |
this.get(row - 1, col + 1), | |
this.get(row, col - 1), | |
this.get(row, col + 1), | |
this.get(row + 1, col - 1), | |
this.get(row + 1, col), | |
this.get(row + 1, col + 1) | |
].filter(v => v !== undefined) as T[]; | |
} | |
toImageData<U>( | |
mapFn: (value: T, row: number, col: number, self: Grid<T>) => U | |
): ImageData { | |
const imageData = new ImageData(this.cols, this.rows); | |
const data = imageData.data; | |
for (let r = 0; r < this.rows; r++) { | |
for (let c = 0; c < this.cols; c++) { | |
const value = mapFn(this.get(r, c)!, r, c, this); | |
const index = (r * this.cols + c) * 4; | |
if (Array.isArray(value) && value.length === 4) { | |
// Assume value is [R, G, B, A] | |
data[index] = value[0]; // R | |
data[index + 1] = value[1]; // G | |
data[index + 2] = value[2]; // B | |
data[index + 3] = value[3]; // A | |
} else { | |
throw new Error( | |
'mapFn must return an array of [R, G, B, A] values' | |
); | |
} | |
} | |
} | |
return imageData; | |
} | |
} |