Getting Started
Usage of GitHub Issues:
| Agile Artifact | GitHub feature |
|---|---|
| User Story | Issue |
| Task | Issue Task List |
| Epic | Issue labels |
| Backlog | Issue list, Project Todo column |
| Sprint | Milestone |
| Agile Board | Project 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
changeParametermessages is kept - How oneShot
changeParametermessages 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.
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.
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.