Building an interactive Conway’s Game of Life in vanilla JavaScript

Ian Schumann
Mar 14, 2018 · 12 min read

The short version is: I’ve completed my first serious code project in the Galvanize Web Dev Immersive — a unique and highly interactive rendition of Conway’s Game of Life.

Source here.

The long version

In the fall of 2017 I decided I wanted to make the switch from software consulting and client services to engineering. I found Galvanize in downtown Austin and was accepted to the spring program a couple weeks later. Over the winter I started devouring JavaScript basics via PluralSight, Udemy, and CodeAcademy. I left my job in January, and started the Galvanize course on Feb 5th.

Q: So, how’s it been going since then?
A: Effin’ great.

I knew I was ready to give engineering a solid shot and see how it went. But I wasn’t prepared to be totally enamored and fascinated. I feel like I’ve found the thing I was meant to do with my prime career years.

That brings us to my first big project. For each quarter of the Galvanize course, each of us spend a week feverishly building a solo project.

The Q1 Project: Conway’s Game of Life

Our first-quarter curriculum provided the ‘toolkit’ with which we’d build our projects. This included:

  • Client-side JS fundamentals (main focus)
  • Computer science foundations
  • A refresher on HTML and CSS
  • A quick look at TDD via Mocha / Chai
  • Intro to terminal, git, and npm
  • A few days on asynchrony, Promises and AJAX

So what can you build with that? Quite a lot, of course.

I had gotten to know my instructor by now and he’d been encouraging toward my eagerness and aptitude. At some point Conway’s Game of Life came up, and he mentioned that he’d always hoped for a student of his to build it for their Q1 project — as long as they did it the “right” way, with a properly decoupled model and view, etc.

I was sold on the idea, and was pretty confident I could hack it together in a few days, much less a week. So that was that.

Warming Up and spec’ing Stage 0

I wrote up a project proposal with plenty of specifics and a ton of stretch goals, then made a simple mockup in Balsamiq. The minimum viable product (which I called Stage 0) just had to have a way to draw on the game board, and advance forward in time while observing the official Conway rules of cell behavior.

Basic MVP mockup in Balsamiq

Most of my Stage 0 specs were concerned with the model — because Conway’s game is built around 4 paramount rules of cell behavior, and if those rules aren’t computed properly, then it’s not Conway’s game!

Having been exposed to TDD a little bit, I decided to use Mocha / Chai to build out some tests on the first day. This led me to a basic list of cell- and board-level behaviors.

Not knowing exactly where I was headed, I built out each chunk of tests just before adding the features described. Here’s a sampling of that test suite, by the end of this phase:

Test suites with Mocha / Chai

The timeframe for this project was a full 7 days, but I ended up hitting MVP on the model layer by the end of the first evening, after about 8 hours of code. The crowning achievement for that evening was seeing a successful propagation test in the console.

This was a sign I had achieved MVP with my Model, so it was time to move on to constructing a View in the browser, and binding them together with a Controller.

The View wasn’t bad! I programmatically generated a 2D array of HTML spans, and added dataset X and Y coordinates to each element which would correspond to its ‘partner’ inside the Model.

Event delegation and event bubbling made it easy to handle user interaction — a single eventListener went on the whole gameboard, and any click on a Cell would update background-color of the target span while updating the state of its partner within the Model.

Finally, the Step button would signal the Controller to trigger a mass-propagation of the Model, and then loop through the nodes in the View, updating the background-color of each to correspond with the latest state of the Model.

At the end of all this, I had two-way data binding and dirty checking — enough to hit Stage 0 by the evening of day 2, roughly 30 hours after the start of the project. I deployed this to as a memento.

Stage 0 — MVP complete

Stage 1: playback and clear features

With MVP done way earlier than expected, I moved on to my quite-deep backlog of stretch goals.

An easy enhancement beyond the MVP stage was to add the ability for continuous playback rather than stepwise propagation, and also the ability to clear the board and start over.

These were accomplished by adding just a couple more methods to Model, View, and Controller, and some corresponding controls in the UI. Being not much more complicated than stage 0, I got this done, and deployed for posterity, in the morning on Day 3:

Stage 1 — Added continuous playback and board reset

Stage 2: A Splash of Chroma

When I built up my reserve of stretch goals, I dreamed up something that wasn’t part of Conway’s original spec, and which would be pure fun and whimsy: what if you could have different colored Cells on the board, and have them intermingle and blend colors when they came in contact with each other? En masse, this would allow the user to set up different populations of cells (by color) and watch to see which patterns were most successful at propagation.

I was pretty sure I could get this done in a day or so, and it was an exciting prospect. So this is where I set my sights next.

First, adding a palette so the user could plant different. No big deal.

The big fish here was to figure out how to interpolate colors when Cells ran into each other. I’d need to do some kind of averaging based on RGB or HSL values, and the length of the set would differ based on how many neighbors a Cell might have in a given round.

The D3 library turned out to be helpful here, as it has a color interpolator that easily converts between various color notations.

The full process to calculate the new color of any given cell is:

  1. Grab the neighbors around a cell and filter out the ones that are not currently alive.
  2. Use to generate a flat list of colors from those live-neighbors.
  3. Use array.reduce to average all those neighbor-colors into a single RGB value.
  4. If the subject cell was previously dead, then the reduced-average is the cell’s new color. If the subject cell was already alive (and had its own color) then we give a 50% weight to that current color, where the other 50% of the average is the reduce-result from the previous step.
Cell methods for interpolating colors from neighboring cells on the board

This worked great! I added a few more features and polish at this point, giddy from the momentum that I was enjoying.

While I had momentum at this stage, I went a little wild and added a few more pieces to the app — some playback-speed options in the bottom-left, a rearrangement of the UI, a rather-large color palette generated programmatically, some help from Google Fonts, and the visual skeleton of a new File menu in the top left. Somehow I had this all done and deployed by Monday morning — I say ‘somehow’ because it seems fast to me in retrospect, but I’m pretty sure I was getting giddy at this point and probably had a lot of caffeine in my system:

Color interpolation, the Chroma in ChromaCon. Also, new color palette, file menu at top, and playback speed controls at left.

Stage 3: new features because why tf not

I had 4 days of code left at this point, and the sky seemed to be the limit. So the question was: what the heck do I do next? I had no shortage of features left from my original proposal, so …

… I allowed the user to restart the game with variable gameboard dimensions. This required a more robust and reusable init function to be available, which would clear out the existing model and view, and remake them both with custom arguments for dimensions:

Custom new-game dimensions

I built a basic windowing system because the UI was becoming crowded and I needed to free up some space. To handle this, I provisioned a global object that waited in the background for the user to click on one of these window “handles” — then it tracked mouse position and continuously updated window positioning relative to the mouse offset:

Basic windowing system

For some less-exotic code work, I added a ‘demographics’ layer to the model that updated each round, and sent that over to a new UI component (‘stats’) during continuous playback. Because the UX was also getting complicated around this time, I used Tippy.js to add tooltips to most of the interface elements on the screen. Also because everyone likes tooltips right?

Finally, I added a ‘Quicksave’ feature so the user could guess-and-check a pattern or two without having to start all over. This required inventing a basic way to capture the state of the model, store it somewhere handy, and give the model an ability to reimport that stored state without breaking everything:

Demographic stats on the right, quick-save tools on the left, and tooltips all over

This was all done by Tuesday evening, and deployed here.

Managing Complexity e.g. THIS is why tf not

By Wednesday (Day 5) there was so much going on in my app that in order to add 1 new feature I ended up breaking 3 others. Yes, I had entered the golden lands. Move slow and break things.

I spent a full day doing nothing but refactoring: improving separation of concerns, making components more modular, reorganizing and making code more readable, and code splitting to roughly double my number of JS src files. I considered using a bundler at this point, but I didn’t want to get sidetracked learning a utility when I still had more features to build :-)

My instructor’s feedback here was helpful. Essentially he said, I had wandered into the territory where frameworks(!) become useful and important. Frameworks create a rigid scaffold to which all that complexity can be pinned, and lots of things can be components with their own concepts of model, view, and controller, etc.

But it would still be worthwhile to try to manage all this complexity myself, in vanilla JS, using the application of good practices and design patterns. So that’s what I did.

That was Wednesday. Wednesday was tough. Refactoring is tough. Managing complexity makes the brain hurt. Wednesday was the toughest day of the week in this project. But it was worth it.

Complexity managed; now how to use it

By Thursday morning (Day 6), about 30 hours before presentation time, I had broken the code base into components that were much more happy to talk to each other. In theory, I could create multiple gameboards anywhere I wanted on the page now, simply by initializing a new View, Model, and Controller, centered on an HTML node which would accept a bunch of game board span elements.

This allowed me to attack a big stretch goal for the project: a collapsible palette of Game of Life patterns, where the user could observe the behavior of a given pattern before actually dropping it wholesale onto the board.

In other words — a sidebar that included a whole series of MVC assemblies. Now I would have a half-dozen new Models and Views on the screen, and they would need a simplified type of Controller. So I created a MiniController class to handle these mini-gameboards. There were no playback controls here, since a simple mouseover on the View should trigger propagation. These also had to accept a predefined model state to initialize each of these various (known) patterns.

In order to drop these patterns onto the board, I created a new floating window div (the patternDropperFloater) and worked up an ad-hoc method to transfer one of these patterns from the palette over to the dropper. Then I spent about 2 hours figuring out how to do the actual drop. This involved finding the x/y intercept on the gameboard in the background, then looping over the cells in the dropper and adding them sequentially to the main board. It was a little hackish and complicated, but hey, I had 24 hours to go.

In any case, the results were gratifying:

Home Stretch: performance, splash screen, etc

With about 12 hrs left in the hackathon, I spent the evening of Day 6, and the morning of Day 7, making some optimizations and stylistic tweaks:

  1. … Reducing lag using Chrome’s Performance profile recorder. It turns out that creating a large gameboard would lock up the browser for several seconds because FontAwesome’s JS would querySelectorAll for new nodes to style every time a new View cell was added to the DOM. That’s bad! Switching to the CSS option solved that. No more lock-up.
  2. … Adding a sexy splash screen with a full-coverage video background. Because I’m led to believe this is a cool thing(?). At very least, impress the laypeople.
  3. … Adding keyboard shortcuts on most input elements. Because maybe somebody (like me) will enjoy actually using this at some point.
  4. … Adding subtle toast notifications for most input actions. Because toasts are great.
  5. … Fixing sizing and layout, cleaning up CSS. Because there are a lot of things on the screen, and it would be nice if this works on a laptop or a desktop. [No chance of it working on mobile any time soon 😅]
  6. … Naming the project finallyChromaCon, because ‘chroma’ means ‘color’ and ‘con‘ is short for Conway(‘s Game of Life, duh). And because Conway’s Game of Life is just too dang mouthy.
  7. … Adding a secret “Tim mode” where instead of colors, turtle emoji will propagate across the gameboard. Because Tim is my classmate and I liked his idea for turtle-propagation. So I added a double-click eventListener on the most poop-colored color in the palette — which you can see here if you watch closely:
Tim Mode, in honor of my classmate Tim

Summing up

Making ChromaCon was stressful, thrilling, addictive, and a freaking blast. I was entirely pleased, and surprised, at how much I was able to generate in a week’s-worth of focused effort. Giving my final presentation to my classmates and the Galvanize campus was rewarding and encouraging.

Writing this a week after the presentation, if I could start over from scratch and rebuild the app:

  1. I think I could probably get it done in a couple of days, not a week.
  2. On a tight timeline I probably would not spend so much time on TDD.
  3. I’d build most everything as a fleshed-out component from the start, rather than adding modularity and control features later.
  4. I’d take pains to more carefully segment my code, keep the global namespace cleaner, and probably use a build tool like Browserify or Webpack to generate a single, bundled, minified, fast file for the browser to run.

Meanwhile, thanks a ton for reading. Again, the final app can be found here, or at least “final” as of my presentation at the end of Unit 1.

[Update: One month later]

It’s mid-April now and I’ve learned plenty more about app design, architecture, and so on. We spent q2 learning Node, Express, SQL, and building our first full-stack apps. And now we have a week off, so ….

… I’m going to come back and update this project.

At the time that I built it, I was only just becoming aware of the Observer design pattern. Now I want to go back and implement this in my Model and View — effectively making the redraw of the gameboard event-based instead of simply doing dirty-checking every round.

My hunch is that this should substantially speed up the frame rate on large-size games with lots of Cells to propagate. According to the Chrome Performance profile I captured in March, a significant chunk of each frame just goes to redrawing the board, since every Cell is getting checked and redrawn each round.

That Performance Panel in Chrome should make it easy to compare before and after. I’ll post a link here when the work is done.

At the time of this writing, Ian is learning full-stack web development at Galvanize-Austin. His progress and portfolio can be found at

Ian Schumann

Written by |

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade