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 organizing 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,Player
s 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
Pile
s, 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 calladdChild(myself)
on its new parent, which in effect would immediately callremoveChild(thatNewObject)
… Now all objects and parent are properly notified of new child elements, and theirelements
arrays andparent
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.