Gamification: The implementation

  • Author Dalibor Karlović
  • Publication date Dec 26, 2015
  • Reading length 11 min

For quite a long time, gamification has been a hot topic. If things said at tech conferences and boardrooms across the world were tweets, I’m sure #gamification would be trending quite often.

Play where you used to work

The premise is simple: take your willingness to spend your own resources (like money, time, and effort) and drive for mastery, which we all willingly dispense while playing games and somehow harness it toward a goal more to your liking.

While people are ready to pay quite a lot of money to endlessly grind in their favorite game while weeks, months, years pass them by,

Grinding is a term used in video gaming to describe the process of engaging in repetitive tasks.

Just try telling them to place proper numbers in this thing called Excel, play with the graphs and fonts for 8 hours a day, five days a week and suddenly you’ve got "an employee" who expects "a salary, health benefits" and "time off." "Time off what," you ask, "having a sweet computer all for yourself and making these awesome 3D-accelerated graphs?" Turns out, yes.

Apparently, millions of people are willing to work on your farm without pay (or even pay you for the experience), as long as the said farm is on Facebook? What’s the difference, besides lowered proximity to actual manure?

While proximity to manure does indeed have a certain significance, it’s not the whole story.

It’s in the game

As the preceding title (and, quite frankly, the whole article up to this point—should have had a "spoiler alert" somewhere in there) might have clued you in, the "game" part is important. People like playing games. No, scratch that: people LOVE ❤️ playing games. People love playing (with) stuff that just sort of looks like a game, without even thinking about it.

square pavement
square pavement, credit RGBStock

As a kid (before new-fangled inventions like cell phones or friends), have you ever tried making your trip to and from school less boring by only stepping on the pavement blocks, but not on the border separating them?

For some reason, it made time and the road go by quicker, you got to your destination not bored out of your little mind. If so, you’ve basically gamified your trip home all by yourself, without Wikipedia articles, books, APIs or even any kind of framework.

Gamification is the application of game-design elements and game principles in non-game contexts.

The key part is "in non-game context"; you don’t need to make people play "in game context", they do that on their own if the game is appealing. You do, however, want to align their efforts with your desired outcome, be it profit, more users, higher quality users, more engagement / content, higher quality engagement / content, you name it. It obviously makes the process easier if your goals are more or less aligned with the users’.

Our stab at gamification

animated stabbing motion from Futurama series
Stand back, I gotta practice my stabbin’!, credit Fox (I guess?)

Our desired outcome was simple, although quite sinister: we wanted good stuff to happen (to our platform and our users) more often and sooner. I won’t describe the whole process as details selecting the elements we took into consideration for gamification, the procedures, copy-writing, design process, etc are out of scope for this article.

What we’re interested here is our implementation. Right away, we decided not to implement it directly as the part of the app but instead chose the micro-service route, mostly to have an opportunity to use an off-the-shelf solution. We looked around and found some interesting projects. Unfortunately, we couldn’t get any of them to work well (all written in Python, we’re a PHP shop) which deterred us from going down that path as we weren’t really comfortable pushing a technology we don’t thoroughly understand to production.

Not using an existing project does not mean we can’t steal use its documentation for inspiration.

We’re left with implementing it ourselves.

Game engine at a glance

To implement a "game engine" (GE), first we need to know what "a game" means to us. In our context, it consists of two parts:

  1. current game state: > Currently, the user has 6 potatoes
  2. changes between previous state and current state: > User has harvested 1 potato and now has 7 potatoes, is that relevant to us?

By feature, we can separate the system into two parts: event processing and game evaluation.

Event processing: $potatoes++ 🧑‍🌾

Event processing is looking at what the users are doing, inspecting that data by various aspects (…) and assigning the outcome to one of GE’s predetermined variables, triggering the game evaluation.

In our example, the base system (which we’re gamifying) is inspecting every action which would lead to the change of the current number of potatoes and, when the number changes, notifies the GE on the change. GE stores the latest state.

Game evaluation: $potatoes >= 10? 🙌

Game evaluation begins on the GE after at least one variable has been altered: we check if the change has affected the current state of the game and, if it has, we change the state (award the user with the achievement), notifying the main system of the change.

In our example, GE now knows the current state for $potatoes has changed and begins the evaluation stage:

  1. is there an achievement which monitors the $potatoes variable?
  2. if so, is the fact that currently $potatoes == 10 relevant?
  3. if so, award the related achievement.

That’s really everything there is to it. With these basic features (plus minor tweaks), we can implement all sorts of games.

Data model and key concepts

We’ll go through couple of key aspects of the implementation, starting with the DB schema.

Database schema

As every image is said to be worth a thousand words, here’s an image of our DB schema, followed by a few thousand words explaining it.

database schema

Variables

Variables are used to define which aspects of current state we’re interested in. We have the ability to optionally set the grouping mechanism. Grouping will determine users_variables_values.group_key which is always an integer, even for dates.

For example:

  • $potatoes
    Total number of potatoes a user has
    (no grouping)
  • $potatoes_by_day
    Total number of potatoes a user has had on a specific day
    (grouping by time, a day)
  • $potatoes_by_type
    Total number of specific types of potatoes the user has
    (grouping by custom property)

Achievements

Achievements are used to specify the game rules, as follows:

  • Achievement: Potato lover
    is the end target, by being awarded achievements the user "wins." Each achievement has one or more achievement levels. User is not awarded an achievement, but an achievement level, even if the achievement is a single-level achievement.
  • Achievement levels: Potato enthusiast, Potato lover, Potato master
    gives us the ability to segment an achievement down to milestones. You can only win level 2 if you’ve already won level 1.
level $potatoes >= X name
1 10 Potato Enthusiast
2 100 Potato Lover
3 10.000 Potato Master
  • Achievement goal: $potatoes_by_day >= 2, INTERVAL 7 DAY
    is a single test of the current game (variable) state, differing in evaluation types.
    • Each goal defines which variable it monitors, the evaluation type it will use, for which time interval, etc.
    • Each level has one or more achievement goals and all goals must be satisfied for the level to be awarded.

Achievement goal evaluation types

The GE defines three distinct types of evaluation it can use on a variable. The type availability depends on the fact if the variable is grouped and by which property.

  1. comparison
    the simplest form, take the variable value and compare it against some preset value
    (that is, $potatoes >= 10, total number of potatoes is greater than 10)
  2. interval
    take the variable value sum for a certain time interval and then apply the simple comparison
    (that is, $potatoes >= 10, INTERVAL 2 WEEK, total number of potatoes in the last two weeks is greater than or equal to 10)
  3. streak
    take a certain time interval and apply the comparison for each interval period. The total is not considered.
    (that is, $potatoes >= 2, INTERVAL 3 DAY, for the last three days, number of potatoes is greater than or equal to 2 each day. If the user skips a day, it cannot be "fixed" by doing twice as much the next day, this goal will still fail)

Users, their variable values & achievements levels

Lastly, we come to the main protagonist of our story, The User.

  • users
    as our user system is managed remotely, the users catalog is only a list of valid IDs, used for referential integrity.
  • variables values
    this is where we keep the complete current state. Group key is determined by inspecting the variable’s grouping mechanism (if specified, equals to zero if not). If the grouping type is temporal in nature (which it most often is), we look up the proper value in the calendar table, inspired by the excellent Calendar table concept.
  • achievement level
    as stated earlier, we do not assign the user an achievement but instead an achievement level, even for single-level achievements.

Assorted implementation details

  • we’re using Symfony, with DoctrineBundle, FOSRestBundle, SonataAdminBundle (for game administration), also using Gearman with GearmanBundle for asynchronous game evaluation processing
  • we’ve done quite a bit of automated testing on this app. The main reason is: it’s terrible to debug otherwise: its output is just a bunch of weird numbers in a database. Yuck.
  • example usage: as the GE was implemented as a micro-service, all communication is done via a REST-style API.
# increment the $potatoes variable by 1 for user 123
-v -X PATCH -H"Content-Type: application/json" https://example.com/api/users/123/variables/potatoes -d'[{"op": "replace","path": "/value","value": "+1"}]'

# decrement the $potatoes variable by 1 for user 123
-v -X PATCH -H"Content-Type: application/json" https://example.com/api/users/123/variables/potatoes -d'[{"op": "replace","path": "/value","value": "-1"}]'

# set the $potatoes variable to exactly 11 for user 123
-v -X PATCH -H"Content-Type: application/json" https://example.com/api/users/123/variables/potatoes -d'[{"op": "replace","path": "/value","value": "11"}]'

# fetch the game progress for user 123 (with complete stats & progress breakdown per achievement level)
-v -X PATCH -H"Accept: application/json" https://example.com/api/users/123/achievements

So… what’s next

The system has been in production for quite a while, and seeing great results, there are more KPIs and FTWs than you can swing a stick at.

Managed games vs unmanaged games

As we’ve implemented it, the GE does not know about the nature of events being processed, semantics of variables, etc. This means that the system playing the game has quite a bit of work to do in order for it to work. I have no idea if this concept has an official name, so I’ve called this "an unmanaged game."

  • unmanaged game
    the system playing is doing all the calculations on current variable value and sending the calculation result to the GE
  • managed game
    the system playing is just forwarding the user-generated events (and the context) happening to the GE which, in turn, persists the events, calculates and updates all the related variable values, after which evaluation is run just like in an unmanaged game.

The managed version means much easier to implement the game inside your own system, and we would get some sort of sanity checking, as you could recalculate all the variables from scratch and verify their current values. Also, introducing a new variable retroactively would be possible (currently, we’re using a sort of "primer script" which puts the system in the desired state before introducing a new variable).

What the game engine is missing

  • support for multiple games running simultaneously
  • support for managed games
  • being an open source project, as it would help at least one of you two who managed to read up until this point. We’re looking into this.

I hope this has at least given you a few ideas for implementing this yourself if not source code to copy/paste.

Happy gaming.