{"id":20058838,"url":"https://github.com/ably-labs/depict-it","last_synced_at":"2025-05-05T15:30:26.516Z","repository":{"id":37172635,"uuid":"289242435","full_name":"ably-labs/depict-it","owner":"ably-labs","description":"a hilarious peer to peer drawing game built with vue.js using Ably channels.","archived":false,"fork":false,"pushed_at":"2023-07-18T23:20:27.000Z","size":2639,"stargazers_count":76,"open_issues_count":13,"forks_count":21,"subscribers_count":17,"default_branch":"main","last_synced_at":"2024-05-05T13:32:47.812Z","etag":null,"topics":["ably-js","demo","game","game-development","javascript","peer-to-peer","pubsub","realtime-messaging","vue"],"latest_commit_sha":null,"homepage":"https://depictit.ably.dev/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ably-labs.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-08-21T10:29:37.000Z","updated_at":"2024-03-18T03:33:58.000Z","dependencies_parsed_at":"2024-01-05T23:01:55.276Z","dependency_job_id":null,"html_url":"https://github.com/ably-labs/depict-it","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably-labs%2Fdepict-it","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably-labs%2Fdepict-it/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably-labs%2Fdepict-it/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ably-labs%2Fdepict-it/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ably-labs","download_url":"https://codeload.github.com/ably-labs/depict-it/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224452840,"owners_count":17313668,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ably-js","demo","game","game-development","javascript","peer-to-peer","pubsub","realtime-messaging","vue"],"created_at":"2024-11-13T13:04:32.983Z","updated_at":"2024-11-13T13:04:33.634Z","avatar_url":"https://github.com/ably-labs.png","language":"JavaScript","funding_links":[],"categories":["Examples [🔝](#readme)","Resources"],"sub_categories":["Examples"],"readme":"# Depict-It\n\nDepict-It is a party game for 4 to 8 players (ideally!) where you mutate a phrase through drawings and captions, to make up funny scenarios with your friends.\n\nYou can play Depict-It online at this url: [https://depictit.ably.dev](https://depictit.ably.dev)\n\n## The rules of the game\n\n* The game is played in rounds.\n* Each player is provided with a `Game Stack` containing a `Caption` and a blank screen for them to draw on.\n* They have 180 seconds to draw a picture of what is described in the caption.\n* Once either *all players* have finished, or 180 seconds elapse, each drawing is passed to the next player.\n* Now each player writes a caption which describes the drawing presented to them.\n* Once the first player has their own `Game Stack` returned to them the `Scoring phase` begins.\n* During scoring, each progression from starting caption through drawings and descriptions is displayed. The players can vote on the funniest card in the progression.\n* Points are awarded to each player based on the number of votes they've received and the `Host` can start a new round.\n\n## About this document\n\nIf you're just interested in running this project on your own machine, or on Azure, scroll to the bottom of this document for instructions.\nThe rest of this readme is a teardown, and an explaination of how the game is made.\n\n# Contents\n\n- [What are we going to build?](#what-are-we-going-to-build-)\n- [Dependencies](#dependencies)\n  * [A brief introduction to Vue.js](#a-brief-introduction-to-vuejs)\n  * [Ably Channels for pub/sub](#ably-channels-for-pub-sub)\n  * [Ably channels and API keys](#ably-channels-and-api-keys)\n  * [Making sure to send consistent messages by wrapping the Ably client](#making-sure-to-send-consistent-messages-by-wrapping-the-ably-client)\n  * [Building a web app](#building-a-web-app)\n  * [HandleMessageFromAbly](#handlemessagefromably)\n  * [P2PClient](#p2pclient)\n  * [P2PServer](#p2pserver)\n- [Designing the game](#designing-the-game)\n  * [The GameStateMachine](#the-gamestatemachine)\n    + [Defining a game](#defining-a-game)\n    + [Defining a handler](#defining-a-handler)\n  * [How the GameStateMachine works](#how-the-gamestatemachine-works)\n    + [What is the GameStateMachine doing?](#what-is-the-gamestatemachine-doing-)\n  * [The GameStateMachine and our game](#the-gamestatemachine-and-our-game)\n    + [StartHandler](#starthandler)\n    + [DealHandler](#dealhandler)\n    + [GetUserDrawingHandler](#getuserdrawinghandler)\n    + [GetUserCaptionHandler](#getusercaptionhandler)\n    + [PassStacksAroundHandler](#passstacksaroundhandler)\n    + [GetUserScoresHandler](#getuserscoreshandler)\n    + [EndHandler](#endhandler)\n  * [Handlers and async / await](#handlers-and-async---await)\n- [The game UI](#the-game-ui)\n  * [Building the UI with Vue](#building-the-ui-with-vue)\n  * [Inside P2PServer](#inside-p2pserver)\n  * [Inside P2PClient](#inside-p2pclient)\n  * [Splitting our game phases into Vue components](#splitting-our-game-phases-into-vue-components)\n  * [The DepictIt Client](#the-depictit-client)\n  * [Storing images into Azure Blob Storage via an Azure Function](#storing-images-into-azure-blob-storage-via-an-azure-function)\n  * [Drawing using HTML5 Canvas](#drawing-using-html5-canvas)\n    + [How does the drawing canvas work?](#how-does-the-drawing-canvas-work-)\n    + [Touch support](#touch-support)\n- [Recap](#recap)\n- [Running on your machine](#running-on-your-machine)\n  * [Local dev pre-requirements](#local-dev-pre-requirements)\n  * [How to run for local dev](#how-to-run-for-local-dev)\n- [Hosting on Azure](#hosting-on-azure)\n- [Helpful Resources](#helpful-resources)\n\n\n# What are we going to build?\n\n`Depict-It` is [a progressive web app](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps). It is built with JavaScript, [Vue.js](https://vuejs.org/), HTML and CSS.\n\nThe game uses the [Ably Basic Peer to Peer demo](https://github.com/thisisjofrank/p2p-demo-ably) as a base and [Ably Channels](https://www.ably.io/channels) to send messages between players.\n\nWe'll be hosting the application on [Azure Static Web Applications](https://azure.microsoft.com/en-gb/pricing/details/app-service/static/) and we'll use [Azure Blob Storage](https://azure.microsoft.com/en-gb/services/storage/blobs/) to store user generated content.\n\n# Dependencies\n\nThe app uses Vue.js and Ably.\n\n## A brief introduction to Vue.js\n\n\u003e Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. It is designed from the ground up to be incrementally adoptable, and can easily scale between a library and a framework depending on different use cases. It consists of an approachable core library that focuses on the view layer only, and an ecosystem of supporting libraries that helps you tackle complexity in large Single-Page Applications.\n\u003e \u003ccite\u003e- [vue.js Github repo](https://github.com/vuejs/vue)\u003c/cite\u003e\n\n[Vue.js](https://vuejs.org/) is a single-page app framework, and we will use it to build the UI of the app. The Vue code lives in [index.js](index.js) and handles all of the user interactions. We're using Vue because it doesn't require a toolchain and it provides simple binding syntax for updating the UI when data changes.\n\nA Vue app looks a little like this abridged sample:\n\n```js\nvar app = new Vue({\n  el: '#app',\n  data: {\n    greeting: \"hello world\",\n    displayGreeting: true,\n  }\n  methods: {\n    doSomething: async function(evt) { ... }\n  }\n});\n```\n\nIt finds an element with the id of `app` in the markup, and treats any elements within it as markup that can contain `Vue Directives` - extra attributes to bind data and manipulate the HTML based on the application's state.\n\nTypically, the Vue app makes the properties of the data object available to bind into your markup (such as `greeting` in the above code snippet). When data changes, it'll re-render the parts of the UI that are bound to it.\nVue.js exposes a `methods` property, which can be used to implement things like click handlers and callbacks from the UI, like the `doSomething` function above.\n\nThis snippet of HTML should help illustrate how Vue if-statements and directives work:\n\n```html\n\u003cmain id=\"app\"\u003e\n    \u003cdiv v-if=\"displayGreeting\" v-on:click=\"doSomething\"\u003e\n        {{ greeting }}\n    \u003c/div\u003e\n\u003c/main\u003e\n```\n\nHere you'll see Vue's `v-if` directive, which means that the `div` and its contents will only display if the `displayGreeting` data property is true.\nYou can also see Vue's binding syntax, where we use `{{ greeting }}` to bind data to the UI.\n\n## Ably Channels for pub/sub\n\nThe app uses [Ably](https://www.ably.io/) for [pub/sub messaging](https://www.ably.io/documentation/core-features/pubsub) between the players. Ably is an enterprise-ready pub/sub messaging platform that makes it easy to design, ship, and scale critical realtime functionality directly to your end-users.\n\n[Ably Channels](https://www.ably.io/channels) are multicast (many publishers can publish to many subscribers) and we can use them to build peer-to-peer apps.\n\n\"Peer to peer\" (p2p) is a term from distributed computing that describes any system where many participants, often referred to as \"nodes\", can participate in some form of collective communication. The idea of peer to peer was popularised in early file sharing networks, where users could connect to each other to exchange files, and search across all of the connected users. In this demo, we're going to build a simple app that will allow one of the peers to elect themselves to be the **\"leader\"**, and co-ordinate communication between each instance of our app.\n\n## Ably channels and API keys\n\nIn order to run this app, you will need an Ably API key. If you are not already signed up, you can [sign up now for a free Ably account](https://www.ably.io/signup). Once you have an Ably account:\n\n1. Log into your app dashboard.\n2. Under **“Your apps”**, click on **“Manage app”** for any app you wish to use for this tutorial, or create a new one with the “Create New App” button.\n3. Click on the **“API Keys”** tab.\n4. Copy the secret **“API Key”** value from your Root key, we will use this later when we build our app.\n\nThis app is going to use [Ably Channels](https://www.ably.io/channels) and [Token Authentication](https://www.ably.io/documentation/rest/authentication/#token-authentication).\n\n## Making sure to send consistent messages by wrapping the Ably client\n\nIn [PubSubClient.js](https://github.com/ably/depict-it/blob/main/app/js/p2p/PubSubClient.js) we make a class called `PubSubClient` - which adds metadata to messages sent outwards, so we don't have to remember to do it in the calling code.\n\n```js\nclass PubSubClient {\n  constructor(onMessageReceivedCallback) {  \n    this.connected = false;\n    this.onMessageReceivedCallback = onMessageReceivedCallback;\n  }\n```\n\nFirst we define a `constructor` for the class - and set up some values - a property called `connected`, set to false, and `onMessageReceivedCallback` - a function passed to the constructor that we will use later when Ably messages arrive.\n\nInside the `PubSubClient` class, we define a `connect` function:\n\n```js\n  async connect(identity, uniqueId) {\n    if(this.connected) return;\n\n    this.metadata = { uniqueId: uniqueId, ...identity };\n\n    const ably = new Ably.Realtime.Promise({ authUrl: '/api/createTokenRequest' });\n    this.channel = await ably.channels.get(`p2p-sample-${uniqueId}`);\n\n    this.channel.subscribe((message) =\u003e {\n      this.onMessageReceivedCallback(message.data, this.metadata);\n    });\n\n    this.connected = true;\n  }\n```\n\nWhile we're making a connection, we're subscribing to an Ably Channel and adding a callback function that passes on the `data` property from the Ably message. The data property in the Ably message is the JSON that the `peers` sent, along with some `identifying metadata`. The `PubSubClient` calls the callback function that we pass to its constructor with the data and the metadata we receive from Ably - in this case, the metadata would contain the `identity` object with a unique ID and name for each player.\n\nIn the `PubSubClient` we also define a `sendMessage` function, that adds some functionality on top of the default `Ably publish`.\n\n```javascript\n  sendMessage(message, targetClientId) {\n    if (!this.connected) {\n      throw \"Client is not connected\";\n    }\n\n    message.metadata = this.metadata;\n    message.forClientId = targetClientId ? targetClientId : null;\n    this.channel.publish({ name: \"myMessageName\", data: message});\n  }\n}\n```\n\nThis ensures that whenever `sendMessage` is called, the data stored in `this.metadata` that was set during construction, is included. We're also making sure that if the message is for a specific peer - set using `targetClientId` - then this property is added to our message before we publish it on the Ably Channel.\n\nThe `PubSubClient` is passed to the instances of our `P2PClient` and `P2PServer` classes, to make sure they publish messages in a predictable way.\n\n## Building a web app\n\nThe application is composed of a `Vue` UI, and two main classes, `P2PClient` and `P2PServer`.\n\nThe `peer` who elects themselves as host will be the only one to have an instance of `P2PServer` and all of the `peers` will be `P2PClients`. When we define the Vue app, we create two `null` properties, one for each of these things, inside `Vue data`:\n\n```js\nvar app = new Vue({\n  el: '#app',\n  data: {\n    p2pClient: null,\n    p2pServer: null,\n  ...\n```\n\nWhen a Vue instance is created, it adds all the properties found in its data object to Vue’s [reactivity system](https://vuejs.org/v2/guide/reactivity.html). When the values of those properties change, the view will “react”, updating to match the new values.\n\nBy defining both  of the `p2pClient` and `p2pServer` properties inside of Vue's data object, they become **reactive** - any changes observed to the properties will cause the UI to **re-render**.\n\nOur Vue app only contains two functions, one to start `hosting` and the other to `join`. In reality, they're both doing the same thing (connecting to an `Ably channel` by name), but depending on which button is clicked in the UI, that `peer` will either behave as a host or a client.\n\n```js\n    host: async function(evt) {\n      evt.preventDefault();\n\n      const pubSubClient = new PubSubClient((message, metadata) =\u003e {\n        handleMessagefromAbly(message, metadata, this.p2pClient, this.p2pServer);\n      });\n\n      const identity = new Identity(this.friendlyName);\n      this.p2pServer = new P2PServer(identity, this.uniqueId, pubSubClient);\n      this.p2pClient = new P2PClient(identity, this.uniqueId, pubSubClient);\n\n      await this.p2pServer.connect();\n      await this.p2pClient.connect();\n    },\n```\n\nThe `host` function creates an instance of the `PubSubClient` and provides it with a callback to `handleMessageFromAbly`. Afterwards, it:\n\n* Creates a new `Identity` instance, using the `friendlyName` bound to our UI.\n* Creates a new `P2PServer`.\n* Creates a new `P2PClient`.\n* Connects to each of them (which in turn, calls `connect` on the `PubSubClient` instance).\n\nJoining is very similar:\n\n```js\n    join: async function(evt) {\n      evt.preventDefault();\n\n      const pubSubClient = new PubSubClient((message, metadata) =\u003e {\n        handleMessagefromAbly(message, metadata, this.p2pClient, this.p2pServer);\n      });\n\n      const identity = new Identity(this.friendlyName);\n      this.p2pClient = new P2PClient(identity, this.uniqueId, pubSubClient);\n\n      await this.p2pClient.connect();\n    }\n```\n\nHere, we're doing *exactly the same* as the host, except we're only creating a `P2PClient`.\n\n## HandleMessageFromAbly\n\n`handleMessageFromAbly` is the callback function that the `PubSubClient` will trigger whenever a message is received on the Ably Channel.\n\n```js\nfunction shouldHandleMessage(message, metadata) {  \n  return message.forClientId == null || !message.forClientId || (message.forClientId \u0026\u0026 message.forClientId === metadata.clientId); \n}\n\nfunction handleMessagefromAbly(message, metadata, p2pClient, p2pServer) {\n  if (shouldHandleMessage(message, metadata)) {\n    p2pServer?.onReceiveMessage(message);  \n    p2pClient?.onReceiveMessage(message);\n  } \n}\n```\n\n`handleMessageFromAbly` is responsible for calling `onReceiveMessage` on the instance of `P2PServer` if the current player is the `host`, and then calling `onReceivedMessage` on the instance of `P2PClient`.\n\nIf the received message has a property called `forClientId` and it is **not** for the current client, the message will not be processed.\n\nThis is deliberately **not secure**. All the messages sent on our `Ably channel` are multicast, and received by all peers, so it should not be considered tamper proof - but it does prevent us from having to filter inside of our client and server instances.\n\n## P2PClient\n\nThe [`P2PClient`](https://github.com/ably/depict-it/blob/main/app/js/p2p/P2PClient.js) class does most of the work in the app. It is responsible for sending a `connected` message over the `PubSubClient` when `connect` is called, and most importantly, for keeping track of a copy of the `serverState` whenever a message is received.\n\n```js\nclass P2PClient {\n  constructor(identity, uniqueId, ably) {\n    this.identity = identity;\n    this.uniqueId = uniqueId;\n    this.ably = ably;\n\n    this.depictIt = null;\n    this.serverState = null;\n    this.countdownTimer = null;\n\n    this.state = {\n      status: \"disconnected\",\n      instructionHistory: [],\n      lastInstruction: null\n    };\n  }\n```\n\nThe `P2PClient` constructor assigns its parameters to instance variables, and initializes a `null` `this.serverState` property, along with its own client state in `this.state`.\n\nWe then go on to define the `connect` function:\n\n```js\n  async connect() {\n    await this.ably.connect(this.identity, this.uniqueId);\n    this.ably.sendMessage({ kind: \"connected\" });\n    this.state.status = \"awaiting-acknowledgement\";\n    // this.depictIt = new DepictItClient(this.uniqueId, this.ably);\n  }\n```\n\nThis uses the provided `PubSubClient` (here stored as the property `this.ably`) to send a `connected` message. The `PubSubClient` is doing the rest of the work - adding in the `identity` of the sender during the `sendMessage` call. It also sets `this.state.status` to `awaiting-acknowledgement` - the default state for all of the client instances until the `P2PServer` has sent them a `connection-acknowledged` message.\n\n`OnReceiveMessage` does a little more work:\n\n```js  \n  onReceiveMessage(message) {\n    if (message.serverState) {\n      this.serverState = message.serverState;\n    }\n\n    switch (message.kind) {\n      case \"connection-acknowledged\":\n        this.state.status = \"acknowledged\";\n        break;\n      /*case \"instruction\":\n        this.state.instructionHistory.push(message);\n        this.state.lastInstruction = message;\n        break;*/\n      default: { };\n    }\n  }\n```\n\nThere are two things to pay close attention to here - firstly that we update the property `this.serverState` whenever an incoming message has a property called `serverState` on it. Clients use this to keep a local copy of whatever the `host` says its state is, and we'll use this to bind to our UI later. Secondly, there is a switch on `message.kind` - the type of message we're receiving. In this case, we only actually care about the `connection-acknowledged` message, and updating the `this.state.status` property to `acknowledged` once we receive one.\n\nThere are a few commented lines in this code that we'll discuss later on.\n\n## P2PServer\n\nThe [`P2PServer`](P2PServer.js) class hardly differs from the client. It contains a constructor that creates an empty `this.state` object:\n\n```js\nexport class P2PServer {\n  constructor(identity, uniqueId, ably) {\n    this.identity = identity;\n    this.uniqueId = uniqueId;\n    this.ably = ably;\n\n    // this.stateMachine = DepictIt({ channel: ably });\n\n    this.state = {\n      players: [],\n      hostIdentity: this.identity,\n      started: false\n    };\n  }\n```\n\nIt also contains a connect function that connects to Ably via the `PubSubClient`:\n\n```js\n    async connect() {\n      await this.ably.connect(this.identity, this.uniqueId);\n    }\n```\n\nAnd finally, it contains an `onReceiveMessage` callback function that responds to the `connected` message:\n\n```js\n  onReceiveMessage(message) {\n    switch (message.kind) {\n      case \"connected\": this.onClientConnected(message); break;\n      default: {\n        // this.stateMachine.handleInput(message);\n      };\n    }\n  }\n```\n\nAll of the work is done in `onClientConnected`:\n\n```js\n  onClientConnected(message) {\n    this.state.players.push(message.metadata);\n    this.ably.sendMessage({ kind: \"connection-acknowledged\", serverState: this.state }, message.metadata.clientId);\n    this.ably.sendMessage({ kind: \"game-state\", serverState: this.state });\n  }\n```\n\nWhen a client connects, we keep track of their `metadata` and then send two messages. The first message is a `connection-acknowledged` message - that is sent **specifically** to the `clientId` that just connected. The second is a `game-state` message, with a copy of the latest `this.state` object, that will in turn trigger all the clients to update their internal state.\n\nThere's a little more that happens in the server class (in the currently commented `stateMachine` line), but let's talk about how our game logic works first. We'll revisit expanded versions of `P2PClient` and `P2PServer` later in this article.\n\n# Designing the game\n\nThe game plays out over messages between the `host` and all of the `players`.\n\nWe send messages from the `host` to each individual client representing the next thing they have to do. The `game stacks` (the piles of Depict-It cards), are stored in memory in the host's browser, with only the information required to display to each respective player sent in messages at any one time. This keeps our message payloads small and means we can structure the application in pairs of messages - requests for user input and their responses.\n\nThe game has five key phases:\n\n* Dealing and setup\n* Collecting image input from players (repeats until game end)\n* Collecting text captions from players (repeats until game end)\n* Collecting scores from players\n* Displaying scores\n\nEach of these phases is driven by pairs of messages.\n\nWe store a variable called `lastMessage` inside the `P2P client`. This allows us to make the UI respond to the contents of this last message. This is a simple way to control what is shown on each player's screen.\n\nWe'll use a message type called `wait` to place players in a holding page while other players complete their inputs.\n\nHere are the messages used in each phase of the game:\n\n| Phase                                   | Message kind            | Example                                                                                  |\n| --------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------- |\n| Dealing and setup                       | No messages             |                                                                                          |\n| Collecting image input                  | `drawing-request`       | { kind: \"instruction\", type: \"drawing-request\", value: lastItem.value, timeout: 30_000 } |\n| Collecting image input response         | `drawing-response`      | { kind: \"drawing-response\", imageUrl: \"http://some/url\" }                                |\n| Collecting caption input                | `caption-request`       | { kind: \"instruction\", type: \"caption-request\", value: lastItem.value, timeout: 30_000 } |\n| Collecting caption input response       | `caption-response`      | { kind: \"caption-response\", caption: \"a funny caption\" }                                 |\n| Collecting scores from players input    | `pick-one-request`      | { kind: \"instruction\", type: \"pick-one-request\", stack: stack }                          |\n| Collecting scores from players response | `pick-one-response`     | { kind: \"pick-one-response\", id: \"stack-item-id\" }                                       |\n|                                         | `skip-scoring-forwards` | { kind: \"skip-scoring-forwards\" }                                                        |\n| Displaying scores                       | `show-scores`           | { kind: \"instruction\", type: \"show-scores\", playerScores: state.players }                |\n|                                         | `wait`                  | { kind: \"instruction\", type: \"wait\" }                                                    |\n\nEach of these messages is sent through the `PubSubClient` class, adds some identifying information (the id of the player that sent each message) into the message body for us to filter by in the code.\n\nAs our game runs, and sends these messages to each individual client, it can collect their responses and move the `game state` forwards.\n\nLuckily, there isn't very much logic in the game, it only has to:\n\n* Ensure that when a player sends a response to a request, it is placed on the correct `game stack` of items.\n* Keep track of scores when players vote on items.\n* Keep track of which stack each player is currently holding.\n\nWe need to write some code for each of the game phases to send these `p2p messages` at the right time, and then, build a web UI that responds to the last message received to add a gameplay experience.\n\nWe're going to use a software pattern called a `State Machine` - a way to model a system that can exist in one of several known states, to run the game logic.\n\n## The GameStateMachine\n\nNext we'll write code to capture the logic of the game. We're going to break the phases of the game up into different `Handlers` - that represent both the logic of that portion of the game, and the logic that handles user input during that specific game phase.\n\nOur implementation is part [state machine](https://www.smashingmagazine.com/2018/01/rise-state-machines/), part [command pattern](https://www.dofactory.com/javascript/design-patterns/command) handler.\n\nLet's take a look at what state machine code **can** look like - here's a two-step game definition, taken from [one of our unit tests](https://github.com/ably/depict-it/blob/main/tests/js/game/GameStateMachine.test.js):\n\n```js\nconst twoStepGame = () =\u003e ({\n    steps: {\n        \"StartHandler\": {\n            execute: async function (state) {\n                state.executeCalled = true;\n                return { transitionTo: \"EndHandler\" };\n            }\n        },\n        \"EndHandler\": {\n            execute: async function (state) { }\n        }\n    }\n});\n```\n\nThis game definition doesn't do anything on its own - it's a collection of `steps`. This example shows a start handler that just flags that execute has been called, and then `transitionTo`s the `EndHandler`.\n\n### Defining a game\n\nA game definition looks like this:\n\n```js\nconst gameDef = () =\u003e ({\n    steps: {\n        \"StartHandler\": { ... },\n        \"EndHandler\": { ... }\n    },\n    context: {\n      some: \"object\"\n    }\n});\n```\n\n* Steps **must** be named.\n* Steps **must** contain `StartHandler` and `EndHandler`.\n* Properties assigned to the `state` object during `handleInput` **can** be read in the `execute` function.\n* `context` can be provided, and can contain anything you like to make your game work.\n\n### Defining a handler\n\nHere's one of the handlers from the previous example:\n\n```js\n{\n    execute: async function (state, context) {\n        await waitUntil(() =\u003e state.gotInput == true, 5_000);\n        return { transitionTo: \"EndHandler\" };\n    },\n    handleInput: async function(state, context, input) {\n        state.gotInput = true;\n    }\n}\n```\n\nThis is an exhaustive example, with both an `execute` and a `handleInput` function, though only `execute` is required.\n\n* Handlers **must** contain an `execute` function.\n* Handlers **can** contain a `handleInput` function.\n* Handlers **can** call `waitUntil(() =\u003e some-condition-here);` to pause execution while waiting for input.\n* `handleInput` **can** be called multiple times.\n* `waitUntil` can be given a `timeout` in `milliseconds`.\n* `context` will be passed to the `execute` and `handleInput` functions every time they are called by the `GameStateMachine`.\n* Handlers **must** return a `transitionTo` response from their `execute` function, that refers to the next `Handler`.\n* Handlers **must** be `async functions`.\n\n## How the GameStateMachine works\n\nThe [`GameStateMachine`](https://github.com/ably/depict-it/blob/main/app/js/game/GameStateMachine.js) takes a `Game Definition` - comprised of `steps` and an optional `context` object, and manages which steps are executed and when. It always expects a game to have a `StartHandler` and an `EndHandler` - as it uses those strings to know which game steps to start and end on.\n\nCreate a new instance of a game by doing something like this:\n\n```js\nconst game = new GameStateMachine({\n    steps: {\n        \"StartHandler\": { ... },\n        \"EndHandler\": { ... }\n    },\n    context: {\n      some: \"object\"\n    }\n});\n```\n\nThen, when you have a `game` object, you can call `game.run();` to start processing the game logic at the `StartHandler`.\n\n### What is the GameStateMachine doing?\n\nThe constructor for the `GameStateMachine` takes the `steps` and the `context` and saves them inside itself.\nOnce that's done, the `run` function does all the hard work.\n\n```js\nasync run() {\n    console.log(\"Invoking run()\", this.currentStepKey);\n\n    this.trackMilliseconds();\n\n    const currentStep = this.currentStep();\n    const response = await currentStep.execute(this.state, this.context);\n\n    if (this.currentStepKey == \"EndHandler\" \u0026\u0026 (response == null || response.complete)) {\n        return; // State machine exit signal\n    }\n\n    if (response == null) {\n        throw \"You must return a response from your execute functions so we know where to redirect to.\";\n    }\n\n    this.currentStepKey = response.transitionTo;\n    this.run();\n}\n```\n\nThe state machine:\n\n* Keeps track of the `currentStepKey` - this is the string that you use to define your `steps` in the `game definition`.\n* Keeps track of time.\n* Awaits the `execute` function of the `StartHandler`.\n* Evaluates the response.\n\nOnce a response from the current handler has been received:\n\n* If the `currentStepKey` is `EndHandler` then `return` - the game has concluded.\n* Otherwise, update the `currentStepKey` to be the target of the `transitionTo` response - changing the current active state of the game.\n* Call `run` again, to process the step we've just arrived at.\n\nThis flow of moving between game steps based on the outcome of the current step allows us to define all kinds of games!\n\nThe state machine contains a `handleInput` function:\n\n```js\nasync handleInput(input) {\n    const currentStep = this.currentStep();\n    if (currentStep.handleInput) {\n        currentStep.handleInput(this.state, this.context, input);\n    } else {\n        console.log(\"Input received while no handler was available.\");\n    }\n}\n```\n\nWe pass user input to this function and it will find the currently active step, and forward the input onto the relevant `handleInput` function defined in it. This means that if any of our steps require user input, the input will be passed through this function.\n\nWe can connect this up to our Web UI and Ably connection later.\n\n## The GameStateMachine and our game\n\nInside [/app/js/game/](https://github.com/ably/depict-it/tree/main/app/js/game) there are a series of files. The ones with `DepictIt` in the filename contain the game logic.\n\n```[bash]\nDepictIt.js\nDepictIt.cards.js\nDepictIt.handlers.js\nDepictIt.types.js\nGameStateMachine.js\n```\n\n[`DepictIt.js`](https://github.com/ably/depict-it/blob/main/app/js/game/DepictIt.js) is the entrypoint, and references all of the game handlers, returning the `Game Definition` needed to create a game:\n\n```js\nexport const DepictIt = (handlerContext) =\u003e new GameStateMachine({\n  steps: {\n    \"StartHandler\": new StartHandler(),\n    \"DealHandler\": new DealHandler(),\n    \"GetUserDrawingHandler\": new GetUserDrawingHandler(180_000),\n    \"GetUserCaptionHandler\": new GetUserCaptionHandler(60_000),\n    \"PassStacksAroundHandler\": new PassStacksAroundHandler(),\n    \"GetUserScoresHandler\": new GetUserScoresHandler(),\n    \"EndHandler\": new EndHandler()\n  },\n  context: handlerContext\n});\n```\n\n`DepictIt` is a function because we're going to pass in an Ably connection inside the `handlerContext` parameter, but it returns a fully created `GameStateMachine` instance to run in the Vue.js app. The game is defined as a series of handlers in the sample above. Each of these game handlers are imported from the [DepictIt.handlers.js](https://github.com/ably/depict-it/blob/main/app/js/game/DepictIt.handlers.js) file.\n\nEach `Handler` has access to an `ably client` supplied as a property called `channel` in a `context` object. The game works by having the hosting player's browser keep track of where all the `game hands` are, sending players p2p messages to make the client code in their browsers prompt the players for input.\n\nEach of these messages looks similar:\n\n```js\ncontext.channel.sendMessage({\n    kind: \"instruction\",\n    type: \"drawing-request\",\n    value: lastItem.value,\n    timeout: this.waitForUsersFor\n  }, player.clientId);\n```\n\nThey each contain a property called `kind` with a value of `instruction`, which allows the clients to process these messages differently to the standard `connection` messages. They also each have a `type` - which varies depending on which phase of the game is currently being played.\n\n`Handlers` control which message `types` the players send. Additionally, messages will always contain a `value`.\n\nThis `value`, when in the drawing phase of the game, is going to be the `prompt` the player is using to draw from. If we're in the `captioning` phase of the game, it'll contain the URL of the image they need to caption so our player's browser can render it in the UI.\n\nMessages can also feature an optional `timeout` value (some of the steps have a limit on the length of time they'll wait for users to reply with a drawing or caption), so including this `timeout` in the `instruction` means we can render a timer bar on the client side.\n\nLet's now dive into a few of our steps and take a look at what they do.\n\n### StartHandler\n\nOn `execute`:\n\n* Creates prompt deck imported from [DepictIt.cards.js](https://github.com/ably/depict-it/blob/main/app/js/game/DepictIt.cards.js).\n* Shuffles deck.\n* Transitions to `DealHandler`.\n\nOn `handleInput`:\n\n* There is no user input.\n\n### DealHandler\n\nOn `execute`:\n\n* Creates `Game Stack` for every player in `state.players`.\n* Adds prompt to the top of the `Game Stack`.\n* Transitions to `GetUserDrawingHandler`.\n\nOn `handleInput`:\n\n* There is no user input.\n\n### GetUserDrawingHandler\n\nOn `execute`:\n\n* Sends `drawing-request` for every player in `state.players`.\n* Request contains `prompt` from the top of that players `Game Stack`.\n* Waits for players to respond, or for 180 seconds to elapse.\n* Adds placeholder images to `Game Stack` if players do not respond.\n* Transitions to `PassStacksAroundHandler`.\n\nOn `handleInput`:\n\n* Handler expects a `url` property in the player response message. `url` points to image stored somewhere publically accessible. (We're going to use `Azure storage buckets` for this later on.)\n* When player input is received, an `instruction` is sent to the player, prompting them to `wait`.\n\n### GetUserCaptionHandler\n\nOn execute:\n\n* Sends `caption-request` for every player in `state.players`.\n* Request contains `url` from the top of that players `Game Stack`.\n* Waits for players to respond, or for 60 seconds to elapse.\n* Adds \"Answer not submitted\" to `Game Stack` if players do not respond.\n* Transitions to `PassStacksAroundHandler`.\n\nOn handleInput:\n\n* Handler expects a `caption` property in the player response message.\n* When player input is received, an `instruction` is sent to the player, prompting them to `wait`.\n\n### PassStacksAroundHandler\n\nOn `execute`:\n\n* Moves the `Game Stacks` forward to the next player that is required to contribute.\n* If the `Game Stacks` have been moved to their original owner, transitions to `GetUserScoresHandler`.\n* Otherwise, picks either `GetUserDrawingHandler` or `GetUserCaptionHandler`.\n* Picks `GetUserDrawingHandler` when the top item in the `Game Stack` is a `Caption`.\n* Picks `GetUserCaptionHandler` when the top item in the `Game Stack` is a `Drawing`.\n\n### GetUserScoresHandler\n\nOn `execute`:\n\n* Sends a `pick-one-request` for each `Game Stack`.\n* Waits for all players to submit a score for that specific `Game Stack`.\n* Sends the next `pick-one-request` until all `Game Stacks` have been scored.\n\nOn `handleInput`:\n\n* Assigns a vote to the author of each picked `Game Stack Item`.\n* Handles admin input to progress the game forward and skip the user scoring, to prevent games hanging.\n\n### EndHandler\n\nOn `execute`:\n\n* Sends a `show-scores` message with the final scores of the `Game round`.\n\n## Handlers and async / await\n\nThe interesting thing about these handlers is that we're using `async/await` and an **unresolved Promise** to pause the execution while we wait for user input. This allows us to represent the game's control flow linearly while waiting for messages to arrive over the `p2p channel`.\n\n`GetUserDrawingHandler` is an example of this linear flow: First we set up an `execute` method, creating an instance variable called `submitted` (scoped to `this`). We know that when the number of `submitted` drawings is equal to the total number of `players`, every player has sent an image.\n\n```js\nasync execute(state, context) {\n    this.submitted = 0;\n    ...\n```\n\nNext, we send an `instruction` to each player, in this case a `drawing-request`\n\n```js\n    for (let player of state.players) {\n        ...\n        context.channel.sendMessage({ kind: \"instruction\", type: \"drawing-request\", ...);\n    }\n```\n\nThen we begin waiting for responses.\nWe use the syntax `await waitUntil(() =\u003e some condition)` to do this.\n\n```js\n    const result = { transitionTo: \"PassStacksAroundHandler\" };\n\n    try {\n        await waitUntil(() =\u003e this.submitted == state.players.length, this.waitForUsersFor);\n    }\n    catch (exception) {\n        result.error = true;\n        ... /* error handling */\n    }\n\n    return result;\n}\n```\n\nThis creates an **unresolved Promise** that is [polling](https://davidwalsh.name/javascript-polling) in the background and executing the function passed to it. When that function returns `true`, the execution will continue, and the `Promise` will resolve.\n\nWhile the code is paused here, awaiting the unresolved promise, messages sent via the Ably channel will be passed to the `handleInput` function of this specific handler.\n\n```js\nasync handleInput(state, context, message) {\n    if (message.kind == \"drawing-response\") {\n        const stackItem = new StackItem(\"image\", message.imageUrl);\n        const stack = state.stacks.filter(s =\u003e s.heldBy == message.metadata.clientId)[0];\n\n        stack.add({ ...stackItem, author: message.metadata.clientId, id: createId() });\n        context.channel.sendMessage({ kind: \"instruction\", type: \"wait\" }, message.metadata.clientId);\n\n        this.submitted++;\n    }\n}\n```\n\nThe input handler increments `this.submitted` each time it receives a message from Ably. Each time the `waitUntil` condition runs, it checks what the current value of `this.submitted` is. Eventually, enough messages will be received for the promise to resolve.\n\nThe `waitUntil` call also takes a timeout value - in this example it's the instance variable `this.waitForUsersFor` which is provided in the constructor. If the callback condition hasn't been reached by the moment the timer reaches this timeout value, the `Promise` will be rejected, and an `exception` will be thrown. This means that we can do things like handling a player taking too long to draw a picture by submitting a default image.\n\n# The game UI\n\nWe'll now go over the basics of the Vue app, the `P2PClient`, and how the `GameStateMachine` orchestrates the gameplay.\n\nThe Vue app is split out into `Vue Components`. Each component will respond to a specific `Game State Instruction` message. The `Game State Machine` will forward on messages received from Ably to the Vue app, so that the `Game Handlers` can respond and update the UI accordingly. We'll use an HTML canvas to present the players with a way of drawing on the screen with a mouse (or fingers/pointer on touch screen devices) and capturing their input.\n\n## Building the UI with Vue\n\nThe UI markup is deceptively simple at the top level, because we use `Vue Components` for all of the game phases.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\n\u003chead\u003e\n  \u003cmeta charset=\"utf-8\" /\u003e\n  \u003ctitle\u003eDepict-it\u003c/title\u003e\n  \u003cmeta ...\u003e\n  \u003cscript src=\"//cdn.ably.io/lib/ably-1.js\" defer\u003e\u003c/script\u003e\n  \u003cscript src=\"//cdn.jsdelivr.net/npm/vue/dist/vue.js\"\u003e\u003c/script\u003e\n\n  \u003cscript src=\"/index.js\" type=\"module\"\u003e\u003c/script\u003e\n  \u003clink href=\"https://fonts.googleapis.com/css2?family=Sora\u0026display=swap\" rel=\"stylesheet\" /\u003e\n  \u003clink href=\"/style.css\" rel=\"stylesheet\" /\u003e\n\u003c/head\u003e\n```\n\nIn the HTML head we reference the Ably JavaScript SDK, along with the Vue.js library.\nWe also reference the `Index.js` file as a module - this means we can use native browser `import` and `export` module syntax. Finally, we reference a Google Gont and `style.css` which contain all the styles for the UI.\n\nThe game UI is defined within the HTML `main` element: \n\n```html\n    \u003cdiv v-if=\"!joinedOrHosting\" class=\"join-container\"\u003e\n      \u003ccreate-game-form v-on:create=\"host\" v-on:join=\"join\"\u003e\u003c/create-game-form\u003e\n    \u003c/div\u003e\n```\n\nFirst, we show the form that players will use to create a new game - the `CreateGameForm`. This is imported from [CreateGameForm.js](https://github.com/ably/depict-it/blob/main/app/js/components/CreateGameForm.js). Using [Vue's conditional rendering](https://vuejs.org/v2/guide/conditional.html) we can make this element show only when a player hasn't yet hosted or joined a game. Once a game *has* been joined or hosted, we then show the `activeGame` portion of the app, which contains the components that trigger when the game is running. This is split into several parts. The first part is called the `game-lobby`:\n\n```html\n    \u003cdiv v-else id=\"activeGame\" class=\"game-info\"\u003e\n      \u003cdiv class=\"game-lobby\" v-if=\"gameCanBeStarted\"\u003e\n        \u003cinvite-link :game-id=\"gameId\"\u003e\u003c/invite-link\u003e\n        \u003cconnected-players-summary :state=\"transmittedServerState\"\u003e\u003c/connected-players-summary\u003e\n        \u003cready-or-waiting-prompt :is-host=\"isHost\" :state=\"transmittedServerState\" v-on:startgame=\"startGame\"\u003e\n        \u003c/ready-or-waiting-prompt\u003e\n      \u003c/div\u003e\n      \u003cdiv v-if=\"!gameCanBeStarted \u0026\u0026 !state?.lastInstruction\"\u003e\n        \u003cloader\u003e\u003c/loader\u003e\n      \u003c/div\u003e\n```\n\nThe `game-lobby` references components to render invite links, connected players and prompt cards.\n\nThere is also a `timer-bar` component that binds to any `timeouts` sent from the `host`:\n\n```html\n      \u003ctimer-bar v-if=\"state?.lastInstruction?.timeout != null\" :countdown=\"state?.lastInstruction?.timeout\"\u003e\n      \u003c/timer-bar\u003e\n```\n\nFinally, we have the markup components for the different game state instructions sent from the `host`. You'll spot familiar names here as they line up with the handlers that were defined earlier - one component per game phase.\n\n```html\n      \u003cdiv v-if=\"state?.lastInstruction\" class=\"playfield\"\u003e\n        \u003cplayfield-wait-for-others :state=\"state\"\u003e\u003c/playfield-wait-for-others\u003e\n\n        \u003cplayfield-drawing :state=\"state\" :client=\"depictItClient\"\u003e\u003c/playfield-drawing\u003e\n        \u003cplayfield-caption :state=\"state\" :client=\"depictItClient\"\u003e\u003c/playfield-caption\u003e\n        \u003cplayfield-pick-one :state=\"state\" :client=\"depictItClient\" :is-host=\"isHost\"\u003e\u003c/playfield-pick-one\u003e\n\n        \u003cplayfield-show-scores :state=\"state\" :is-host=\"isHost\" v-on:nextround=\"nextRound\"\u003e\u003c/playfield-show-scores\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/main\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n\nThroughout the markup we're using the Vue.js syntax `:state=` and `:is-host`. These attributes are Vue bindings that pass values from our main Vue app down to the Vue components so that the components can use them. Likewise, the `v-on:` event handlers bind functions in the Vue app to `events` that the components can raise.\n\nWe touched on the layout of our `index.js` file briefly at the start of this README, but let's take a look at it here in full.\n\n```js\nexport var app = new Vue({\n  el: '#app',\n  data: {\n    p2pClient: null,\n    p2pServer: null,\n\n    gameId: null,\n    friendlyName: null,\n  },\n```\n\nFirst we define some data properties. `Vue` makes these properties `observable` so we can bind them to the UI (whenever anything changes in these properties, the `UI` will update).\n\nNext we define some computed properties to make the HTML binding code more succinct:\n\n```js\n  computed: {\n    state: function () { return this.p2pClient?.state; },\n    transmittedServerState: function () { return this.p2pClient?.serverState; },\n    joinedOrHosting: function () { return this.p2pClient != null || this.p2pServer != null; },\n    isHost: function () { return this.p2pServer != null; },\n    hasMessage: function () { return this.message != null; },\n    gameCanBeStarted: function () { return this.transmittedServerState \u0026\u0026 !this.transmittedServerState.started },\n    depictItClient: function () { return this.p2pClient?.depictIt; }\n  },\n```\n\nThese computed properties are used in our markup above. Where `depictItClient` is used in the HTML, it is this computed property that is being referenced. Anything you bind to either needs to be in your `Vue data` or your `Vue computed` properties.\n\nFinally we define our `host` and `join` methods to bind to clicks, along with `startGame` and `nextRound` to bind to events emitted by our `Vue components`.\n\n```js\n  methods: {\n    host: async function (context) {\n      this.gameId = context.gameId;\n      this.friendlyName = context.friendlyName;\n\n      const pubSubClient = new PubSubClient((message, metadata) =\u003e {\n        handleMessagefromAbly(message, metadata, this.p2pClient, this.p2pServer);\n      });\n\n      const identity = new Identity(this.friendlyName);\n      this.p2pServer = new P2PServer(identity, this.gameId, pubSubClient);\n      this.p2pClient = new P2PClient(identity, this.gameId, pubSubClient);\n\n      await this.p2pServer.connect();\n      await this.p2pClient.connect();\n    },\n    join: async function (context) {\n      this.gameId = context.gameId;\n      this.friendlyName = context.friendlyName;\n\n      const pubSubClient = new PubSubClient((message, metadata) =\u003e {\n        handleMessagefromAbly(message, metadata, this.p2pClient, this.p2pServer);\n      });\n\n      const identity = new Identity(this.friendlyName);\n      this.p2pClient = new P2PClient(identity, this.gameId, pubSubClient);\n\n      await this.p2pClient.connect();\n    },\n    startGame: async function (evt) {\n      this.p2pServer?.startGame();\n    },\n    nextRound: async function (evt) {\n      this.p2pServer?.nextRound();\n    }\n  }\n});\n```\n\nThis is the entire outline of the top level of the app, most of the display logic is hidden in the `Vue components`.\n\nRemember, when a user joins or hosts a game, a `P2PClient` or `P2PServer` instance is created, and the state managed inside of them becomes observable, so we can bind any properties on these objects into the app.\n\nAt the bottom of `Index.js` is also a `handleMessagefromAbly` function that passes messages received over the `P2P channel` onto the `P2PServer` and `P2PClient` instances. Let's take a quick look inside those classes again to see how this all works.\n\n## Inside P2PServer\n\nP2PServer is really where most of the game is managed.\n\nWhen the host creates a game, a new instance of `P2PServer` is created. In turn, this creates an instance of the `GameStateMachine` with an empty `this.state` object.\n\n```js\nimport { DepictIt } from \"../game/DepictIt.js\";\n\nexport class P2PServer {\n  constructor(identity, uniqueId, ably) {\n    this.identity = identity;\n    this.uniqueId = uniqueId;\n    this.ably = ably;\n\n    this.stateMachine = DepictIt({\n      channel: ably\n    });\n\n    this.state = {\n      players: [],\n      hostIdentity: this.identity,\n      started: false\n    };\n  }\n```\n\nNotice that when we call the imported `DepictIt` function, we're passing a `context object` with the property `channel` set to the `ably` parameter. By providing this channel to the `GameStateMachine` instance, we're making sure that every time one of our `Handlers` executes, it has access to the Ably channel, and can use it to send `p2p messages` to all the other clients.\n\nNext, we're going to define `connect` and `startGame`:\n\n```js\n  async connect() {\n    await this.ably.connect(this.identity, this.uniqueId);\n  }\n\n  async startGame() {\n    this.state.started = true;\n\n    this.ably.sendMessage({ kind: \"game-start\", serverState: this.state });\n    this.stateMachine.state.players = this.state.players;\n    this.stateMachine.run();\n  }\n```\n\nThese two functions are bound into our `Vue components`, and both assign the currently connected players to the `stateMachine.state` property, and trigger `stateMachine.run();`, which starts the game of Depict-It.\n\n`nextRound` is used to progress the game - it is a function that calls `resetCurrentStepKeepingState` on our `stateMachine` before invoking `run` again - a function that moves the current handler back to the start without clearing player scores.\n\n```js\n  async nextRound() {\n    this.stateMachine.resetCurrentStepKeepingState();\n    this.stateMachine.run();\n  }\n```\n\nAnd finally, let's take a look at the vitally important `onReceiveMessage` handler:\n\n```js\n  onReceiveMessage(message) {\n    switch (message.kind) {\n      case \"connected\": this.onClientConnected(message); break;\n      default: {\n        this.stateMachine.handleInput(message);\n      };\n    }\n  }\n\n  onClientConnected(message) {\n    this.state.players.push(message.metadata);\n    this.ably.sendMessage({ kind: \"connection-acknowledged\", serverState: this.state }, message.metadata.clientId);\n    this.ably.sendMessage({ kind: \"game-state\", serverState: this.state });\n  }\n}  \n```\n\nYou can see that in this version of the handler, we treat clients connecting as a special case, by replying with `connection-acknowledged`. We use this to update our `connected` status in the debug UI element.\n\nMost importantly however, is that any *other* messages are passed to the `this.stateMachine.handleInput()` function.\n\nWhat we're doing here is delegating responsibility for processing our messages to whichever `handler` is currently active, routed via the `GameStateMachine` instance. This is the glue that takes a message received by our `Ably connection`, and passes it through our `GameStateMachine` to the currently active `Handler`.\n\n## Inside P2PClient\n\nThe `P2PClient` that gets created when anyone joins a game follows the same general pattern as the `P2PServer`.\n\nFirst we have a constructor that creates some state, and a few properties that our game is going to use:\n`depictIt` is going to store a client wrapper that we'll later bind into some `Vue components`, and the `this.state` object contains both an `instructionHistory` as well as a `lastInstruction` for us to track all the messages this client has received from the `host`.\n\n```js\nimport { DepictItClient } from \"../game/DepictIt.js\";\n\nexport class P2PClient {\n  constructor(identity, uniqueId, ably) {\n    this.identity = identity;\n    this.uniqueId = uniqueId;\n    this.ably = ably;\n\n    this.depictIt = null;\n    this.serverState = null;\n\n    this.state = {\n      status: \"disconnected\",\n      instructionHistory: [],\n      lastInstruction: null\n    };\n  }\n```\n\nNext, much like in `P2PServer`, we define a `connect` function that sends a message to the `host` and waits for `acknowledgement`. We're also creating an instance of `DepictItClient` - a class that offers a function for each response the `client` has to send back to the `host`. We bind this to the `Vue components` so they can reply to the `host` when they have player input.\n\n```js\n  async connect() {\n    await this.ably.connect(this.identity, this.uniqueId);\n    this.ably.sendMessage({ kind: \"connected\" });\n    this.state.status = \"awaiting-acknowledgement\";\n    this.depictIt = new DepictItClient(this.uniqueId, this.ably);\n  }\n```\n\nAnd finally we have the `onReceiveMessage` function.\n\nThe most important piece here, is that when the `P2Pclient` receives a message with a `kind` of `instruction`, it'll store a copy of it into the `instructionHistory`and will assign the most recent message to the property `this.lastInstruction`.\n\n```js\n\n  onReceiveMessage(message) {\n    if (message.serverState) {\n      this.serverState = message.serverState;\n    }\n\n    switch (message.kind) {\n      case \"connection-acknowledged\":\n        this.state.status = \"acknowledged\";\n        break;\n      case \"instruction\":\n        this.state.instructionHistory.push(message);\n        this.state.lastInstruction = message;\n        break;\n      default: { };\n    }\n  }\n}\n```\n\nPractically all of the UI is going to be bound-up to the values in the `lastInstruction` property. It is the most important piece of data in the entire application.\n\n## Splitting our game phases into Vue components\n\n`Vue components` let us split out parts of the functionality into what looks like separate Vue apps. They follow practically the same syntax, but contain both the UI template and the JavaScript.\n\nThe Depict-It app is split into a bunch of smaller components:\n\n```bash\nbase-components/CopyableTextBox.js\nbase-components/DrawableCanvas.js\n\nConnectedPlayersSummary.js\nCreateGameForm.js\nInviteLink.js\nLoader.js\nPlayfieldCaption.js\nPlayfieldDrawing.js\nPlayfieldPickOne.js\nPlayfieldShowScores.js\nPlayfieldWaitForOthers.js\nReadyOrWaitingPrompt.js\nStackItem.js\nTimerBar.js\n```\n\nKeeping to a sensible convention, the component name has the phase of the game it's associated with in the filename.\n\nLet's look inside `StackItem` as an example, since it is a simple component:\n\n```js\nexport const StackItem = {\n  props: ['item'],\n  methods: {\n    emitIdOfClickedElement: async function () {\n      this.$emit('click', this.item.id);\n    }\n  },\n  template: `\n\u003cspan v-if=\"item.type == 'string'\"\n      v-on:click=\"emitIdOfClickedElement\"\n      class=\"stack-item stack-text\"\u003e{{ item.value }}\u003c/span\u003e\n\n\u003cimg  v-else\n      v-bind:src=\"item.value\"\n      v-on:click=\"emitIdOfClickedElement\"\n      class=\"stack-item\" /\u003e\n`\n};\n```\n\nVue Components:\n\n* Can have named `props` that you can bind in `HTML` using the `:prop-name=\"something\"` syntax.\n* Can have methods.\n* Can have computed properties.\n* Have a template string.\n\nIn the example of the `StackItem`, there is a `v-if` and `v-else` statement displaying a `span` if the item is a `string` (a caption), or an `img` tag when the item is a `drawing`.\n\nAll of the components follow a similar pattern - capturing bits of interaction.\n\nThe other piece of syntax you can see here is the `this.$emit` function call.\n\nDoing this allows us to define custom events that can be bound in the consuming `Vue component` or `Vue app` - so if we emit an event, in the parent we can use the `v-on` syntax to listen and respond to it. In this case, we're creating an event called `click`, and passing the `item.id` of the selected `Stack Item` to subscribers of that event.\n\nLet's now take a look at the `PlayfieldDrawing` component to see how we handle sending data back from the server.\n\n```js\nexport const PlayfieldDrawing = {\n  props: ['state', 'client'],\n\n  methods: {\n    sendImage: async function (base64EncodedImage) {\n      await this.client.sendImage(base64EncodedImage);\n    }\n  },\n\n  template: `\n  \u003csection v-if=\"state?.lastInstruction?.type == 'drawing-request'\"\u003e\n    \u003cdiv class=\"drawing-prompt\"\u003e\n      \u003cdiv class=\"prompt-front\"\u003eDraw This\u003c/div\u003e\n      \u003cdiv class=\"prompt-back\"\u003e\n        {{ state.lastInstruction.value }}\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdrawable-canvas v-on:drawing-finished=\"sendImage\"\u003e\u003c/drawable-canvas\u003e\n  \u003c/section\u003e\n  `\n};\n```\n\nThis `Vue component` is typical of the others that require interactivity. Remember when we walked through `P2PClient` and we created an instance of `DepictItClient`?  We've bound that client into the `Vue component` as the property `client`. What this means, is that when our `sendImage` function is triggered by the `DrawableCanvas` raising the `drawing-finished` event, we can use that client to send an image back to the `host`.\n\nThis general pattern holds for collecting captions and scoring our game.\n\n## The DepictIt Client\n\nOur `DepictIt Client` is a small wrapper class around all of our components' interactions with the `host`.\nThis client is responsible for sending data and little else.\n\nAll those messages that are expected? They're all defined here.\n\n```js\nexport class DepictItClient {\n  constructor(gameId, channel) {\n    this.gameId = gameId;\n    this.channel = channel;\n  }\n\n  async sendImage(base64EncodedImage) {\n    ...\n  }\n\n  async sendCaption(caption) {\n    this.channel.sendMessage({ kind: \"caption-response\", caption: caption });\n  }\n\n  async logVote(id) {\n    this.channel.sendMessage({ kind: \"pick-one-response\", id: id });\n  }\n\n  async hostProgressedVote() {\n    this.channel.sendMessage({ kind: \"skip-scoring-forwards\" })\n  }\n}\n```\n\nThere is one extra interesting function in here though - `sendImage`.\n\n```js\n  async sendImage(base64EncodedImage) {\n    const result = await fetch(\"/api/storeImage\", {\n      method: \"POST\",\n      body: JSON.stringify({ gameId: this.gameId, imageData: base64EncodedImage })\n    });\n\n    const savedUrl = await result.json();\n    this.channel.sendMessage({ kind: \"drawing-response\", imageUrl: savedUrl.url });\n  }\n```\n\n`sendImage` has to POST the `base64EncodedImage` that's created by our `DrawableCanvas` component to an `API` running on our instance of `Azure Functions` before it sends a message back to the `host`.\n\n## Storing images into Azure Blob Storage via an Azure Function\n\nTo make our images work, we've added an extra function to the directory [/api/storeImage/index.js](https://github.com/ably/depict-it/tree/main/api/storeImage):\n\n```js\nconst { StorageSharedKeyCredential } = require(\"@azure/storage-blob\");\nconst { BlobServiceClient } = require(\"@azure/storage-blob\");\n\nfunction uuidv4() {\n    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n      var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r \u0026 0x3 | 0x8);\n      return v.toString(16);\n    });\n}  \n\nmodule.exports = async function (context, req) {\n\n    const defaultAzureCredential = new StorageSharedKeyCredential(process.env.AZURE_ACCOUNT, process.env.AZURE_KEY);\n    const blobServiceClient = new BlobServiceClient(process.env.AZURE_BLOBSTORAGE, defaultAzureCredential);\n    const containerClient = blobServiceClient.getContainerClient(process.env.AZURE_CONTAINERNAME);\n\n    const unique = `game_${req.body.gameId}_${uuidv4()}.png`;\n    const url = `${process.env.AZURE_BLOBSTORAGE}/${process.env.AZURE_CONTAINERNAME}/${unique}`;\n    const fileData = req.body.imageData.replace(/^data:image\\/\\w+;base64,/, \"\");\n    const buffer = new Buffer(fileData, 'base64');\n\n    const blockBlobClient = containerClient.getBlockBlobClient(unique);\n    const uploadBlobResponse = await blockBlobClient.upload(buffer, buffer.length || 0);\n\n    context.res = { \n        headers: { \"content-type\": \"application/json\" },\n        body: { url: url }\n    };\n};\n```\n\nThis you may recognize as boiler-plate, it's the standard `Azure Blob Storage SDK` code to upload a file to a storage bucket. This `Azure function` is mounted by the `Azure functions runtime` to the path `/api/storeImage` so we can call it using our browser's `Fetch API`.\n\nThe function returns an `absolute url` of the stored image - which is stored in a bucket that supports `unauthenticated reads`.\nThe bucket is also configured to auto-delete items after 24-hours to keep our storage costs really low.\n\nThis is a super quick way to add a little bit of statefulness to our app - especially because the average size of our images is over the message size cap for `Ably messages`.\n\n## Drawing using HTML5 Canvas\n\nWe have a `Vue component` that we use to handle drawing with a mouse, or \"finger painting\" on touch devices.\n\n```js\nexport const DrawableCanvas = {\n  ...\n\n  mounted: function () {\n    const element = document.getElementById(this.canvasId);\n    if (element \u0026\u0026 !this.canvas) {\n      this.canvas = new DrawableCanvasElement(this.canvasId).registerPaletteElements(this.paletteId);\n    }\n  },\n\n  ...\n\n  template: `\n  \u003cdiv class=\"drawable-canvas\"\u003e\n    \u003cdiv class=\"canvas-and-paints\"\u003e\n      \u003ccanvas v-bind:id=\"canvasId\" class=\"image-frame paint-canvas\" width=\"400\" height=\"400\"\u003e\u003c/canvas\u003e  \n      \u003cdiv v-bind:id=\"paletteId\" class=\"palette\"\u003e\n        \u003cdiv style=\"background-color: black;\" v-on:click=\"colorSelected\"\u003e\u003c/div\u003e\n        \u003cdiv style=\"background-color: red;\" v-on:click=\"colorSelected\"\u003e\u003c/div\u003e\n        \u003cdiv style=\"background-color: green;\" v-on:click=\"colorSelected\"\u003e\u003c/div\u003e\n        \u003cdiv style=\"background-color: blue;\" v-on:click=\"colorSelected\"\u003e\u003c/div\u003e\n        \u003cdiv style=\"background-color: white;\" v-on:click=\"eraserSelected\"\u003e\u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n    \u003cbutton v-on:click=\"$emit('drawing-finished', canvas.toString())\" class=\"form-button finished-drawing\"\u003eI'm finished!\u003c/button\u003e\n  \u003c/div\u003e`\n};\n```\n\nThere are two important things about this `component`. Firstly, we use the class `DrawableCanvasElement` from the npm package [`@snakemode/snake-canvas`](https://github.com/snakemode/snake-canvas) (This package was built while writing this game).\n\nWe emit an event called `drawing-finished` when the user clicks the **I'm finished** button in the template. This event is listened to in the consuming `Vue component` - in this case, the `PlayfieldDrawing` component that deals with the drawing phase of the game. As an event, we pass the result of the function call `canvas.toString()` - this is a thin wrapper around the native browser call to convert a HTML Canvas element to a base64-encoded PNG. The consuming component then uses this to upload images to our `Azure Blob Storage` account.\n\n### How does the drawing canvas work?\n\nThere's not too much to the drawing canvas - it takes an element Id (that it presumes is on a HTML Canvas element), and adds some click handlers on mouse up/down/move. Whenever the mouse is moved, a line between the last position and the current one is drawn, and a 1px blur applied to smooth out the aliasing in the image.\n\nYou might notice that we're also calling the function `registerPaletteElements`. This adds a click handler to each child element of the passed in Id (the palette elements). When they are clicked, the active colour is set to the background colour of the clicked palette.\n\nThis means we can add and remove colours to our drawable canvas at will.\n\n### Touch support\n\nThe canvas also has touch support - we have to do a little bit of maths to make sure we're using the correct x and y coordinates in our canvas to support both mouse and touch. Multi-touch isn't supported.\n\n```js\ngetLocationFrom(e) {\n  const location = { x: 0, y: 0 };\n\n  if (e.constructor.name === \"TouchEvent\") {\n      const bounds = e.target.getBoundingClientRect();\n      const touch = e.targetTouches[0];\n\n      location.x = touch.clientX - bounds.left;\n      location.y = touch.clientY - bounds.top;\n  } else {\n      location.x = e.offsetX;\n      location.y = e.offsetY;\n  }\n\n  return location;\n}\n```\n\nThis function is used to work out exactly where the player is drawing on our canvas - using either the mouse position, or the position of the first touch event.\n\n# Recap\n\nWe've spoken at length about how the core pieces of this game hang together.\n\nIf you want a deeper understanding, the code is all here, and you can run it locally by pulling this repo, and executing `npm run start`, once you've added API keys for Ably, and Azure Blob Storage into the `/api/local.settings.json` file.\n\n# Running on your machine\n\nWhile this whole application runs inside a browser, we need some kind of backend to keep our `Ably API key` safe. The running version of this app is hosted on `Azure Static Web Apps (preview)` and provides us a `serverless` function that we can use to implement [Ably Token Authentication](https://www.ably.io/documentation/core-features/authentication#token-authentication).\n\nWe need to keep the `Ably API key` on the server side, so people can't grab it and eat up your usage quota. The client side SDK knows how to request a temporary key from an API call, we just need something to host it. In the `api` directory, there's code for an `Azure Functions` API that implements this `Token Authentication` behaviour.\n\n`Azure Static Web Apps` automatically hosts this API for us, because there are a few .json files in the right places that it's looking for and understands. To have this same experience locally, we'll need to use the [Azure Functions Core Tools](https://www.npmjs.com/package/azure-functions-core-tools).\n\n## Local dev pre-requirements\n\nWe use [live-server](https://www.npmjs.com/package/live-server) to serve our static files, and Azure functions for interactivity\n\n```bash\nnpm install -g live-server\nnpm install -g azure-functions-core-tools\n```\n\nTo set your API key for local development:\n\n```bash\ncd api\nfunc settings add ABLY_API_KEY Your-Ably-Api-Key\n```\n\nRunning this command will encrypt your API key into the file `/api/local.settings.json`. You don't need to check it in to source control, even if you do, it won't be usable on another machine.\n\nNext you need to [Create an Azure Blob Storage Account](https://azure.microsoft.com/en-gb/services/storage/blobs/?\u0026OCID=AID2100128_SEM_XqK-bwAAAfw50RTJ:20200812092318:s\u0026msclkid=3cef80961050146d866fdfa5a5531dc2\u0026ef_id=XqK-bwAAAfw50RTJ:20200812092318:s\u0026dclid=CLKMy-irlesCFTwWBgAdZdYGKA), create a container, a storage bucket, and generate an API key.\n\nPlease refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal) for this. Once you know all your Azure configuration, you can either edit your `local.settings.json` file by hand, or add to it using the `func` command as demonstrated above. You'll need to add the following keys:\n\n```bash\nAZURE_ACCOUNT\nAZURE_CONTAINERNAME\nAZURE_BLOBSTORAGE\nAZURE_KEY\n```\n\nHere is an example of an unencrypted local.settings.json file:\n\n```js\n{\n  \"IsEncrypted\": false,\n  \"Values\": {\n    \"ABLY_API_KEY\": \"ably-api-key-here\",\n    \"AZURE_ACCOUNT\": \"scrawlimages\",\n    \"AZURE_CONTAINERNAME\": \"gameimages\",\n    \"AZURE_BLOBSTORAGE\": \"https://scrawlimages.blob.core.windows.net\",\n    \"AZURE_KEY\": \"some-azure-access-token-from-the-storage-account\",\n    \"FUNCTIONS_WORKER_RUNTIME\": \"node\"\n  },\n  \"ConnectionStrings\": {}\n}\n```\n\n## How to run for local dev\n\nTo run the Depict-It app, first install the npm modules and the modules in the api directory, then back in the root directory, run the start script:\n\n```bash\nnpm install\ncd api\nnpm install\ncd ../\nnpm run start\n```\n\n# Hosting on Azure\n\nWe're hosting this project as an Azure Static Web App - and the deployment information is in [hosting.md](hosting.md).\n\n# Helpful Resources\n\n* [Everything you need to know about Publish/Subscribe](https://www.ably.io/concepts/pub-sub)\n* [Ably Channels](https://www.ably.io/documentation/realtime/channels)\n* [Dedicated Server vs Peer to Peer connections](https://www.reddit.com/r/explainlikeimfive/comments/6ue3jk/eli5_how_do_peertopeer_connection_in_games_work/)\n* [Vue.js Guide](https://vuejs.org/v2/guide/)\n* [Azure Functions Guide](https://docs.microsoft.com/en-us/azure/azure-functions/)\n* [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/overview)\n* [Azure Blob Storage](https://docs.microsoft.com/en-us/azure/storage/blobs/)\n* [Progressive Web Apps](https://www.smashingmagazine.com/2016/08/a-beginners-guide-to-progressive-web-apps/)\n\n[![Ably logo](https://static.ably.dev/badge-black.svg?depict-it)](https://ably.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably-labs%2Fdepict-it","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fably-labs%2Fdepict-it","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fably-labs%2Fdepict-it/lists"}