Multiplayer JavaScript game dev log #2

There’s some progress! Since I first started coding this thing it went through a lot of changes:

I ditched Redux and redux-like states on server-side. That was probably already mentioned in the last post. Generally I need to focus on mutations, not replacing the whole state with every tiny change (immutability).

I ditched React for client-side rendering. I wanted to have some nice animations which would move the cards from one place to another. That worked quite well until I then needed to position everything around the circle + around each player in a game screen, where you don’t know the exact number of players upfront.

Added pixi.js for the rendering. I’m somewhat familiar to this rendering as I played around with Phaser game engine for a tiny bit.

But let me take a few steps back and remind myself about the last time I wrote anything on this blog.

… so.

Structure, and how to send it to browser

On server most of the objects have a reference to its parent element. This will help me decide ownership of each object and position in the game. Some cards may be held in hand, others may stay in deck of cards, and each of those containers may belong to a Player. The root object in my structure is still some imaginary “root”, because some object may just be lying on the table. Neutral and not belonging to anyone yet.

The problem appeared the first time I tried to send that data to the client. Server shuts down with stack overflow error.

My debugging process of the server app wasn’t perfect. It was non-existent. After trying and failing to debug this issue with a bunch of console.logs, I tried to hook Visual Studio Code to my app as the debugger. Finally I got it working, I could enter the code in runtime, set some breakpoints, go through the code step-by-step to discover this: Colyseus translates state object to JSON before sending it to client. And every time it hits my parent parameter, it goes deeper and deeper, recursively following that looped chain of object references…

That’s where I decided to store parent’s id instead of a reference. So now every newly created game object is assigned with brand new ID and is stored in a private Map in the Base.js class. I can even call Base.get("someID") to get a reference of any object.

const uuid = require('uuid/v4')

const objects = new Map()

module.exports = class Base {
  constructor(options = {}) {
    this.id = uuid()

    // Store a reference to itself by ID
    objects.set(this.id, this)
    // ...
  }

  // ...

  /**
   * Get a reference to the object by its ID
   * 
   * @static
   * @param {string} id 
   * @returns {any}
   */
  static get(id) {
    return objects.get(id)
  }
}

With that out of the way, now I’m able to send and receive ANY state change on the client.

War game layout

Having parent/child tree helps in organising everything. Especially when it comes down to rendering each object on the screen. I wanted my “renderer” to be screen-size-independent, so for now:

  • Table component will fill 100% of the screen in width,
  • Players will be positioned in the circle, in case of 2-player game, one in front of  the other,
  • every object belonging to that player gets nested inside it’s main Container as a “child element”.

This way, I can decide general position of each container right on the server.

  • player’s Deck of cards are ~5cm to their right
  • the Piles, where players throw their cards are in front of them
  • and player’s Hand container is right on spot of player component (0, 0)

Unit tests for the rescue (seriously)

Don’t underestimate the power of unit tests! I’ve just started to write them right now and I’ve already found 3 bugs that could have occurred in the future. For now I’ve found 3 bugs:

  • when new game object was created with parent in constructor, it would call addChild(myself)on its new parent, which in effect would immediately call removeChild(thatNewObject)…  Now all objects and parent are properly notified of new child elements, and their elements arrays and parent properties are correctly managed.
  • utility function getAllParents(everything, me.id) wouldn’t correctly return full array with all parents of deeply nested elements.
  • utility function exists was incorrect, mind the apostrophes:
const exists = (value) => typeof value !== undefined
//                                        ^ whoops  ^

I’m using AVA (futuristic test runner) right now for its speed and concurrency – it runs multiple tests in parallel. I’m using Mocha in my daily job and I’m always up for testing new things, so that’s why AVA stays.

The next step would be to make test coverage report, but for now I don’t have much unit tests written and I’m focusing on making this game work first. Coverage report lands in my backlog though.

THXBYE

That’s it for now! I’ll try to keep those posts smaller and actually hit that “Publish” button every once in a while. This project to create “cards game archive” multiplayer games bundle still lives. It’s still not complete, but I still have a lot to share.