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; | |
| } | |
| } |