Concepts

The philosophy of Scoreboard:

  • one scoreboard service
  • many clients using its api
  • clients specify how the scores are calculated
  • data for scoreboards / leaderboards provided in common formats

It a Dynamic Calculator as a Service, essentially.

Users

Scoreboard needs to know who should have access to what, so Users are Scoreboard’s form of access control. Anyone can log in with OpenId, and Administrators of scoreboards can grant access.

Administrators can also generate application tokens, which can be used to login in the same way a User would.

There are three classes of users:

  • Readers: can look at computed leaderboard data
  • Writers: (all of the above), can see Point history, can append to Points.
  • Administrators: (all of the above), can rewrite history, add Players and Groups.

Players

Users have nothing to do with the data for your Scoreboard, but Players do. A Player is the atomic entity for scoring: that is, it’s the smallest thing you could possibly care about aggregating scores by.

It may be the case that Players map 1:1 to people, but if you are running a Trivia Night, your Player may map to a table of people, since you don’t care about breaking down who at the table answered the question. Equivalently, you could also have multiple Players for a person, but I couldn’t think of a plausible example for that…

Groups

If Players are the atomic unit, Groups are the molecular unit. Groups are simply sets of Players. There is no constraint past this: Groups do not need to be disjoint (if you need this, you will need to enforce it yourself).

To give an example, if you were running a school carnival, you would want each student to be a Player, but you could have Groups for each school house, and for each age group.

Groups can be made up of Players and other Groups. Scoreboard will try to catch any created circular dependencies and return an error, but we reserve the right to simply crash any operation if you try to do this. Yeah, don’t do that.

Games, Question and Points

You may want to run multiple events using the same Players and Groups, which are represented with Games. Be aware that all Games are independent, so you should use one Game per time period: don’t try to split an event up into multiple Games (just use multiple Leaderboards to represent the different parts).

Each Game is made up of multiple Questions. When a Player answers a Question (in your application), you will do some check to see if it’s a correct answer or not, and create a Point for that Player and Question.

You can have multiple Points for a (Question, Player) pair, but Points can also have mutations. To understand why both of these exist, imagine you’re running a Trivia Night, and this happens:

  1. Team 1 send a wrong answer to Marker 1
  2. Marker 1 accidentally marks it correct
  3. Team 1 realise they made a mistake and send the correct answer to Marker 2
  4. Marker 2 sees that it’s already correct, and leaves it
  5. Marker 1 notices their mistake and marks it incorrect

In order to avoid this situation, you can use this model:

  1. Team 1 send a wrong answer to Marker 1
  2. Marker 1 creates a Point and marks it correct
  3. Team 1 send the correct answer to Marker 2
  4. Marker 2 creates a Point and marks it correct
  5. Marker 1 notices their mistake and mark the original Point as incorrect

Each Point has a creation timestamp, and each mutation also has a timestamp.

Scoring Functions

However, this model means that we need to have some way of evaluating the score for a Player for a Question, and this is done by a Point Scorer, which takes a Question, a collection of Points for the Question and the current game time (which allows for “decaying” scores), and returns some value.

A typical Point Scorer would be to look at the final mutation for all their answers, and choose the maximum value:

def point_scorer(question, points, timestamp):
    return max(
        point.mutations[-1].value
        for point in points
    )

In addition to Point Scorers, we also need some way of aggregating the scores for all Questions for a Player, which is a Player Scorer. This takes a collection of (Question, Score) pairs and the current game timestamp, and returns some value. Typically, you would just sum up the values.

def player_scorer(question_scores, timestamp):
    return sum(
        score
        for question, score in question_scores
    )

Leaderboards

Using the service would be pretty pointless unless you could get your data out in some useful form, which is where Leaderboards come in (not called Scoreboards, to avoid confusion with the name of the service).

Leaderboards specify a set of Questions and Players, apply the Point Scorer for each (Question, Player) pair in those sets, applies the Player Scorer for those scores, sorts the result (either ascending or descending), and optionally limits the number of displayed results, finally exporting this data as JSON or in a HTML table.

If you were paying attention earlier (of course you were!), you would have noticed that the scoring functions have a timestamp, which leads us to the final feature of Leaderboards: they can show you different points in time. For example, if you wanted to “freeze” your scoreboard 20 minutes before the end of the competition, you could do that.

However, this also requires you to write your Scoring Functions so that they respect the current game timestamp correctly: you will likely want to have it ignore Points with a later creation timestamp, but still consider mutations with later timestamps so that mistakes in the scoring can be fixed.

Project Versions

Table Of Contents

Previous topic

Welcome to Scoreboard’s documentation!

This Page