Skip to content

Instantly share code, notes, and snippets.

@devill
Last active October 6, 2021 19:13
Show Gist options
  • Select an option

  • Save devill/00a04645298163d6546c4d208ecee1b4 to your computer and use it in GitHub Desktop.

Select an option

Save devill/00a04645298163d6546c4d208ecee1b4 to your computer and use it in GitHub Desktop.
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