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
  • Boardgame.io 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 boardgame.io 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;

server.run(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 https://swatchgame-dev.herokuapp.com/ 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 https://swatchgame.herokuapp.com/... Try it out!

3 comments:

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

    ReplyDelete
  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)
    ```

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

      Refreshing the page *usually* fixes it.

      Delete