Conway's Game of Life
When?
~ November 2019
Where?
What?
This project is an implementation of Conway’s Game of Life, a ‘game’ that shows how a grid of cells that are either are alive or dead changes over time given: 1) a starting state of mixed cells, and 2) rule that define how a cells neighbours affects its status.
At the time I did this I was still doing my software development apprenticeship and only just learning about test-driven development. Much later, after I wrote the initial code and tests, I started teaching myself React and I revisited the project to create a visualisation of the game’s steps.
How?
My software development apprenticeship centred around JavaScript, so I used JavaScript for this project and the testing tools I’d encountered in lessons: chai assertion library and mocha testing framework.
Code
The code itself is one mega-class called GameOfLife, lol:
class GameOfLife {
constructor(board){
if(!this.initialStateValid(board)){
throw new Error('Make sure the supplied board is a 2D array, with consistent row lengths');
}
this.currentState = board;
this.largestYCoordinate = board.length - 1;
this.largestXCoordinate = board[0].length - 1;
...
}
Looking at the code now in 2022 there is a lot I would change but I’m also happy at how I can understand the code well enough due to clear naming and comments!
Testing
For the tests I had unit tests for different methods inside the class where I religiously used Given/When/Then commenting (I was so keen):
describe("Determining who should die (shouldCellDie function)", function() {
it("a single cell will die", function() {
/*
Given a game of life
When there's a single live cell at the center
*/
let board = [
[0,0,0],
[0,1,0],
[0,0,0]
];
let game = new Life(board);
const cell = {x:1, y:1};
// Then I expect to that the cell is meant to die
let death = game.shouldCellDie(cell.x,cell.y);
expect(death).to.be.true;
})
...
}
…and I also had some tests that tested different scenarios on a board, e.g what the next step of a game looks like with a given starting state.
describe("Scenario 5: Grid with no live cells", function() {
// Very similar test to scenario 1
// I think scenario 1 was scoped to just one position, and this test looks at the whole board
it("when the starting state has no live, no live appears following a turn", function() {
// Given a game of life with the initial state containing no live cells
let board = [
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0]
];
let game = new Life(board);
// When the game evolves one turn
game.takeTurn();
// Then the next state also contains no live cells
expect(game.searchForAnyLife()).to.be.false;
})
})
Why?
This task was as part of a job application (won’t say where for!) but I was excited to complete it as Game of Life was something I had previously encountered when I was still studying Biosciences and was interested in programming. At that point, approx. 2015, I only knew basic Python and attempted to implement it, failing horribly. And didn’t even know about testing at that point!