Skip to content

Instantly share code, notes, and snippets.

@holmberd
Last active May 18, 2022 14:52

Revisions

  1. holmberd renamed this gist May 18, 2022. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. holmberd revised this gist Sep 22, 2021. 1 changed file with 0 additions and 3 deletions.
    3 changes: 0 additions & 3 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -141,14 +141,11 @@ class World {

    countAliveNeighbours(location) {
    const cellNeighbourPositions = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]];

    return cellNeighbourPositions.reduce((count, [rowPos, colPos]) => {
    let neighbourLocation = new Location(location.row + rowPos, location.col + colPos);

    if (this.inBounds(neighbourLocation) && this.getCell(neighbourLocation).isAlive) {
    count++;
    }

    return count;
    }, 0);
    }
  3. holmberd revised this gist Sep 8, 2021. 1 changed file with 10 additions and 10 deletions.
    20 changes: 10 additions & 10 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -12,11 +12,11 @@
    *
    * Example usage:
    * const boardStr = '......\n' +
    * '***...\n' +
    * '......\n' +
    * '......\n' +
    * '......\n' +
    * '......\n';
    * '***...\n' +
    * '......\n' +
    * '......\n' +
    * '......\n' +
    * '......\n';
    * const world = createWorld(boardStr);
    * world.evolve();
    * print(world);
    @@ -113,11 +113,11 @@ class World {
    return this.board[location.row][location.col];
    }

    /**
    * Evolves the current World state one generation using the rules of the game.
    *
    * @public
    */
    /**
    * Evolves the current World state one generation using the rules of the game.
    *
    * @public
    */
    evolve() {
    this.board = this.evolveBoard(this.board);
    this.generation++;
  4. holmberd revised this gist Sep 8, 2021. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -129,10 +129,10 @@ class World {
    }

    evolveRow(row, rowIndex) {
    return row.map((cell, colIndex) => this.evolveCell(new Location(rowIndex, colIndex), cell));
    return row.map((cell, colIndex) => this.evolveCell(cell, new Location(rowIndex, colIndex)));
    }

    evolveCell(cellLocation, cell) {
    evolveCell(cell, cellLocation) {
    const aliveNeighbours = this.countAliveNeighbours(cellLocation);
    return cell.isAlive
    ? new Cell(this.rule.survival.some(surviveCount => surviveCount === aliveNeighbours))
    @@ -154,7 +154,7 @@ class World {
    }

    /**
    * Checks if a location is within the board bounds.
    * Checks if a location is within the bounds of the board.
    */
    inBounds({ row, col }) {
    return (row >= 0 && row < this.rows && col >= 0 && col < this.cols);
    @@ -174,15 +174,15 @@ function createWorld(boardStr) {

    function createBoard(boardStr) {
    const board = getBoardRows(boardStr);
    return board.map((row) => createRowCells(row));
    return board.map(createRowCells);
    }

    function getBoardRows(boardStr) {
    return boardStr.split('\n').slice(0, -1);
    }

    function createRowCells(row) {
    return row.split('').map((char) => convertCharToCell(char));
    return row.split('').map(convertCharToCell);
    }

    function convertCharToCell(char) {
  5. holmberd revised this gist Sep 8, 2021. 1 changed file with 11 additions and 11 deletions.
    22 changes: 11 additions & 11 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -11,13 +11,13 @@
    * 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
    *
    * Example usage:
    * const boardScheme = '......\n' +
    * const boardStr = '......\n' +
    * '***...\n' +
    * '......\n' +
    * '......\n' +
    * '......\n' +
    * '......\n';
    * const world = createWorld(boardScheme);
    * const world = createWorld(boardStr);
    * world.evolve();
    * print(world);
    *
    @@ -164,21 +164,21 @@ class World {
    /**
    * Helper for creating a new world from a string.
    *
    * @param {string} boardScheme
    * @param {string} boardStr
    * @return {World}
    */
    function createWorld(boardScheme) {
    const board = createBoard(boardScheme);
    function createWorld(boardStr) {
    const board = createBoard(boardStr);
    return new World(board);
    }

    function createBoard(boardScheme) {
    const board = getBoardRows(boardScheme);
    function createBoard(boardStr) {
    const board = getBoardRows(boardStr);
    return board.map((row) => createRowCells(row));
    }

    function getBoardRows(boardScheme) {
    return boardScheme.split('\n').slice(0, -1);
    function getBoardRows(boardStr) {
    return boardStr.split('\n').slice(0, -1);
    }

    function createRowCells(row) {
    @@ -207,15 +207,15 @@ function print(world) {
    }

    function test() {
    const boardScheme =
    const boardStr =
    '........\n' +
    '........\n' +
    '...***..\n' +
    '........\n' +
    '........\n' +
    '........\n';

    const world = createWorld(boardScheme);
    const world = createWorld(boardStr);
    print(world);
    world.evolve();
    print(world);
  6. holmberd revised this gist Sep 7, 2021. 1 changed file with 3 additions and 11 deletions.
    14 changes: 3 additions & 11 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -154,18 +154,10 @@ class World {
    }

    /**
    * Checks if a location is within the World bounds.
    * Checks if a location is within the board bounds.
    */
    inBounds(location) {
    if (location.row > (this.rows - 1) || location.row < 0) {
    return false;
    }

    if (location.col > (this.cols - 1) || location.col < 0) {
    return false;
    }

    return true;
    inBounds({ row, col }) {
    return (row >= 0 && row < this.rows && col >= 0 && col < this.cols);
    }
    }

  7. holmberd revised this gist Sep 5, 2021. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,9 @@
    /**
    * Conway's Game of Life
    *
    * The world of the Game of Life is an infinite two-dimensional orthogonal grid of square
    * "cells", each of which is in one of two possible states, alive or dead.
    *
    * Rules:
    * 1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
    * 2. Any live cell with two or three live neighbours lives on to the next generation.
  8. holmberd revised this gist Sep 5, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -14,7 +14,7 @@
    * '......\n' +
    * '......\n' +
    * '......\n';
    * const world = createWorld(board);
    * const world = createWorld(boardScheme);
    * world.evolve();
    * print(world);
    *
  9. holmberd revised this gist Sep 4, 2021. No changes.
  10. holmberd revised this gist Sep 4, 2021. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -122,10 +122,10 @@ class World {
    }

    evolveBoard(board) {
    return board.map((row, rowIndex) => this.evolveRow(rowIndex, row));
    return board.map((row, rowIndex) => this.evolveRow(row, rowIndex));
    }

    evolveRow(rowIndex, row) {
    evolveRow(row, rowIndex) {
    return row.map((cell, colIndex) => this.evolveCell(new Location(rowIndex, colIndex), cell));
    }

  11. holmberd revised this gist Sep 4, 2021. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -27,7 +27,7 @@
    * .*....
    */

    const CELL_CHAR = {
    const CellChar = {
    ALIVE: '*',
    DEAD: '.',
    };
    @@ -191,7 +191,7 @@ function createRowCells(row) {
    }

    function convertCharToCell(char) {
    return new Cell(char === CELL_CHAR.ALIVE);
    return new Cell(char === CellChar.ALIVE);
    }

    /**
    @@ -204,7 +204,7 @@ function print(world) {
    let str = '';
    for (const row of world.board) {
    for (const cell of row) {
    str += cell.isAlive ? CELL_CHAR.ALIVE : CELL_CHAR.DEAD;
    str += cell.isAlive ? CellChar.ALIVE : CellChar.DEAD;
    }
    str += '\n';
    }
  12. holmberd revised this gist Sep 4, 2021. 1 changed file with 5 additions and 7 deletions.
    12 changes: 5 additions & 7 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -141,14 +141,12 @@ class World {

    return cellNeighbourPositions.reduce((count, [rowPos, colPos]) => {
    let neighbourLocation = new Location(location.row + rowPos, location.col + colPos);

    if (this.inBounds(neighbourLocation)) {
    let neighbourCell = this.getCell(neighbourLocation);
    if (neighbourCell.isAlive) {
    count++;
    }
    return count;

    if (this.inBounds(neighbourLocation) && this.getCell(neighbourLocation).isAlive) {
    count++;
    }

    return count;
    }, 0);
    }

  13. holmberd revised this gist Sep 4, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -102,7 +102,7 @@ class World {
    }
    const set = new Set(board.map(row => row.length));
    if (set.size > 1) {
    throw Error('Rows must be of the same length');
    throw Error('Rows must be of equal length');
    }
    }

  14. holmberd revised this gist Sep 4, 2021. 1 changed file with 1 addition and 17 deletions.
    18 changes: 1 addition & 17 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -166,22 +166,6 @@ class World {

    return true;
    }

    /**
    * Prints the current state of the World board.
    *
    * @public
    */
    print() {
    let str = '';
    for (const row of this.board) {
    for (const cell of row) {
    str += cell.isAlive ? CELL_CHAR.ALIVE : CELL_CHAR.DEAD;
    }
    str += '\n';
    }
    console.log(str);
    }
    }

    /**
    @@ -213,7 +197,7 @@ function convertCharToCell(char) {
    }

    /**
    * Helper for printing the state of the game world.
    * Helper for printing the current state of the game world.
    *
    * @param {World} world
    */
  15. holmberd revised this gist Sep 4, 2021. 1 changed file with 27 additions and 27 deletions.
    54 changes: 27 additions & 27 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -1,31 +1,31 @@
    /**
    * Conway's Game of Life
    *
    * Rules:
    * 1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
    * 2. Any live cell with two or three live neighbours lives on to the next generation.
    * 3. Any live cell with more than three live neighbours dies, as if by overpopulation.
    * 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
    *
    * Example usage:
    * const boardScheme = '......\n' +
    * '***...\n' +
    * '......\n' +
    * '......\n' +
    * '......\n' +
    * '......\n';
    * const world = createWorld(board);
    * world.evolve();
    * print(world);
    *
    * ---------------------------
    * .*....
    * .*....
    * .*....
    * ......
    * ......
    * .*....
    */
    * Conway's Game of Life
    *
    * Rules:
    * 1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
    * 2. Any live cell with two or three live neighbours lives on to the next generation.
    * 3. Any live cell with more than three live neighbours dies, as if by overpopulation.
    * 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
    *
    * Example usage:
    * const boardScheme = '......\n' +
    * '***...\n' +
    * '......\n' +
    * '......\n' +
    * '......\n' +
    * '......\n';
    * const world = createWorld(board);
    * world.evolve();
    * print(world);
    *
    * ---------------------------
    * .*....
    * .*....
    * .*....
    * ......
    * ......
    * .*....
    */

    const CELL_CHAR = {
    ALIVE: '*',
  16. holmberd revised this gist Sep 4, 2021. 1 changed file with 5 additions and 6 deletions.
    11 changes: 5 additions & 6 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -212,7 +212,6 @@ function convertCharToCell(char) {
    return new Cell(char === CELL_CHAR.ALIVE);
    }


    /**
    * Helper for printing the state of the game world.
    *
    @@ -221,12 +220,12 @@ function convertCharToCell(char) {

    function print(world) {
    let str = '';
    for (const row of this.board) {
    for (const cell of row) {
    str += cell.isAlive ? CELL_CHAR.ALIVE : CELL_CHAR.DEAD;
    }
    str += '\n';
    for (const row of world.board) {
    for (const cell of row) {
    str += cell.isAlive ? CELL_CHAR.ALIVE : CELL_CHAR.DEAD;
    }
    str += '\n';
    }
    console.log(str);
    }

  17. holmberd created this gist Sep 4, 2021.
    248 changes: 248 additions & 0 deletions game_of_life_good.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,248 @@
    /**
    * Conway's Game of Life
    *
    * Rules:
    * 1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
    * 2. Any live cell with two or three live neighbours lives on to the next generation.
    * 3. Any live cell with more than three live neighbours dies, as if by overpopulation.
    * 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
    *
    * Example usage:
    * const boardScheme = '......\n' +
    * '***...\n' +
    * '......\n' +
    * '......\n' +
    * '......\n' +
    * '......\n';
    * const world = createWorld(board);
    * world.evolve();
    * print(world);
    *
    * ---------------------------
    * .*....
    * .*....
    * .*....
    * ......
    * ......
    * .*....
    */

    const CELL_CHAR = {
    ALIVE: '*',
    DEAD: '.',
    };

    /**
    * Represents a World rule determining if a Cell lives or dies.
    *
    * @constructor
    * @param {string} ruleStr
    * @return {Rule}
    */
    class Rule {
    constructor(ruleStr) {
    const [born, survival] = ruleStr.split('/');
    this.born = born.split('').map(Number);
    this.survival = survival.split('').map(Number);
    }
    }

    /**
    * Represents a single Cell on the Board.
    *
    * @constructor
    * @param {boolean} state - State of a Cell (Alive | Dead)
    * @return {Cell}
    */
    class Cell {
    constructor(state) {
    this.state = state;
    }

    get isAlive() {
    return this.state;
    }
    }

    /**
    * Represents a Location on the Board.
    *
    * @constructor
    * @param {number} rowIndex
    * @param {number} colIndex
    * @return {Location}
    */
    class Location {
    constructor(rowIndex, colIndex) {
    this.row = rowIndex;
    this.col = colIndex;
    }
    }

    /**
    * Represents the game World.
    *
    * @constructor
    * @param {Array} board - Representation of the Board on which the Game of Life unfolds.
    * @return {World}
    */
    class World {
    constructor(board = []) {
    this.validateBoard(board);
    this.rows = board.length;
    this.cols = board[0].length;
    this.board = board;
    this.generation = 0;
    this.rule = new Rule('3/23'); // B3/S23 (Conway's Life)
    }

    validateBoard(board) {
    if (!board.length) {
    throw Error('Board is empty');
    }
    const set = new Set(board.map(row => row.length));
    if (set.size > 1) {
    throw Error('Rows must be of the same length');
    }
    }

    getCell(location) {
    return this.board[location.row][location.col];
    }

    /**
    * Evolves the current World state one generation using the rules of the game.
    *
    * @public
    */
    evolve() {
    this.board = this.evolveBoard(this.board);
    this.generation++;
    return this;
    }

    evolveBoard(board) {
    return board.map((row, rowIndex) => this.evolveRow(rowIndex, row));
    }

    evolveRow(rowIndex, row) {
    return row.map((cell, colIndex) => this.evolveCell(new Location(rowIndex, colIndex), cell));
    }

    evolveCell(cellLocation, cell) {
    const aliveNeighbours = this.countAliveNeighbours(cellLocation);
    return cell.isAlive
    ? new Cell(this.rule.survival.some(surviveCount => surviveCount === aliveNeighbours))
    : new Cell(this.rule.born.some(bornCount => bornCount === aliveNeighbours));
    }

    countAliveNeighbours(location) {
    const cellNeighbourPositions = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]];

    return cellNeighbourPositions.reduce((count, [rowPos, colPos]) => {
    let neighbourLocation = new Location(location.row + rowPos, location.col + colPos);

    if (this.inBounds(neighbourLocation)) {
    let neighbourCell = this.getCell(neighbourLocation);
    if (neighbourCell.isAlive) {
    count++;
    }
    return count;
    }
    }, 0);
    }

    /**
    * Checks if a location is within the World bounds.
    */
    inBounds(location) {
    if (location.row > (this.rows - 1) || location.row < 0) {
    return false;
    }

    if (location.col > (this.cols - 1) || location.col < 0) {
    return false;
    }

    return true;
    }

    /**
    * Prints the current state of the World board.
    *
    * @public
    */
    print() {
    let str = '';
    for (const row of this.board) {
    for (const cell of row) {
    str += cell.isAlive ? CELL_CHAR.ALIVE : CELL_CHAR.DEAD;
    }
    str += '\n';
    }
    console.log(str);
    }
    }

    /**
    * Helper for creating a new world from a string.
    *
    * @param {string} boardScheme
    * @return {World}
    */
    function createWorld(boardScheme) {
    const board = createBoard(boardScheme);
    return new World(board);
    }

    function createBoard(boardScheme) {
    const board = getBoardRows(boardScheme);
    return board.map((row) => createRowCells(row));
    }

    function getBoardRows(boardScheme) {
    return boardScheme.split('\n').slice(0, -1);
    }

    function createRowCells(row) {
    return row.split('').map((char) => convertCharToCell(char));
    }

    function convertCharToCell(char) {
    return new Cell(char === CELL_CHAR.ALIVE);
    }


    /**
    * Helper for printing the state of the game world.
    *
    * @param {World} world
    */

    function print(world) {
    let str = '';
    for (const row of this.board) {
    for (const cell of row) {
    str += cell.isAlive ? CELL_CHAR.ALIVE : CELL_CHAR.DEAD;
    }
    str += '\n';
    }
    console.log(str);
    }

    function test() {
    const boardScheme =
    '........\n' +
    '........\n' +
    '...***..\n' +
    '........\n' +
    '........\n' +
    '........\n';

    const world = createWorld(boardScheme);
    print(world);
    world.evolve();
    print(world);
    }

    test();