It is October 2020 and I’m social distancing and missing my friends. At the start of lockdown I played a lot of online multiplayer party games with friends to try and replace our regular board game hangouts. After consuming so many, I decided it was high time that I try and make a multiplayer game myself. I happen to have a robust, globally distributed pub/sub real-time messaging infrastructure right at my fingertips in the form of Ably, perfect for creating a system that can send data between multiple players.
What is pub/sub and how can I use it to build a game?
Pub/sub is short for publish/subscribe and it describes a pattern of real-time message exchange between “publishers” and “subscribers”. Publishers create and send messages to a “channel”. Subscribers are then able to receive messages from the channel by subscribing to it. There can be multiple publishers and multiple subscribers, all of which are decoupled – the publishers do not have knowledge of how many subscribers there are. This is great for me, being the “frontendest of frontenders” (what one friend calls me), because I thus have a way to easily get data between players without having to implement this myself. I can use the pub/sub pattern to build a peer-to-peer game, and I don’t have to set up a central server.
What is Peer-to-Peer and why is it helpful here?
Peer-to-peer (p2p) refers to a network of computers (and/or devices) that share and exchange workloads. These computers, or peers, send messages to each other directly, without an intermediary. The messages are what controls the game state. This differs from client-server architecture where one central machine controls the whole game.
- No need for a central server, making setup easier and cheaper
- Can easily handle a game with up to 10 players
- Easy way to share data between players
- Server error can’t take down the entire game
- Need to manage the loss of a peer (due to a network outage, rage quit etc.) or this could break the game flow
- Possibility for users to cheat by altering the data they’re sending
In order to make state management of the game simpler, I’m going to make one of the peers elect themselves the “host” of the game. The host will initialize a game, invite peers to join, and then coordinate the communication between them.
What can I make with pub/sub and p2p?
Simple multiplayer games like Pictionary, Codenames, Taboo, Cards Against Humanity, and Pass the Pen are all perfect for this architecture — they all have simple, clearly defined rules and steps which players follow to change the state of the game.
In my game, all players get placed in a circle. Each player gets a prompt, and each draws an illustration based on that prompt. The picture that they draw is then passed on to the subsequent player in the circle, and now each player guesses what the picture represents, writes it down and passes their written text guess on. Each player’s guess becomes each subsequent player’s next prompt, and so on.
I called the game Depict-It. It was based on a game called Eat Poop You Cat.
Defining the rules
Defining the rules and steps of Depict-It makes it easier to build out the gameplay in code, so let’s do that here:
- A host creates a game and receives a share link to invite others.
- Other players join the game.
- Once all players are in, the host starts the game.
- Players are each sent a “prompt” card with a phrase on it.
- Players draw their prompt on an HTML canvas – they are given 180 seconds to do this.
- Players’ drawings are passed along to the person who joined after them, except for the last player, whose drawing is passed to the host.
- Players then have to enter a “caption” for that picture into a textbox – they are given 180 seconds to do so.
- Steps 6 and 7 repeat until each player has received the “stack” of drawings from their original prompt.
- The stacks of drawings and captions are shown to each player to vote on which submission was their favourite.
- Votes are counted and shown to players.
- Host is offered a prompt to start a new game.
Defining the state from the rules
From the list of rules, it becomes clearer what state the game might need:
There is an initial state which counts the number of players and keeps track of who the host is. The game then splits into the following phases (each with an input and a response): dealing, drawing, captioning, and voting.
The State Machine software design pattern can be used to model a system that can exist in one of several known states, and this is what I use here. When a player or the game makes a change or provides an input, the state machine does logical checks on what to do next. The game is built on a collection of “handlers” (just blocks of code), between which the state machine switches to execute the entire game.
When players join the game they are connected to an Ably channel, on which they can send and receive messages. As the game phases are moved through, and the state updates, these messages keep all of the players’ browsers in sync and their games up to date with each other. Below is a table of the game phases and the associated sent messages:
|Phase description||phase name||message 'kind'|
|Dealing and setup||No messages|
|Collecting image input||
|Collecting image input response||
|Collecting caption input||
|Collecting caption input response||
|Collecting scores from players input||
|Collecting scores from players response||
Each phase of the game will have a “completion condition”, which will be used to make decisions. For example, “When all of the players have finished drawing…” or “When all of the players have voted for their favourite submission…”.
Sent messages have both a “kind” and a “value”. The “kind” lets our code know to process the message differently from the initial connection messages. The “value” is the payload that the player needs in order for the game to continue, be it a drawing (in the form of a URL) or a caption (a string of text).
What makes peer-to-peer and pub/sub so useful is that we can use the concept of promises to pause game execution while waiting on the completion conditions. For example, while players are drawing, the game issues checks like the following: “Have I had a number of drawings submitted that equals the total number of players?” Until that statement returns true, the game will not continue. Every time a player submits a drawing, a message is sent on the Ably channel which increments the number of submitted drawings. Once the statement has returned true, the next phase of the game can be executed.
You can read more about how the state machine, handlers, and messages work in the README of the project and see the code in the repo:
Writing the game’s user interface with Vue.js
The game UI is written using Vue.js. This framework allows me to split the different game phases into separate Vue Components. These components can have Vue Directives which tell the library what to do with them, for example: whether or not to render the component, or to bind data to the component.
With Vue I can easily create UI elements which are shown only to the host by using the Vue.js syntax:
v-if:isHost. This checks the state for whether the player is the host and evaluates which UI elements to display. Building up the components in this way enables me to check each game phase in the state and show the correct UI elements accordingly.
Vue.js also has a methods property, which can be used to handle events, such as implementing click handlers. The syntax looks like this:
v-on:click=function(). This allowed me to bind functions to events that components can raise as the game phases progress.
This “componentization” makes the markup of the game nice and neat, since it allows me to split each component into a separate file and use the browser’s native import module syntax to import them when needed.
You can read more about how the game UI works in the project’s README.
Extending the game
There are still many improvements that could be made to the game and extensions on how the gameplay could work. For example, let the players suggest captions at the start of the game, or give them an interface to print or save the progressions to their machine for future laughs. You could also limit the number of players in a game and split the overflow players into a separate game so that round doesn’t get too long. The UI could also do with a better explanation for players new to the game. There are lots of ways the game could be improved, and if you’d like to add anything, or make a whole new game I’d love to see it. You can make a PR on the repo or fork it and make it your own. I am also definitely open to funny prompt suggestions.
Sharing the laughs
f you play a game of Depict-It you can share your card progressions to Twitter, and follow other’s silly progressions on the Depict-It Twitter feed:
I hope you’ll enjoy playing the game as much as I enjoyed building it. And that the write up and project will inspire you to make your own multiplayer game.
I can’t wait to see all your silly drawings.
Play the game: https://depictit.ably.dev/
See the code: https://github.com/ably/depict-it
But that's not all! Make sure to check out the Ouijably, a digital, online “spirit board” also built with the peer to peer framework.