Getting Started

Usage of GitHub Issues:

Agile ArtifactGitHub feature
User StoryIssue
TaskIssue Task List
EpicIssue labels
BacklogIssue list, Project Todo column
SprintMilestone
Agile BoardProject Boards

Definitions

TODO

  • game
  • game state
  • app, app instance
  • clock
  • participate
  • parameter
  • qr code action

QR Codes

This chapter provides all technical details on how the QR codes are encoded.

The following assumptions are made:

  • New: QR codes only contain a reference to the actual actions
  • Every QR code can trigger >= 1 actions, i.e. one code may trigger multiple actions
  • Actions from a QR code are processed in order

Encoding of QR Codes

This document specifies the contents of our QR codes.

Types of QR codes

There are two types of QR codes:

  • Onboarding QR code
  • Game action QR codes

Onboarding QR code

This QR code is scanned by the user using the built-in Camera app, Chrome app or an arbitrary QR code scanner app.

The code contains an URL with a game id to our app. When scanned, our app opens.

Example contents

https://abc-dpt.netlify.app/start/cultures-interactive-summer-school-2021

Game action QR codes

These QR codes are scanned during the game by the users.

They contain an URL with an action reference to our app. When scanned within our app, the action is processed.

As it contains an URL to our app, it still can be processed if scanned using another qr code scanner by mistake.

The app then requests what this QR code actually does and eventually executes it using the API.

Example contents

https://abc-dpt.netlify.app/code/pJ6sLbnIDDlthmj8OWu8j

Available QR Code Actions

This document specifies which QR code actions are available.

QR code actions remain universal across games and thus contain no game id.

Types of QR code actions

There are four types of actions:

  • Change Parameter
  • Get Information
  • Send Message
  • Set Character

To be considered in the future:

  • Polls

Change Parameter

This action updates a game state parameter.

Example

{
  "type": "changeParameter",
  "parameter": "foodSupply",
  "add": 7
}

Get Information

This action shows an information to the user.

Example

{
  "type": "getInformation"
  // TODO what kind of information and how is it served?
}

Send Message

This action sends a message to all users.

Example

{
  "type": "sendMessage",
  "message": "Hallo Welt!"
}

Set Character

This action sets the bonus/malus for a player based on a character.

Example

{
  "type": "setCharacter",
  "character": "engineer"
  // TODO define bonus/malus
}

Game Synchronization

This chapter provides all technical details on how the game state is computed and synchronized.

The app's main implementation challenge is that the game state should be two-way synchronized between all app instances: Every instance should be able to display the current state and globally modify it by scanning a QR code.

A HTTP API is used as a simple, centralized, completely synchronized solution:

  • Every app instance frequently requests the current game and clock state from the HTTP API
  • QR-Scan: When scanning a valid QR code, its reference is looked up using the HTTP API. If valid, the server updates its state.
  • Current game state: Every app instance frequently requests the current game and clock state using the HTTP API. They may automatically interpolate the game state between two transmissions.
  • Clock synchronization: By frequently transmitting the current game state, no explicit clock synchronization is needed. However, whether the game is currently running at its speed can be fetched using the HTTP API for the game state interpolation.
  • Connection loss, offline functionality: QR codes can only be submitted online. On loss of connection, app instances automatically try to call the HTTP API when online again.
  • Real-Time updates: It should be sufficient if app instances only update to the actual state every ~30s or so. However, for a faster synchronization, a separate notification channel like a WebSocket could be used to notify clients to refetch the HTTP API data.

App implementation notes

This document is a collection of implementation notes for the app.

Changing the current game state through time

In order to continuously display game state updates to the user, e.g. a ticking countdown, the app needs to interpolate state values between two fetches of the current state from server. This can be implemented using the following tick method:

  • The method updates the local, intermediate game state
  • It is frequently called, e.g. on every animation frame

On every execution, the following algorithm is executed (pseudocode):

lastUpdated = now()

def tick:
  current = now()

  if gameIsPaused:
    # game is paused, nothing can be done
    return

  if parameters[parameters <= 0]:
    # a game parameter is already zero which means game over. nothing can be done
    return

  for param in parameters:
    # update each parameter to its next intermediate value
    delta = current - lastUpdated
    param.value = param.value + param.rate * delta

  lastUpdated = current

Server implementation notes

This document is a collection of implementation notes for the server.

TODO:

  • How the PubSub is efficiently implemented
  • How a log of all changeParameter messages is kept
  • How oneShot changeParameter messages are handled
  • ...

Changing the current parameter values through time

In order to continuously decrement the values of the parameter (according to clock speed and rate) the app needs to know how much time passed between to api calls. This can be implemented using the following tick method:

  • The method updates all parameter
  • It is called every time an api endpoint is requested

On every execution, the following algorithm is executed (pseudocode):

last_time = time.time()


def tick(game):
    if game.clock.state == ClockType.RUNNING:
        current_time = time.time()
        passed_time_ms = (current_time - last_time) * 1000

        # Prevent high-frequency polling from overwhelming the database
        # as well as an litte edge-case where the values appear to be "froozen"
        if passed_time_ms < 500:
            return

        passed_tick = passed_time_ms / game.clock.speed
        last_time = current_time

        for parameter in game.parameter.all():
        	# Since the database stores integer, rounding is needed
            parameter.value -= round(passed_tick * parameter.rate)
            parameter.save()

    else:
        # Clock is not runnig, take no action
        pass

Object relational mapping

This document gives you an high-level insight into the ORM powering the backend.

Players and Characters

At the heart of the ORM lays a Game object. It is used to differentiante between multiple game instances as well as being the "glue" inbetween the other objects. Every Game can have multiple Player objects (representing actual humans playing the game) while a Player can only be part of one Game. It is thereby a One-To-Many (n:1) relation. We did not take action to prevent humans from having multiple Player instances. Each Player has a Character (i.e. Pilot), while each Character posseses arbitrary amount of bonus and penalty. Its there by logical to have small, fixed set of Character-classes and form a Many-To-One (don't be confused! One-To-Many and MTO are the same thing, just from diffrent viewpoints) relationship to the Player, so that every Player has exactly one Character but every Character may have multiple Player. Since there can only be one and exactly one "Pilot"-Class in the game, the CharacterType attribute is set to be unique. In conjunction with the CharacterType-Enum, this achives the desired behavior.

Player and Character in the ORM

The Clock

Each Game needs a Clock which defines how fast-pacted the game is. Since one Clock will always belong to only one Game, a One-To-One relation is appropriate. The thought to "re-use" Clocks by making them One-To-Many is not correct. When one Clock is used by multiple Games, they would all run at the same speed (desired behavior), how ever, stopping/pausing one Game would then also stop all other Games. Which is not okay. One could argument to move the Clock into the Game-Object all together, how ever, this "feels" odd to the end user, when creating a new Game. This could be hidden by a nice UI but would need extra work. Using a OTO relation show the logical connection while maintaining a "pretty feeling" distance. But thats an argue about taste.

The clock in the ORM

Parameter

The Parameter are one the most essential parts of the app. Their value is the reason for certain action by the user and they can also be the reason for a Game-Over. By the nature of these Parameter, they all work the same and are only having a diffrent name. This is why the Enum ParameterType is used. Each Parameter belongs to one Game, thereby forming a Many-To-One relationship. It is possible to instanciate the same ParameterType multiple times. It has to be, to allow multiple Games simultaneously.

Parameter in the ORM