Pluralsight and Conway's Game of Life

Introduction

I've used Pluralsight for (almost) all of my training needs for the past eighteen months now and it just keeps getting better and better. The only downside to the library is that good courses tend to suck up a lot of spare time; if you pay attention and watch them end-to-end. This is why I like the Play by Play format where you get to spend a couple of hours looking over the shoulder of an expert as they tackle a problem. Beyond the solution arrived at, which is often interesting, I derive real value from observing someone else's workflow and the tools that make them productive.

A great example of this is the session with Lea Verou where she tackles the well known Conway's Game of Life model. The rules to this game are deceptively simple and Lea knocks them out with alacrity; what's more revealing is seeing how she structures her Javascript logic and enhances the playability of the game through CSS tweaking. This makes sense given that Lea is a W3C member and front-end expert; just a glance at her blog makes it clear the she is both passionate about the web and happy to get stuck in to problems.

The only drawback with this video, and perhaps the Play by Play approach itself, is that it's coding by the seat of the pants; hacking if you like but in a good way. So there are no tests and very little in the way of refactoring takes place - which is fine but also incomplete. So I've taken some of Lea's ideas (and most of her styling) and decided to see how the code turns out if I start from scratch and proceed TDD (test-driven development) style.

Project Initialisation

Setting up a Javascript TDD project is easier than it might appear these days, due to the ubiquity of npm (node package manager), but it's still important to prepare the ground in a sensible order. In this case I know that I want to edit the source-code with Brackets, manage it with Git and test it with Jasmine (using Karma as a test runner). Beyond this I'm avoiding dependency on external libraries since raw Javascript can easily handle the project requirements.

To get off the ground there are a few pre-requisites:

1) Install Git for local source-code management

2) Install Node js so that we can easily install node packages and perform testing

3) Update npm globally to the latest version because it gets updated more frequently than Node:

$ npm install -g npm

4) Install Karma Command Line and PhantomJS packages globally for Javascript testing:

$ npm install -g karma-cli
$ npm install -g phantomjs

With these tools installed this project, and any other, can be initialised:

1) Set up a repository to control source code changes:

$ git init

2) Set up a node project to handle development dependencies so that testing can be performed:

$ npm init

3) These dependencies need to be kept out of the repository - because npm handles versioning of external packages - and we don't want, or need, to control these changes:

$ echo node_modules >> .gitignore

4) Install Karma locally with Jasmine and browser launcher packages for testing:

$ npm install --save-dev karma
$ npm install --save-dev karma-jasmine
$ npm install --save-dev karma-chrome-launcher
$ npm install --save-dev karma-phantomjs-launcher

5) Initialise Karma, using the standard default settings, so it watches files for changes and run tests:

$ karma init karma.config.js

6) Run the empty tests just to confirm that there are no missing dependencies:

$ karma start karma.config.js

With this framework in place it's easy enough to create a test specification file in Brackets (since we want to begin with a failing test) before creating the game module and fleshing it out. With each substantive change being committed to Git it's trivial to create tests, fill out the implementation, refactor as appropriate and then submit without fear. Each additional test provides confidence that you haven't broken existing functionality and any dead-end can be easily reverted from the source-code archive. In my experience this is a very effective way to code as it allows you to make far-reaching changes without fear.

Results

The results of this process are here but we can embed the game into this post which is nice:

Code on GitHub

Adding existing projects to GitHub is very straightforward; especially if you've used Git for source-control from the beginning. Once the project has been initialised, and some commits made, then it's possible to create, and connect to, a remote repository on GitHub. This involves initialising a blank project on GitHub and then linking to the unique URL:

$ git remote add origin *remote repository URL*

It's simple to check that the remote repository is linked properly:

$ git remote -v

Then just push your history of commits to GitHub:

$ git push -u origin master

The code, and commit history, for this project can be found here.

Conclusions

What I found most interesting about this exercise is that while the underlying logic is quite simple, and can be knocked out easily, it was much more of a challenge to come up with sensible tests. Part of the problem is that the game is just so open-ended (which is its secret sauce) and there are an infinite number of test scenarios (given an endless grid!). Of course most of these reduce to a small set of the same scenarios, and need to be pruned judiciously, but it's hard not to be influenced by known shapes such as the Blinker or the Loaf.

Either way once you have the basic implementation in place, and start considering what to do next, you realise just how wide-ranging the options are; which makes sense given that the game is Turing complete. There is just so much that you can do with these simple rules given enough time and a large enough grid. Plenty of examples are given on the comprehensive LifeWiki; here you can find 774 separate patterns such as the exotically named 487-tick reflector. Given that this requires a grid of 95x134 pixels it seems unlikely that you'd stumble across the pattern by chance.

More prosaically a nice aspect of this implementation is that, just off the top of my head, I can think of some useful additions: for a start adding flexibility over the grid size and timing interval will improve the user experience. Beyond that I'd like to make some technical improvements such as adding keyboard support and switching the UI to a Canvas implementation - just to see how easy it is to make such a change with the model/view separation. Finally I've found manually inputting different patterns rather painful since a one-block mistake generally leads to life dying out - so an automated tool for setting common patterns would be very useful.

comments powered by Disqus