# Game of Life

{% tabs %}
{% tab title="📗 參考" %}
* [x] codewars ⟩ [Conway's Game of Life](https://www.codewars.com/kata/52423db9add6f6fc39000354/javascript)  ⭐️
{% endtab %}

{% tab title="Second Tab" %}

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="GameOfLife" %}
```javascript
// 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;
  }
}
```
{% endtab %}

{% tab title="Matrix" %}
```javascript

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
```
{% endtab %}

{% tab title="⬆️ 需要" %}
* [helpers](../../../appendix/custom/helper-functions)
* [Matrix](../../../appendix/custom/class/matrix)
{% endtab %}

{% tab title="💾 程式" %}
* codesandbox ⟩ [Matrix & GameOfLife (1.1)](https://codesandbox.io/s/matrix-gameoflife-1-1-jfvr4?file=/src/Matrix.mjs)
{% endtab %}
{% endtabs %}
