Faster Multiplayer with Jiber

A Starting Point

Platform Racing 2 is a game I wrote in 2007, and it is still alive and kicking today thanks to a dedicated and generous community of players. It's a multiplayer platformer with many great features, such as shooting your friends in the face with a laser gun. Here's how Platform Racing 2 works that out: (it runs on a multiplayer server called Blossom Server)

output_HFhw4x

That's four steps. If we estimate that each step takes 100ms, then we're looking at waiting a whopping 400ms just to find out if we shot shomeone in the face or not. Surely we can do better!

Getting Faster

Jiber is a modern multiplayer server for web games. It can run a copy of the game on the server, which speeds up some events considerably. Here's how Jiber works out the process of shooting your friend in the face:

Webp.net-gifmaker

That's two steps. If we stick with each step taking 100ms, then we'll be waiting 200ms to find out if we shot our friend in the face or not. Not bad... but can we do even better?

Faster Still

That's right, Jiber is fast as crap! It sets up peer to peer connections between players, resulting in a face shooting process like this:

Webp.net-gifmaker--1-

That's one step! Once Sue knows that Bob was at rest, she can deduce that she definitely shot Bob squarely in the face. We're looking at a 400% speed increase compared to Platform Racing 2.

Ok, security

Jiber's peer to peer

Hosting Ghost With Docker

Ghost

Ghost is open source blogging sofware that kind-of sort-of competes with WordPress, which is open source world domination software.

Let someone else host it

It is absolutely worth the extra bit of money to have someone else go through the trouble of hosting Ghost for you. For those of you who are smart (unlike myself), here are a couple of great options.

  • ghost.org
    Hosting by the people who wrote Ghost. Cool! Their cheapest plan is $29 a month, or $19 a month if you pay yearly.
  • runkite.com
    Hosting by some people who like kites. Cool! Their cheapest plan is $5 a month. As of right now they are running an old version of Ghost. This is a great cheap option if you don't share my phobia of outdated software.

Host it yourself

Ghost's installation instructions aren't too bad if you're used to doing this sort of thing. But it's not exactly the easist thing ever.

Instead, a simpler solution is to install docker, and then run the ghost image.

docker run --name ghost -d \
-p 80:3268 \
-v /data/ghost:/var/lib/ghost/content \
ghost:alpine

Done!

Add SSL

If you want to go the extra mile, here's a docker setup that adds automatic ssl via Let's Encrypt.

docker run --name nginx -d \
-p 80:80 \
-p 443:443 \
-e ENABLE_IPV6=true \
-e DEFAULT_HOST=yourdomain.com \
-v /data/certs:/etc/nginx/certs:ro \
-v /etc/nginx/vhost.d \
-v /usr/share/nginx/html \
-v /var/

Keeping Multi-User Apps In Sync (simply)

Here is a simple method to keep multiple users in sync with a shared state (in real time). The core concepts here are flexible enough to work anything from a collaborative document editor to a real-time multiplayer game.

To help keep things simple, we'll build a super fun game called "Push the Button First!". This game will be played by Bob and Sue, and BOY do they both want to be the first person to push that button.

State?

State is the data that you want to share between two people. For our "Push the Button" game, our state will be very simple.

// state
const state = {  
  personWhoPushedFirst: null
}

Changing the State

The state of our game will change when Bob or Sue click the button. To keep things simple, we will impose a rule: State can only be changed by emitting actions. We will have a list of mutations that take emitted actions, and use them to change the state.

// state
cosnt state = {  
  personWhoPushedFirst: null
}

// all possible ways to change the state go here
const mutations = {  
  clickTheButton (action) {
    if (!state.personWhoPushedFirst) {
      state.personWhoPushedFirst = action.person
    }
  }
}

// action emitter
function emit(action) {  
  const type = action.type
  const mutation = mutations[type]
  mutation(action)
}

Push That Button!

Let's say Bob and Sue both want to win, so they both push the button as fast as they can. On Bob's computer, this action will be emitted:

emit({  
  type: 'clickTheButton',
  person: 'Bob'
})
// now state.personWhoPushedFirst equals 'Bob' on Bob's computer

...However, on