Monday, December 27, 2021

Making a turn-based game: A game server on DreamHost

The next step in setting up our turn-based game is prepping up our server to do double-duty: it needs to both vend static HTML content and handle dynamic client-server traffic to run the game logic. To that end, we need to extend the initial configuration to support both.

Koa server

Boardgame.io integrates with the koa framework to serve HTML. It's a relatively straightforward web server that supports a static server (koa-static); the server is even instrumented with the npm debug library and can give debug logging output if the DEBUG=koa-static env var is set.

Armed with this knowledge, I can improve the server.js file to vend static content.

const frontendJsPath = path.resolve(__dirname, '..');
const frontendPublicPath = path.resolve(__dirname, '../../public');
server.app.use(async (ctx, next) => {
    console.log(`requesting ${ctx.request.path}`);
    await next();
});
server.app.use(serve((frontendJsPath)));
server.app.use(serve((frontendPublicPath)));

Now we're making progress, but the client code doesn't work because the browser can't import npm modules. It's time to bundle up the client-side code into something a regular JS browser can handle.

Webpack

Being already familiar with webpack, I chose to grab that as my weapon of choice for generating browser-compatible code. First step is to get it into the project:

npm install --save-dev webpack
npm install --save-dev ts-loader

I set up the webpack config based on the guide and changed my build rule to tsc && webpack. My output is now public/bundle.js instead of build.

At this point, I realized I'd need two different configs for client and server code. I can do this with two tsconfig.json files and the tsc -p build option to select which one to build. With a little reorganizing of my code (client-specific in "client", server-specific in "server", and common in "game"), I can now generate two build artifacts: a server that can run in Node, and a client that the server vends to a browser that connects to it.

After uploading all that content (the build artifacts and the node_modules directory) and giving it a run, I had a successful instance of the game running from my server.

The Swatch user interface, showing a color to name


Now to set up a lobby.

Supporting a lobby

Boardgame.io provides a rudimentary lobby server that makes it easy to create and connect to games (just replace the top-level game object in your code with a Lobby client). Not quite documented is that lobby also assumes that HTML components that are class hidden will be hidden from user view (display: none css rule works fine). At this point, I also discovered that the default config rules for caching on my Dreamhost server didn't work well; every piece of GET content was served with a Cache-Control: max-age=172800. I added some cache-breaking to the .htaccess file:

Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0

Hiding colors from end-users

One last bit of housekeeping at this stage: the color database is a JSON object that gets read into memory, but it should live only in the server memory; clients should not get a whole copy of it. To that end, I stored the colors in private/colors.json and used Node to fetch them (require('fs'), require('path'), dynamically load the file at boot time). I also had to get those files out of webpack's field of view so it didn't try (and fail) to bind fs and path into browser code. The following got added to webpack.config.js:

resolve: {
  fallback: {
    'fs': false,
    'path': false,
  }
}

So in the common code, I can check if fs.readFileSync is defined, and if it is not I know I'm in the browser and can avoid trying to load colors.

Give it a try!

The game is up and available for 2 or more players at http://swatch.fixermark.com; feel free to try it and let me know what you think!

Next post, I'll talk a bit about re-architecting the game to support different kinds of rounds.

Monday, December 13, 2021

A Whirligig for a play

Taking a short break from talking about Boardgame.io for a project I took on this weekend.

My wife is in a play this holiday season, and one of the plot points is a kid struggling with a science fair project getting a little help from the main character. I went ahead and did some prop work for the before-and-after on the project, and I'm pretty happy with how it turned out.

Preparation

Amanda sourced several STEM kits from Sntieecr for raw materials so we could make the prop dynamic. They were pretty good, but they don't include any structural elements. I had a few yard stakes in the basement that I figured had enough bulk to serve. When it comes to woodworking, I'm absolutely amateur; I don't even have a jigsaw or a workbench. But I was able to jury-rig a bench with random stuff in the basement, and with some grunting with a saw I was able to segment a few yard stakes into 8" and 10" lengths.
A jury-rigged workbench: two folding chairs supporting a single shelf from an old Borders bookshelf. On the shelf, my drill kit, saw, the wood, and some clamps.
Literal basement-tier work

I figured I could make a sufficiently-sturdy shape with a 3-4-5 right triangle and a cross span to keep the whole thing from tipping over. Yes, my understanding of mechanical stability pretty much tops out at this.

Against the backdrop of a stack of three cut lawn spikes, "The plan": a rough sketch of a windmill.


The work

One thing I was concerned about was that without a jigsaw, cutting an angle into the top of the diagonal was going to be a real chore. Then I remembered that since I was using yard stakes, the original manufacturer had tapered the end for me. Thanks, industrial manufacturing!

Two trusses, one including a diagonal brace, the second with the diagonal brace pulled away.



With a "before" and "after" truss finished, I was ready to put the STEM kit on them.

On a large table, the two trusses and an open bag of small parts: motors, switches, plastic fan blades that fit to the motors, battery cases, and a large assortment of wires.


Here I hit a small snag: after affixing one of the light bulb sockets, it came apart when I screwed the light bulb in! It turns out the whole affair (socket threading, anode terminal, cathode terminal, and a paper insulator between) was held together with a single screw, and it had shipped loose; screwing the lightbulb in unscrewed te screw and dropped all the parts out of the socket. Once I figured out what had gone wrong, it was pretty straightforward to put together.

The kit worked great once assembled, and I was excited to see it in action.


The last step was to bond the gadgets to a base board for easy transport, I was worried about a screw sunk into the stakes on the thin side splitting the stakes. So I used hot glue. A lot of hot glue.

Close-up on the joint showing hot glue
There are problems hot glue cannot solve.
Someday I'll meet one.

The result

Overall, I think they turned out okay! I hope they're robust enough for handling during the show, but I think they'll be fun!

The "before" whirligig, with several small parts laying around it indicating it's half-done.


The "after" whirligig, notably Christmas-themed.

Monday, December 6, 2021

Making a turn-based game: Getting started with boardgame.io

For my game, I went looking for a framework that would handle some of the boring parts and free me up to focus on the game mechanics and logic. It didn't actually take much hunting to stumble upon boardgame.io, which I was surprised to discover handled just about everything I wanted.

In this post, I'll talk a bit about my choice of engine and the steps I took to get started.

Architecture

The boardgame.io framework has several features I appreciate:

Language and libraries

  • Designed to run on NodeJS, which I'm already familiar with
  • Includes React components
  • Includes TypeScript type bindings

Features

  • State synchronization between server and clients
  • Client-side prediction of next move on public state, to speed up the experience
  • Automated client information hiding
  • Game state machine library
  • Debugging UI
  • Integration with a couple of backing stores
  • Rudimentary multi-game server and client lobby

The framework was designed with Heroku in mind, but I was able to get it working on a shared DreamHost server. For the remainder of this post, I'll go into that process (which involved a lot of dead-ends, but eventually worked!).

Getting started with a boardgame.io project

For this project, I set up a pretty standard Node project:

  • installed nvm using the wget directions
  • nvm install 12.22.7 to sync with version required by DreamHost for compatibility with Passenger Phusion
  • configured the project to use TypeScript
  • Started setting up the boardgame.io tic-tac-toe tutorial

By the end of this stage, I had the following files set up:

tsconfig.json ``` { "compilerOptions": { /* Language and Environment / "target": "es6", / Set the JavaScript language version for emitted JavaScript and include compatible library declarations. / "lib": ["es2015"], / Specify a set of bundled library declaration files that describe the target runtime environment. */

/* Modules */
"module": "commonjs",                                /* Specify what module code is generated. */
"rootDir": "src",                                    /* Specify the root folder within your source files. */
"moduleResolution": "node",                          /* Specify how TypeScript looks up a file from a given module specifier. */

/* Emit */
"outDir": "build",                                   /* Specify an output folder for all emitted files. */

/* Interop Constraints */
"esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
"forceConsistentCasingInFileNames": false,           /* Ensure that casing is correct in imports. */

/* Type Checking */
"strict": true,                                      /* Enable all strict type-checking options. */

/* Completeness */
 "skipLibCheck": true                                /* Skip type checking all .d.ts files. */

} } ```

tslint.json


{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {
        "no-console": false
    },
    "rulesDirectory": []
}

One note about the boardgame.io tutorials: they'll mention using parcel-bundler, but it's deprecated for parcel.

Next on the list was to set up React.

  • npm install react (which gave me v17.0.2)
  • npm i --save-dev @types/react
  • add "jsx": "react" to compilerOptions in tsconfig.json
  • npm install react-dom
  • add dom to lib in compilerOptions
  • add "include": ["./src/*"] as a peer to compilerOptions (note: src/* is not the same and will result in failure to find modules)

This gets us halfway to a working solution, but to serve from DreamHost, we'll need to have a JavaScript program that Node.JS can run and a program (likely one JS file) that can be vended to the client. Configuring that is the next step.