Game of Life
// import {log, toString} from './helpers.mjs';
import {Matrix} from './Matrix.mjs';
/* ⭐️ Conway's Game of Life
------------------------
• n = 3 -> alive
• n = 2 & alive -> alive
*/
export class GameOfLife {
// init
constructor(cells){
this._matrix = new Matrix(cells);
}
// game.cells
get cells() {
return this._matrix.cells;
}
// game.toString() or `${game}`
toString(){
// const d = ['⓪', '➊', '➋', '➌', '➍', '➎', '➏', '➐', '➑', '➒'];
const b = ['□', '■'];
return this._matrix.toString(' ', n => b[n]);
}
// game.nextGen
get nextGen(){
let m = this._matrix.expand();
// 2D array of neighbors count of each cell
let count = m.map((_,i,j) => m.sumOfCellsAround(i,j));
let neighbors = new Matrix(count);
// • n = 3 -> alive
// • n = 2 & alive -> alive
let arrAlive = neighbors
.map((n,i,j) => +(n === 3 || (n === 2 && m.cell(i,j))));
let alive = new Matrix(arrAlive).trim();
return new GameOfLife(alive.cells);
}
// return array of self + n generations
generations(n){
let arr = [this];
let parent = this;
for(let i = 0; i < n; i++) {
let child = parent.nextGen;
arr.push(child);
parent = child;
}
return arr;
}
}
import {toString} from './helpers.mjs';
// Matrix
// ----------------
// 1.1 - refactor
export class Matrix {
// init
constructor(arr) {
// ⭐️ deepcopy of `arr`, so we won't share `arr` with outer code.
let copy = this._deepCopy(arr);
const str = toString(copy);
const prefix = `❌ new Matrix():`;
// ⭐️ check if it's an array
if (!Array.isArray(copy)) {
throw new Error(`${prefix} "${str}" is NOT an array❗️`);
}
// copy IS an array
// if copy == [], let it pass & return
if (copy.length === 0) { this._array = []; return; }
// ⭐️ check if copy is 2D array (array of array)
if (!Array.isArray(copy[0])) {
throw new Error(`${prefix} "${str}" is NOT 2D array❗️`);
}
// copy[0] IS an array
// ⭐️ check if first row (copy[0]) contains element(s)
// first row empty, store [] instead & return;
if (copy[0].length === 0) { this._array = []; return; }
// now first row contains element(s)
// ⭐️ check if every row is Array & has same length;
const n = copy[0].length;
if (!copy.every(row => Array.isArray(row) && row.length === n)) {
throw new Error(`${prefix} "${str}" is NOT a matrix❗️`);
}
// passed all tests, let it pass.
// Note: we don't check if it's a 3D array or something else ...
this._array = copy;
}
isValidRowIndex(i){
return (0 <= i && i < this.rows);
}
isValidColIndex(j){
return (0 <= j && j < this.cols);
}
isValidCellIndex(i,j){
return (this.isValidRowIndex(i) && this.isValidColIndex(j));
}
// ith row
row(i){
if (!this.isValidRowIndex(i)) return undefined;
return this._array[i];
}
cell(i,j){
if (!this.isValidCellIndex(i,j)) return undefined;
return this._array[i][j];
}
// ⭐️ matrix.map() -> new 2D array (Note: not a new Matrix)
map(f){
return this._array.map((row, i) => row.map((n, j) => f(n,i,j)));
}
// deep copy an array (make sure we won't SHARE arr with outer code)
// for internal use only, external code should use `matrix.cells`.
_deepCopy(arr){
return JSON.parse(JSON.stringify(arr));
}
// return a copy of array
get cells(){
return this._deepCopy(this._array);
}
// matrix.rows
get rows(){
return this._array.length;
}
// matrix.cols
get cols(){
let firstRow = this._array[0];
// if _array == [], firstRow will be undefined,
// and the following code will return 0;
return ~~(firstRow && firstRow.length);
}
// matrix.isEmpty
get isEmpty() {
// we choose to DEFINE an empty matrix as [] (0x0 matrix)
return this.rows === 0;
}
// this.isZeroRow(i)
isZeroRow(rowIdx){
if (this.isEmpty || !this.isValidRowIndex(rowIdx)) return false;
return this._array[rowIdx].every(n => n === 0);
}
get isFirstRowZero(){
return this.isZeroRow(0);
}
get isLastRowZero(){
return this.isZeroRow(this.rows - 1);
}
// this.isZeroCol(j)
isZeroCol(colIdx){
if (this.isEmpty || !this.isValidColIndex(colIdx)) return false;
return this._array.every(row => row[colIdx] === 0);
}
get isFirstColZero(){
return this.isZeroCol(0);
}
get isLastColZero(){
return this.isZeroCol(this.cols - 1);
}
// matrix.copy()
copy(){
return new Matrix(this.cells);
}
// matrix.toString()
toString(join=' ', transform=(x=>x)){
if (this.isEmpty) return `empty matrix: []`;
let maxWidth = 0;
let temp;
// loop through array, get max width
// and turn array of values into array of strings
return this._array.map(row => row.map(n => {
temp = String(transform(n));
maxWidth = Math.max(maxWidth, temp.length);
return temp;
})).map(row =>
row
.map(str => str.padStart(maxWidth, ' '))
.join(join)
).join('\n');
}
}// end: Matrix
codesandbox ⟩ Matrix & GameOfLife (1.1)
Last updated