Monday, January 10, 2022

Making a turn-based game: Heroku is unfairly good, yo

The Heroku logo

So over the course of several weeks (and several blog posts), I've created a multiplayer turn-based color-guessing game hosted on DreamHost. I've been pretty happy with it, but there's still a couple annoying quirks in the situation:

  • The server is HTTP only
  • supports websockets, but attempting to use them fails

Both of these are related to my host: I have an old shared-hosting solution on DreamHost running Passenger Phusion, which makes it slightly trickier to support these features. It doesn't appear to e impossible: there is, for example, some discussion and a code example for setting up both SSL and websockets on DreamHost. But I think there's a simpler approach that I wanted to try first.

Friends, Heroku is unfairly good.

Setting up Sw─âtch on Heroku

Heroku is a "Cloud Platform as a Service" (PaaS) that lets you very quickly host an app using one of several formats and frameworks, including NodeJS. Once configured, deploying changes is as easy as git push to a relevant target.

The documentation includes some pretty clear directions for setting up a Heroku instance to host a game... If you already know how Heroku works. This was my first Heroku app, so I took a few more steps to get there.

For starters, I set up two new apps on Heroku (swatchgame-dev and swatchgame) and arranged them in a pipeline so that I can get one-click promotion of staging candidates at swatchgame-dev to production at swatchgame. "Every project has a development environment... Some are privileged enough to have a production environment separate from the development environment." I then installed the Heroku CLI tools on my work machine (sudo snap install --classic heroku). That gave me the tooling to configure my git repo to work with Heroku (heroku git:remote -a swatchgame-dev, which set up a remote that would push to my Heroku app).

To prepare my app to work on Heroku, I only needed to do a couple of things:

  1. In package.json, clean up my build rule so it built both the client and the server code:
    "build": "npm run build-server && npm run build-client"
  2. Add a declaration to package.json to let Heroku know what Node version is expected:
    "engines": { "node": "12.22.7" }
  3. Add a Procfile to the root of my project that told Heroku what to do to launch the project once it was installed. The file is a single line, web: node build/app.js, which tells Heroku to set up handling web connections by running the app.
  4. Make the app read from a PORT environment variable to check what port to bind to. Heroku sets that variable to let Node.js apps know what port to listen on for incoming connections. This required only a few tweaks to the setup logic in the server's app.ts file

The necessary code change was very simple:

import 'process';
. . .
const MAYBE_PORT = Number(process.env.PORT);

const PORT = isNaN(MAYBE_PORT) ? 8000 : MAYBE_PORT;, () => {
    console.log('server on');

Once all that was done, I simply had to push to Heroku. Heroku is watching for changes to only a couple of branches (master or main), so I just had to make sure my push matched the correct branch name (since I develop in a local develop branch):

git push heroku develop:main

I then visited and... It just worked! No further changes needed. Both HTTPS access and websockets are handled via Heroku's default configurations.

Seriously, Heroku made this ridiculously easy, and it's free to use for a project this small. I couldn't recommend anything else at this juncture.

The game is now playable at Try it out!


  1. Look into `heroku labs:enable` too, and any language-specific enablements for runtime metrics.

  2. Created match, tried to join it, got:

    swatch OPEN [free], [free] Join
    failed to join match sLrQIH-ytFy (Error: game instance sLrQIH-ytFy not found)

    1. That'll happen from time-to-time; it appears to be a bug in that I haven't tracked down yet.

      Refreshing the page *usually* fixes it.