Conway’s Game of Life is a “cellular automaton” — basically a grid of cells that can have two states, on and off (or live and dead, if we continue the “cell” metaphor). The simulation is constrained by a simple ruleset, and each iteration of the rules produces a new state. This results in some interesting patterns, as well as the capability to create self-sustaining structures (such as “guns”).
The rules of Conway’s Game of Life are so simple, that ususally the hard part of any implementation is actually displaying the game state. But fortunately, we have access to Waffle, which easily lets us display/update a grid on a webpage. Download the Waffle distribution, which has a game.js
example template file. You can then open the index.html
file (File → Open File → index.html) in a browser and immediately run your code.
The template defaults to a 10x10 grid, but we’ll make it slightly larger, in order to create some interesting patterns. Make the following change in game.js
:
- const rows = 10;
- const columns = 10;
+ const rows = 50;
+ const columns = 50;
The game.js
file already has a Waffle.onPointDown
handler pre-programmed, which toggles a highlight
class on a clicked or tapped cell. We can re-use this in order to set the initial state of simulation; consider a highlighted cell “alive” and an empty cell “dead.” So really all that needs to be done is figure out a way to run the Game of Life rules every second or so. In browser-based JavaScript, we can use the global setInterval
function to run some code at specified intervals.
Waffle.onPointDown(({ x, y }, { primary, secondary }) => {
console.log(`${secondary ? 'right' : 'left'}-clicked cell (${x}, ${y})`);
/* replace this with your own code! */
const state = Waffle.state;
if (Waffle.isEmpty(state[x][y])) {
state[x][y] = 'highlight';
} else {
state[x][y] = '';
}
Waffle.state = state;
});
+ const updateTimeInMilliseconds = 500;
+
+ setInterval(() => {
+ // updating the game state will happen here, every half second
+ }, updateTimeInMilliseconds);
OK, so what are the rules of Game of Life?
- Any live cell with fewer than two live neighbors dies
- Any live cell with two or three live neighbors continues to live
- Any live cell with more than three live neighbors dies
- Any dead cell with three live neighbors becomes alive again
We can loop through each cell in the grid and pretty easily apply these rules:
const updateTimeInMilliseconds = 500;
setInterval(() => {
- // updating the game state will happen here, every half second
+ const currentState = Waffle.state;
+ const newState = Waffle.state.map(column => column.fill('')); // create a blanked-out copy
+
+ for (let x = 0; x < rows; x += 1) {
+ for (let y = 0; y < columns; y += 1) {
+ const isAlive = currentState[x][y] === 'highlight';
+ const liveNeighbors = Waffle.getNeighbors({x, y}, true)
+ .filter(({x, y}) => currentState[x][y] === 'highlight')
+ .length;
+
+ // 1. Any live cell with fewer than two live neighbors dies
+ if (isAlive && liveNeighbors < 2) {
+ newState[x][y] = '';
+ }
+
+ // 2. Any live cell with two or three live neighbors continues to live
+ if (isAlive && (liveNeighbors === 2 || liveNeighbors === 3)) {
+ newState[x][y] = 'highlight';
+ }
+
+ // 3. Any live cell with more than three live neighbors dies
+ if (isAlive && liveNeighbors > 3) {
+ newState[x][y] = '';
+ }
+
+ // 4. Any dead cell with three live neighbors becomes alive again
+ if (!isAlive && liveNeighbors == 3) {
+ newState[x][y] = 'highlight';
+ }
+ }
+ }
+
+ Waffle.state = newState;
}, updateTimeInMilliseconds);
The problem with the above code is that the simulation updates too quickly for you to really set up any interesting structures. If you click quickly you might be able to highlight a few cells before the next half-second update runs, but it’s not very fun or interesting. To solve this problem let’s make a button that toggles the simulation on or off. In the index.html
file, under the “grid” <div>
, add a button:
<div id="grid"></div>
+ <button type="button" id="toggle">START</button>
Update the main.css
stylesheet to have the button appear below the grid:
body {
display: flex;
align-items: center;
justify-content: center;
+ flex-direction: column;
+ gap: 1rem;
}
Now, back in the game.js
source file, we can attach some functionality to the button. Instead of having the button enable/disable the entire update loop, we can have it toggle a boolean variable that will just skip the update logic.
+ let active = false;
window.setInterval(() => {
+ if (!active) {
+ return;
+ }
const currentState = Waffle.state;
We can then attach an event listener to the button to toggle the active
variable.
+ const toggleButton = document.querySelector('#toggle');
+
+ toggleButton.addEventListener('click', () => {
+ active = !active;
+ toggleButton.textContent = active ? 'STOP' : 'START';
+ });
This lets us spend as much time as we want setting up the simulation before letting it run.
There are a number of interesting self-sustaining cell configurations you can create; I’ll list a few here, along with some code that you can paste into your browser console to easily re-create them without having to tediously count and click a bunch of cells.
Gosper Glider Gun

const points = '[{"x":1,"y":5},{"x":1,"y":6},{"x":2,"y":5},{"x":2,"y":6},{"x":11,"y":5},{"x":11,"y":6},{"x":11,"y":7},{"x":12,"y":4},{"x":12,"y":8},{"x":13,"y":3},{"x":13,"y":9},{"x":14,"y":3},{"x":14,"y":9},{"x":15,"y":6},{"x":16,"y":4},{"x":16,"y":8},{"x":17,"y":5},{"x":17,"y":6},{"x":17,"y":7},{"x":18,"y":6},{"x":21,"y":3},{"x":21,"y":4},{"x":21,"y":5},{"x":22,"y":3},{"x":22,"y":4},{"x":22,"y":5},{"x":23,"y":2},{"x":23,"y":6},{"x":25,"y":1},{"x":25,"y":2},{"x":25,"y":6},{"x":25,"y":7},{"x":35,"y":3},{"x":35,"y":4},{"x":36,"y":3},{"x":36,"y":4}]';
const state = Waffle.state;
JSON.parse(points).forEach(({x, y}) => state[x][y] = 'highlight');
Waffle.state = state;
Simkin Glider Gun

const points = '[{"x":6,"y":18},{"x":6,"y":19},{"x":7,"y":18},{"x":7,"y":19},{"x":10,"y":21},{"x":10,"y":22},{"x":11,"y":21},{"x":11,"y":22},{"x":13,"y":18},{"x":13,"y":19},{"x":14,"y":18},{"x":14,"y":19},{"x":26,"y":35},{"x":26,"y":36},{"x":27,"y":28},{"x":27,"y":29},{"x":27,"y":30},{"x":27,"y":35},{"x":27,"y":37},{"x":28,"y":27},{"x":28,"y":30},{"x":28,"y":37},{"x":29,"y":27},{"x":29,"y":30},{"x":29,"y":37},{"x":29,"y":38},{"x":31,"y":27},{"x":32,"y":27},{"x":32,"y":31},{"x":33,"y":28},{"x":33,"y":30},{"x":34,"y":29},{"x":37,"y":29},{"x":37,"y":30},{"x":38,"y":29},{"x":38,"y":30}]';
const state = Waffle.state;
JSON.parse(points).forEach(({x, y}) => state[x][y] = 'highlight');
Waffle.state = state;
Various Oscillators

const points = '[{"x":4,"y":7},{"x":4,"y":8},{"x":4,"y":9},{"x":4,"y":13},{"x":4,"y":14},{"x":4,"y":15},{"x":6,"y":5},{"x":6,"y":10},{"x":6,"y":12},{"x":6,"y":17},{"x":7,"y":5},{"x":7,"y":10},{"x":7,"y":12},{"x":7,"y":17},{"x":8,"y":5},{"x":8,"y":10},{"x":8,"y":12},{"x":8,"y":17},{"x":9,"y":7},{"x":9,"y":8},{"x":9,"y":9},{"x":9,"y":13},{"x":9,"y":14},{"x":9,"y":15},{"x":11,"y":7},{"x":11,"y":8},{"x":11,"y":9},{"x":11,"y":13},{"x":11,"y":14},{"x":11,"y":15},{"x":12,"y":5},{"x":12,"y":10},{"x":12,"y":12},{"x":12,"y":17},{"x":13,"y":5},{"x":13,"y":10},{"x":13,"y":12},{"x":13,"y":17},{"x":14,"y":5},{"x":14,"y":10},{"x":14,"y":12},{"x":14,"y":17},{"x":16,"y":7},{"x":16,"y":8},{"x":16,"y":9},{"x":16,"y":13},{"x":16,"y":14},{"x":16,"y":15},{"x":17,"y":27},{"x":17,"y":28},{"x":17,"y":29},{"x":17,"y":30},{"x":17,"y":31},{"x":17,"y":32},{"x":17,"y":33},{"x":17,"y":34},{"x":18,"y":27},{"x":18,"y":29},{"x":18,"y":30},{"x":18,"y":31},{"x":18,"y":32},{"x":18,"y":34},{"x":19,"y":27},{"x":19,"y":28},{"x":19,"y":29},{"x":19,"y":30},{"x":19,"y":31},{"x":19,"y":32},{"x":19,"y":33},{"x":19,"y":34},{"x":28,"y":3},{"x":28,"y":8},{"x":28,"y":14},{"x":28,"y":15},{"x":29,"y":3},{"x":29,"y":8},{"x":29,"y":9},{"x":29,"y":14},{"x":30,"y":3},{"x":30,"y":8},{"x":30,"y":9},{"x":30,"y":17},{"x":31,"y":9},{"x":31,"y":16},{"x":31,"y":17}]';
const state = Waffle.state;
JSON.parse(points).forEach(({x, y}) => state[x][y] = 'highlight');
Waffle.state = state;