In this blog post we are going to write down some thoughts regarding a game leaderboard implementation. They are written as question and answer pairs and cover the leaderboard design process as well as its technical implementation on a high level.
What is a leaderboard?
Leaderboards are a necessary asset for many types of games. A leaderboard provides a means to reward the best users and increase the game’s replay value by allowing the players to compete. It is defined as a collection of high scores achieved in a game session during a specific time segment in a specific portion of game for a specific set of users.
- ‘time segment’ relates to the lifetime of the leaderboard. Is it permanent or resets every day/week/month?
- ‘game portion’ relates to the portion/segment of the game the score was achieved in. Is the score relevant to the entire game or in just one of its levels? Is the score relevant to a single round of gameplay (single session) or multiple ones?
- ‘specific set of users’ relates to the ‘locality’ of the users and means that the leaderboard may contain scores for users that are in single machine or in a specific region (e.g. Europe) or worldwide
What purpose does the leaderboard serve?
In a single player game, the leaderboard would ‘push’ the user to compete against herself, whereas in a multiplayer game (either one played over the Internet or just an arcade – local – system that can be played by many players at different times) the leaderboard would allow a player to compare her score against others (either on the local system or within a region or worldwide).
How can we represent a leaderboard programmatically?
We can represent a leaderboard as an array (or list) of Score objects/structures. This list will be immutable (as soon as we insert a score we cannot remove it from the list) and it can be queried and sorted.
How would a game client access the leaderboard?
There are many ways we could use to implement the access leaderboard. A modern way would be to design the leaderboard operations (create a new score, get all user’s scores, get top scores for today etc.) as a RESTful API server. All operations should be well documented and easily accessible, preferably using an authentication mechanism.
What is contained in a Score object?
The Score object is comprised of a unique ID, a score value, the user ID, and the date/time structure which the score was awarded to the gamer. Moreover, it may contain additional details like the country of the player, the game level that this score was awarded, etc.
As the leaderboard list itself, the Score object is immutable. This is because the score value correlates to a specific point in time and game segment that the score was awarded to the gamer. As we cannot go back in time (yet!), so the Score object cannot be changed. Moreover, as it is always the case when storing date/time objects from users in different timezones, you are encouraged to store scores’ date/time in an absolute manner (e.g. using UTC time). The transformation from UTC into player’s timezone can easily be done on the player’s game client.
The Score value itself is usually an integer. In this case, the score with the higher value is better than the one with the lower value. In some cases, the opposite might be true (such as when we wish to store the number of seconds it took the player to clear a game level). You will want to take this into consideration when sorting your leaderboard to present the best scores.
Should we use a relational database or a NoSQL one?
As in all cases where this argument pops-up: it depends. If we implement a leaderboards mechanism using a NoSQL database, we should have de-normalization of the data, e.g. the User object could hold some Scores itself (like the top user’s score and/or the latest ones). If we use a relational database, we should apply normalization (separate Users and Scores table). Of course, we would have to use JOINs to query Scores and Users data (and pay the relevant performance penalty). However, aggregate functions (count, average, etc.) would probably be faster on a relational database than a NoSQL one. You can always use a key-value pair database (like Redis) for a leaderboard, the pros of it are mostly alike to using a NoSQL database.
What types of leaderboards are available in various games?
In most video games we usually encounter one of the following options or the combination of some of them.
1) A global leaderboard that contains username, score value, date and maybe other details (e.g. country, level of the game etc.). This leaderboard contains only one entry per player, with her best score.
2) A single leaderboard per gamer that contains all her scores along with the date/time that they were accomplished.
3) Leaderboards that are generated after a game session, e.g. after a Deathmatch on a PvP FPS game (Player vs Player – First Person Shooter). This is the norm with multiplayer games played over the Internet. This leaderboard contains scores regarding the current game session, they may or may not be archived
4) A global leaderboard that expires (e.g. once per day or once per week). This is just a simple variation of (1) and it allows to get the top players per day, per week or per a specifically designated season (e.g. Christmas). Programmatically, this can easily be implemented with a timed function that drops and recreates the leaderboard from scratch. ‘Past’ leaderboards can optionally be stored and queried on demand.
5) A game may have many levels. In this case, the game designer may opt to create one leaderboard per level. Again, this a slight variation of (1), or more strictly speaking, many instances of (1).
Should a player have easy access to all her scores, to the top ones in the leaderboard or just her best one?
That’s a question which the leaderboard designer should answer as early as possible during the leaderboard creation. Programmatically,
- If we want to just keep the best score, we could store it as a simple property/column on the user object
- If we want to keep all the scores of the user, we could keep them as attachments to the user object, in a case of a NoSQL database or as separate rows. In case of a relational database we could store them as rows in the Scores table along with their corresponding UserID.
- If we want to keep scores for a specific date/game session/anything else, we could use different tables/collections which could have specific lifetime (e.g. refreshed once per day or week)
How should we implement a leaderboard for each level of the game?
We could store a score array/list for each level of the game. In a NoSQL database, we could have a single document per level with all these details. In a relational one, we could have a ScoresPerLevel table with ScoreID and LevelID as foreign keys.
Thanks for reading this far! Feel free to comment out anything you think I omitted.