Source: grid.js

"use strict";
/** @namespace GridRelated */

/**
 * Grid constructor (2 dimensional array).
 * all cells set with value '1' according to the pattern(coordinates), otherwise '0'.
 * @constructor
 * @memberof GridRelated
 * @param {int} rows
 * @param {int} lines
 * @param {array} pattern
 * @returns {Grid}
 */
function Grid(rows, lines, pattern) {
    var grid = [[]];
    for (var row = 0; row < rows; row++) {
        grid[row] = [];
        for (var line = 0; line < lines; line++) {
            grid[row][line] = 0;
        };
    };
    return populateGrid(grid, pattern);
}

/**
 * Returns new grid by given rows and lines size
 * and initialized with given pattern.
 * @member
 * @memberof GridRelated
 * @param {int} rows 
 * @param {int} lines 
 * @param {array} pattern
 */
function setupGrid(rows, lines, pattern) {
    try { // protect grid initialization
        assert((rows > 0 && lines > 0), `rows/lines must be larger than 0` +
            `(will start with defaults: rows=${defaults.rows}, lines=${defaults.lines})`);
    }
    catch (err) {
        rows = defaults.rows;
        lines = defaults.lines;
        console.log(`Error! ` + err);
    };
    // Using 'multiplier' to make our grid larger than the canvas size in order to give the notion
    // of infinite world. we can also loop the grid (see comment under 'knocKnock' function).
    var multiplier = 1.5;
    rows *= multiplier; lines *= multiplier;

    return Grid(rows, lines, pattern);
}



/**
 * Sets the given pattern(coordinates) on given grid.
 * @member
 * @memberof GridRelated
 * @param {array} grid
 * @param {array} pattern
 * @returns {Grid}
 */
function populateGrid(grid, pattern) {
    pattern.forEach(function (coordinates) {
        var x = coordinates[0], y = coordinates[1];
        if (grid[x] === undefined || grid[x][y] === undefined) {
            return grid;
        };
        grid[x][y] = 1;
    });
    return grid;
}

const cellSize = 8;
/**
 * Setups canvas context and binds it to the canvas object.
 * @member
 * @memberof GridRelated
 * @param {int} width
 * @param {int} height
 * @returns {canvas}
 */
function setupCanvas(width, height) {
    var canvas = document.getElementById('gameCanvas')
    canvas.height = height * cellSize;
    canvas.width = width * cellSize;

    canvas.ctx = canvas.getContext('2d');
    canvas.ctx.strokeStyle = 'lightgray';
    canvas.ctx.fillStyle = 'black';
    return canvas;
}

/**
 * Drawing the current grid values on given canvas.
 * @member
 * @memberof GridRelated
 * @param {canvas} canvas
 * @param {array} grid
 */
function draw(canvas, grid) {
    log("Drawing on canvas...");
    // clear canvas before new draw
    canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
    grid.forEach(function (row, x) {
        row.forEach(function (cell, y) {
            canvas.ctx.beginPath();
            canvas.ctx.rect(x * cellSize, y * cellSize, cellSize, cellSize);
            if (cell) {
                canvas.ctx.fill();
            } else {
                canvas.ctx.stroke();
            }
        });
    });
    log("Done drawing on canvas...");
}

/**
 * Returns an updated grid(game board) after one step according
 * to the game's rules.
 * @member
 * @memberof GridRelated
 * @see [game's rules]{@link https://en.wikipedia.org/wiki/Conways_Game_of_Life#Rules}.
 * @param {array} grid
 * @returns {Grid}
 */
function nextStep(grid) {
    log("Running next step on grid...");
    var newGrid = [];

    grid.forEach(function (row, x) {
        newGrid[x] = [];
        row.forEach(function (cellValue, y) {
            var alive = 0;

            switch (findNeighbours(x, y, grid)) {
                case 2:
                    alive = (cellValue > 0) ? 1 : 0;
                    break;
                case 3:
                    alive = 1;
            };
            newGrid[x][y] = alive;
        });
    });
    return newGrid;
}

/**
 * Returns the amount of nearby members
 * of the target cell at [x,y] point on grid.
 * @member
 * @memberof GridRelated
 * @param {*} x
 * @param {*} y
 * @param {*} grid
 */
function findNeighbours(x, y, grid) {
    var up = y - 1,
        down = y + 1,
        right = x + 1,
        left = x - 1;

    function knocKnock(x, y) {
        // We can't really make an infinite world(grid) but we can loop our
        // arrays on grid by checking for neighbours on both ends of the grid.
        // (Note: its destructing Gosper's glider gun after few moments).
        // enable the next 2 lines to create the loop effect:
        // if (x < 0) { x += defaults.rows };
        // if (y < 0) { y += defaults.lines };
        return grid[x] && grid[x][y];
    }

    var neighboursCount = 0;
    // upper line
    neighboursCount += knocKnock(x, up) ? 1 : 0;
    neighboursCount += knocKnock(left, up) ? 1 : 0;
    neighboursCount += knocKnock(right, up) ? 1 : 0;
    // current line
    neighboursCount += knocKnock(left, y) ? 1 : 0;
    neighboursCount += knocKnock(right, y) ? 1 : 0;
    // lower line
    neighboursCount += knocKnock(x, down) ? 1 : 0;
    neighboursCount += knocKnock(left, down) ? 1 : 0;
    neighboursCount += knocKnock(right, down) ? 1 : 0;

    return neighboursCount;
}