Last active
October 6, 2021 19:13
-
-
Save devill/00a04645298163d6546c4d208ecee1b4 to your computer and use it in GitHub Desktop.
Final code for: https://www.youtube.com/watch?v=aBSMhMBdxDA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import expect from 'expect.js' | |
| class GameOfLifeBoard { | |
| alive = {}; | |
| isAlive(cell) { | |
| return !!this.alive[cell]; | |
| } | |
| spawn(cell) { | |
| this.alive[cell] = cell | |
| return this; | |
| } | |
| list() { | |
| return Object.values(this.alive); | |
| } | |
| } | |
| class GameOfLife { | |
| board = new GameOfLifeBoard(); | |
| constructor(stayAliveRules, spawningRule) { | |
| this.stayAliveRules = stayAliveRules || [2,3]; | |
| this.spawningRule = spawningRule || [3]; | |
| } | |
| isAlive(cell) { | |
| return this.board.isAlive(cell); | |
| } | |
| spawn(cell) { | |
| this.board.spawn(cell) | |
| } | |
| evolve() { | |
| this.board = this.board | |
| .list() | |
| .flatMap((cell) => [cell, ...cell.neighbours()]) | |
| .filter(cell => this.shouldBeAlive(cell)) | |
| .reduce((next, cell) => next.spawn(cell), new GameOfLifeBoard()); | |
| } | |
| shouldBeAlive(cell) { | |
| let count = this.countNeighbours(cell); | |
| if(this.isAlive(cell)) { | |
| return this.stayAliveRules.includes(count); | |
| } else { | |
| return this.spawningRule.includes(count); | |
| } | |
| } | |
| countNeighbours(cell) { | |
| return cell.neighbours() | |
| .filter(cell => this.isAlive(cell)) | |
| .length | |
| } | |
| } | |
| class Cell { | |
| constructor(x, y) { | |
| this.x = x; | |
| this.y = y; | |
| } | |
| toString() { | |
| return `(${this.x},${this.y})` | |
| } | |
| neighbours() { | |
| return [ | |
| { x: -1, y: -1}, | |
| { x: -1, y: 0}, | |
| { x: -1, y: 1}, | |
| { x: 0, y: -1}, | |
| { x: 0, y: 1}, | |
| { x: 1, y: -1}, | |
| { x: 1, y: 0}, | |
| { x: 1, y: 1}, | |
| ] | |
| .map(delta => new Cell(this.x + delta.x, this.y + delta.y)) | |
| } | |
| } | |
| class HexagonalCell { | |
| constructor(x,y) { | |
| this.x = x; | |
| this.y = y; | |
| } | |
| toString() { | |
| return `hex(${this.x},${this.y})`; | |
| } | |
| neighbours() { | |
| return [ | |
| { x: -1, y: 0}, | |
| { x: -1, y: 1}, | |
| { x: 0, y: -1}, | |
| { x: 0, y: 1}, | |
| { x: 1, y: -1}, | |
| { x: 1, y: 0}, | |
| ] | |
| .map(delta => new HexagonalCell(this.x + delta.x, this.y + delta.y)) | |
| } | |
| } | |
| class MultiDimensionalCell { | |
| constructor(coordinates) { | |
| this.coordinates = coordinates; | |
| } | |
| toString() { | |
| return `(${this.coordinates.join(',')})`; | |
| } | |
| neighbours() { | |
| return this.coordinates.reduce((neighbours, c) => | |
| neighbours.flatMap(nc => | |
| [ | |
| [...nc, c - 1], | |
| [...nc, c], | |
| [...nc, c + 1], | |
| ]), [[]]) | |
| .map(c => new MultiDimensionalCell(c)) | |
| .filter(c => c.toString() !== this.toString()); | |
| } | |
| } | |
| describe("GameOfLife", () => { | |
| let game = null; | |
| const cell = (x, y) => new Cell(x, y); | |
| const expectDead = (cell) => expect(game.isAlive(cell)).to.be(false); | |
| const expectAlive = (cell) => expect(game.isAlive(cell)).to.be(true); | |
| beforeEach(() => { | |
| game = new GameOfLife(); | |
| }); | |
| specify("that an empty world has dead (0,0) cell", () => { | |
| expectDead(cell(0,0)); | |
| }); | |
| specify("that a (0,0) cell can be spawned", () => { | |
| game.spawn(cell(0,0)); | |
| expectAlive(cell(0,0)); | |
| }); | |
| specify("that a (3,5) cell can be spawned independently of (0,0) cell", () => { | |
| game.spawn(cell(3, 5)); | |
| expectDead(cell(0,0)); | |
| }); | |
| specify("that empty world has dead (0,0) cell after being evolved", () => { | |
| game.evolve(); | |
| expectDead(cell(0,0)); | |
| }); | |
| specify("that lonely (0,0) cell dies when evolved", () => { | |
| game.spawn(cell(0,0)); | |
| game.evolve(); | |
| expectDead(cell(0,0)); | |
| }); | |
| specify("that (0,0) cell with single neighbour (-1, -1) dies", () => { | |
| game.spawn(cell( 0, 0)); | |
| game.spawn(cell(-1,-1)); | |
| game.evolve(); | |
| expectDead(cell(0,0)); | |
| }); | |
| [ | |
| cell(-1, 0), | |
| cell(-1, 1), | |
| cell( 0,-1), | |
| cell( 0, 1), | |
| cell( 1,-1), | |
| cell( 1, 0), | |
| cell( 1, 1) | |
| ].forEach(neighbourCell => { | |
| specify(`that (0,0) cell with two neighbours (-1, -1) and ${neighbourCell} stays alive`, () => { | |
| game.spawn(cell( 0, 0)); | |
| game.spawn(cell(-1,-1)); | |
| game.spawn(neighbourCell); | |
| game.evolve(); | |
| expectAlive(cell(0,0)); | |
| }); | |
| }); | |
| specify("that (0,0) cell with three neighbours (-1, -1), (-1, 0) and (-1,1) stays alive", () => { | |
| game.spawn(cell( 0, 0)); | |
| game.spawn(cell(-1,-1)); | |
| game.spawn(cell(-1, 0)); | |
| game.spawn(cell(-1, 1)); | |
| game.evolve(); | |
| expectAlive(cell(0,0)); | |
| }); | |
| specify("that dead (0,0) cell with three neighbours (-1, -1), (-1, 0) and (-1,1) spawn", () => { | |
| game.spawn(cell(-1,-1)); | |
| game.spawn(cell(-1, 0)); | |
| game.spawn(cell(-1, 1)); | |
| game.evolve(); | |
| expectAlive(cell(0,0)); | |
| }); | |
| specify("that dead (0,0) cell with two neighbours (0, -1) and (1,1) stays dead", () => { | |
| game.spawn(cell( 0,-1)); | |
| game.spawn(cell( 1, 1)); | |
| game.evolve(); | |
| expectDead(cell(0,0)); | |
| }); | |
| specify("that (3,5) cell with two neighbours stays alive", () => { | |
| game.spawn(cell(3,5)); | |
| game.spawn(cell(3,4)); | |
| game.spawn(cell(4,6)); | |
| game.evolve(); | |
| expectAlive(cell(3,5)); | |
| }); | |
| specify("that (-2,8) cell with two neighbours stays alive", () => { | |
| game.spawn(cell(-2,8)); | |
| game.spawn(cell(-1,7)); | |
| game.spawn(cell(-3,9)); | |
| game.evolve(); | |
| expectAlive(cell(-2,8)); | |
| }); | |
| specify("that single neighbour stay alive rule works", () => { | |
| game = new GameOfLife([1]); | |
| game.spawn(cell(0,0)); | |
| game.spawn(cell(0,1)); | |
| game.evolve(); | |
| expectAlive(cell(0,0)); | |
| }); | |
| specify("that single neighbour spawn rule works", () => { | |
| game = new GameOfLife([], [1]); | |
| game.spawn(cell(0,1)); | |
| game.evolve(); | |
| expectAlive(cell(0,0)); | |
| }); | |
| specify("that zero neighbour stay alive rule works", () => { | |
| game = new GameOfLife([0]); | |
| game.spawn(cell(0,0)); | |
| game.evolve(); | |
| expectAlive(cell(0,0)); | |
| }); | |
| [ | |
| [new HexagonalCell(0,0), 'hex(0,0)'], | |
| [new HexagonalCell(4,9), 'hex(4,9)'], | |
| ].forEach(([cell, stringRepresentation]) => { | |
| specify(`that hexagonal cell ${stringRepresentation} can be converted to string`, () => { | |
| expect(cell.toString()).to.be(stringRepresentation); | |
| }); | |
| }); | |
| specify("that hexagonal cell has neighbours", () => { | |
| let cell = new HexagonalCell(0,0); | |
| expect(cell.neighbours()).to.eql([ | |
| new HexagonalCell(-1, 0), | |
| new HexagonalCell(-1, 1), | |
| new HexagonalCell( 0,-1), | |
| new HexagonalCell( 0, 1), | |
| new HexagonalCell( 1,-1), | |
| new HexagonalCell( 1, 0), | |
| ]); | |
| }); | |
| specify("that hexagonal cell (0,0) with non neighbour (-1,-1) and (1,1) dies", () => { | |
| game.spawn(new HexagonalCell(0,0)); | |
| game.spawn(new HexagonalCell(1,1)); | |
| game.spawn(new HexagonalCell(-1,-1)); | |
| game.evolve(); | |
| expectDead(new HexagonalCell(0,0)); | |
| }); | |
| specify("that multi dimensional cell has string representation", () => { | |
| expect(new MultiDimensionalCell([0]).toString()).to.be('(0)'); | |
| expect(new MultiDimensionalCell([4]).toString()).to.be('(4)'); | |
| expect(new MultiDimensionalCell([4,9]).toString()).to.be('(4,9)'); | |
| }); | |
| [ | |
| [ | |
| new MultiDimensionalCell([0]), | |
| [ | |
| new MultiDimensionalCell([-1]), | |
| new MultiDimensionalCell([1]), | |
| ] | |
| ], | |
| [ | |
| new MultiDimensionalCell([5]), | |
| [ | |
| new MultiDimensionalCell([4]), | |
| new MultiDimensionalCell([6]), | |
| ] | |
| ], | |
| [ | |
| new MultiDimensionalCell([5,9]), | |
| [ | |
| new MultiDimensionalCell([4,8]), | |
| new MultiDimensionalCell([4,9]), | |
| new MultiDimensionalCell([4,10]), | |
| new MultiDimensionalCell([5,8]), | |
| new MultiDimensionalCell([5,10]), | |
| new MultiDimensionalCell([6,8]), | |
| new MultiDimensionalCell([6,9]), | |
| new MultiDimensionalCell([6,10]), | |
| ] | |
| ] | |
| ].forEach(([cell, result]) => { | |
| specify(`that multi dimensional cell ${cell} has neighbours`, () => { | |
| expect(cell.neighbours()).to.eql(result); | |
| }); | |
| }); | |
| specify("that multi dimensional cell spawning works", () => { | |
| game.spawn(new MultiDimensionalCell([1,0,0])); | |
| game.spawn(new MultiDimensionalCell([0,1,0])); | |
| game.spawn(new MultiDimensionalCell([0,0,1])); | |
| game.evolve(); | |
| expectAlive(new MultiDimensionalCell([0,0,0])); | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment