In 1970, British mathematician John Conway invented the Game of Life. (Not to be confused with the 1860 boardgame Life.) A zero-player game based on a simple set of rules applied to a grid, the Game of Life creates emergent behavior of cellular automaton. Conway is a heavy hitter, with a lot of serious and foundational work in mathematics, things named after him, etc, so I assume this idea was just a lark — but it has become a classic in the study of emergent systems behavior, cellular automata, and a staple of computer science and coding, as a simple idea that tests basic things like grid design, looping animation, and offers natural ways to incorporate UI and other customizations.
There’s many, many, many great tutorials on the topic, but I tinkered with this recently and figured I’d share my approach. Specifically, I noticed most Javascript implementations and tutorials use the HTML5 Canvas but I’ll offer here a DOM approach. I’m aiming for an audience of beginner (but not novice) level familiarity with HTML, CSS, and JS.
The finished project is live and you can play with it here, or check out the source on Github.
The game occurs on a grid of cells which are either “alive” or “dead” and evolve turn-by-turn according to the following rules:
Some notes (important when we start coding this up):
These simple rules lead to surprisingly interesting behavior that can seem lifelike, and there are many well-known patterns (or “lifeforms”) of interest. For example, some small still-life:
And some small moving ones:
It’s worthwhile to pause and examine the behavior of these to understand, cell by cell, why they behave the way they do.
Then there’s some bigger ones, like Gosper’s glider gun. Interesting story (h/t Sagnik Battachaya) – the glider gun’s existence implies that initial patterns with a finite number of cells can eventually lead to configurations with an infinite number of cells. John Conway conjectured that such a thing was impossible but Bill Gosper discovered the Gosper Glider Gun in 1970, winning $50 from Conway.
Anyway, this is obviously a deep rabbit hole of experimentation, and there’s lots of resources if you want to keep going deeper, but let’s get on to our purpose which is to code it up so we can tinker with lifeforms ourselves.
One of our first choices is whether to use a Canvas or DOM (Document Object Model) approach. If you’re unfamiliar with this distinction, this comparison is a good overview. In short, the Canvas allows us to directly and efficiently manipulate shapes on the screen, with fast performance but at the cost of coding ease – shape locations, event listening, etc. all need to be handled explicitly by us. Canvas is undeniably faster – I think Google Docs switched to Canvas a few years ago – and so a lot of Game of Life implementations use it, aiming for gigantic grids.
Using the DOM, whether that’s a bunch of <svg>
elements or a bunch of <div>
s, requires us to direct the browser to arrange objects the way we want, at the cost of control and performance but with the opportunity to benefit from coding ease (think CSS and event handlers). Since my goal is to make a medium-size Game of Life grid, with at most a thousand or so objects, and to be able to have fun with adding UI features, I thought it’d be nice to implement using the DOM and CSS. In fact, since we’re just dealing with a bunch of squares, I’ll just use strictly div
s and no SVG at all.
Other notes:
This project is very small and we could do all in one index.html
file, but I’ll split out CSS to a style.css
file and the JS to a game.js
file, all in one directory.
I will be using jQuery! No judgment! Of course this could be made without jQuery but it certainly makes life easier with this amount of DOM manipulation and working in straight JS.
Lastly, I’m attempting to keep this as unadorned as possible so I won’t use classes or really any frameworks at all for the code. The model will be an array of arrays, all functions will be in the global namespace, etc.
Now onto the code.
To code up a medium-size square grid, we’ll use a <div>
in the body of our html to contain all the grid elements, use JS to populate this with a bunch of cells (also <div>
s), and then use CSS Grid to structure everything.
So in index.html
we’ll put this in <body>
:
<div id="grid"></div>
and then setup the grid with some CSS:
:root {
--gridwidth: 20;
--gridheight: 10;
--cellsize: 50px;
}
#grid {
text-align: center;
display: grid;
grid-template-rows: repeat(var(--gridheight), var(--cellsize));
grid-template-columns: repeat(var(--gridwidth), var(--cellsize));
justify-content: center; /* centers cells in grid div */
}
.cell {
background-color: white;
position: relative;
border: 0.5px solid lightgray;
width: calc(var(--cellsize) - 1);
height: calc(var(--cellsize) - 1);
}
.active {
background-color: blue;
}
Some key things to highlight:
:root
to set the grid variables. This allows us to use them in other CSS formatting and we can set them from our script later.display: grid
establishes the #grid
div with the CSS Grid framework. The grid-template-
styling forces the rows and columns to a certain number and size.active
to a cell when it’s alive, with the base class representing dead.Finally, in game.js
we’ll have the following code:
// grab these variables from :root
let gridheight = document.documentElement.style.getProperty('--gridheight');
let gridwidth = document.documentElement.style.getProperty('--gridwidth');
// initialize a state array to zeros
// this will represent the grid of cells -- 0 for dead, 1 for alive
let state = Array(gridheight).fill(0).map(x => Array(gridwidth).fill(0));
// set a few cells to alive
state[2][2] = 1;
state[2][3] = 1;
state[2][4] = 1;
function initGrid() {
// grab the grid div
let $grid = $('#grid');
for (let i=0; i<gridheight; i++) { // row
for (let j=0; j<gridwidth; j++) { // col
// define this cell
let $cell = $('<div>').addClass('cell');
// anywhere we turned on state, set to active
if (state[i][j] == 1) {
$cell.addClass('active');
}
// append this cell to grid
$grid.append($cell);
}
}
}
Now let’s cover the basics of the main game loop.
Now we’ll setup a loop to update the model (state
) and visualization. Basically, we’ll create a function called step()
that does one update to the grid, then call it iteratively.
function step() {
// create temp state to store active/non for next step
let temp = Array(gridheight).fill(0).map(x => Array(gridwidth).fill(0));
// check surrounding
for (let i=0; i<gridheight; i++) {
for (let j=0; j<gridwidth; j++) {
// count the surrounding population
let numAlive = 0;
for (let m=-1; m<=1; m++) {
for (let n=-1; n<=1; n++) {
numAlive += isAlive(i+m, j+n);
}
}
numAlive -= isAlive(i,j); // don't count center
// game logic
if (numAlive == 2) { // do nothing
temp[i][j] = state[i][j];
} else if (numAlive == 3) { // make alive
temp[i][j] = 1;
} else { // make dead
temp[i][j] = 0;
}
}
}
// apply new state to state
for (let i=0; i<gridheight; i++) {
for (let j=0; j<gridwidth; j++) {
state[i][j] = temp[i][j];
// apply to viz
let $c = $(`#cell_${i}_${j}`);
if (state[i][j] == 1) {
$c.addClass('active');
} else {
$c.removeClass('active');
}
}
}
// update state counter
numSteps++;
$('#stepcounter').html(`Step: ${numSteps}`);
// take another step
setTimeout( () => {
step();
}, 100);
}
All that’s left is to call these two functions:
// wait to run until DOM is ready
$(document).ready(function() {
initGrid();
step();
})
And we’re off!
There are seemingly endless ways we can take this basic structure and jazz it up. For a few basic UI ideas:
<div class="button" id="btn-run">Run</div>
<div class="button" id="btn-pause">Pause</div>
then style it with css:
.button {
width: 100px;
text-align: center;
margin: 2px;
padding: 2px;
border: 1px solid black;
border-radius: 10px;
}
and give it functionality on click with JS:
// maintain a global to track if we're running or not
let isRunning = false;
// RUN button
$('#btn_run').click(function(e) {
if (isRunning) {
return; // if we're already running, do nothing
}
// need to set this outside the step(), which is called internally
isRunning = true;
step();
});
// PAUSE button
$('#btn_pause').click(function(e) {
isRunning = false;
});
function initGrid() {
...
// add this line after you define $cell
// this allows us to select a specific cell later
$cell.attr('id', `cell_${i}_${j}`);
...
}
function random(p) {
// p needs to be a number between 0 and 1,
// to set the amount of randomness
// stop the game
isRunning = false;
// clear all cells
state = Array(gridheight).fill(0).map(x => Array(gridwidth).fill(0));
$('.cell').removeClass('active');
// make proportion `p` of cells active
for (let i=0; i<gridheight; i++) {
for (let j=0; j<gridwidth; j++) {
if (Math.random() < p) {
$(`#cell_${i}_${j}`).addClass('active');
state[i][j] = 1;
}
}
}
}
More interesting than UI/UX fiddling, I think, is modifying the structure of the game. Here’s a version of the game on a hexagonal grid, which is a slightly more daunting coding challenge. There’s tons of these different variations on the rules, like different colors of cells, 3-dimensions, or constrained grids.
That’s all for now! Hope you’ve enjoyed, let me know your thoughts.
Written on February 4th, 2024 by Steven Morse