Remastering Achievements

From “Let’s add new Achievements” to “Let’s rebuild the Achievement system”

We like rewarding our gamers with many different Achievements they obtain through the GameBay system. But, diversity and abundance often comes with a cost. We realized our old Achievement subsystem wasn’t strong enough to support the amount of Achievements we would like and decided to rebuild it from scratch, with ultimate goal of making it extremely adaptive and infinitely extendable. To do that, one has to be patient and innovative.
How does one build a fundamentally different wheel with old wheels staring at it? How does one forget about the past when it is so entwined in the present?

Intro

Starting small, the first step is to redefine how a single Achievement would be stored and how to differentiate it from others. For example, an Achievement for level up is fundamentally different from Achievement for playing PUBG. This differentiation can be simply solved by introducing a type to Achievement. We are now equipped with 7 different types of Achievements: TrackingAchievement that handles the Client’s playtime and level ups, GameAchievement that handles Client’s Games, ItemAchievement that handles Client’s bought Items, AchievementAchievement that handles Client’s previously won Achievements, ManualAchievement that is awarded manually by our employees, ApiAchievement that communicates with third-party APIs and PeriodicAchievement that handles Periodic Achievement awards.

Although these 7 types are fundamentally different, this is not the end of diversity. Consider the two GameAchievement Achievements:

  • Within 30 days, play some Game for 10 hours
  • Play Fortnite 5 days in a row for 1 hour each day

We now have to differentiate Achievements within each type. Figuring out what is the minimum number of variables required to enable the creation of every Achievement we can think of takes a lot of time and testing. At last, we end up with just 5 variables that, given their possible distinct forms, give us at least 25 (32) logically un-equivalent sets and enable theoretically infinite number of different Achievements for each type. For example, the two Achievements we mentioned above end up living in different worlds because they are not logically equivalent. Example of logically equivalent Achievements would be: “within 10 days, play CSGO 3 days (any), 2 hours each day” and “within 30 days, play UT 7 days (any), 1 hour each day”.

Deciding who gets

Now that we have set all the storage related logic, we can focus on the algorithm that decides who can get which Achievement. Having 7 different types of Achievements naturally leads to the conclusion that each of them has to have its own implementation of this algorithm. This is true, but there is a slight catch we will get to a little bit later.

Here, we will be covering Game, Item and Achievement Achievement types.

Let us first think of GameAchievement. The variables it has are:

  • days
  • days_consecutive
  • games
  • game_durations
  • games_unique
Attribute days is used to define how many days a Client has to pass this Achievement’s conditions and similarly, days_consecutive tells us the same thing but for consecutive days. Games tells us which Games can be played for this Achievement and game_durations tells us for how long. Attribute games_unique tells us how many Games from the variable Games are necessary.

With these 5 variables, we can define in our mind all the combination but the problem is now how to implement this neatly and give meaning to some of those variables when they are not defined. The first approach can be to have a lot of if-else statements covering the cases when some variables are defined and not, but this looks too messy and amateur-like for our tastes. The smart thing to do is to assign a meaning to undefined variables. So we say that days and days_consecutive are 1 (and mean all the data from desired period, regardless of days in this period) when Achievement manager doesn’t set them, empty games variable means all Games, empty game_durations means 1 second (at least some playtime) and empty games_unique means the count of all Games from games variables. Now we can approach the problem without having the main algorithm wonder what does it mean if it, for example, got no Games.

The main machinery checks whether a Client can get the Achievement, given the Client’s relevant data and Achievement variables. What is the Client’s relevant data? For GameAchievement, this should be the list of dates, each of which is a list of Games the Client has played that day and the corresponding durations. Suppose we are handed this data by some GameGod entity and let us proceed with the algorithm.

The best approach is to simply loop through dates from Client’s data and count how many consecutive or regular days comply with Achievement’s conditions (variables). To comply with conditions, the games_unique number of Games that are both in Achievement’s Games variable and Client’s data must all be of at least duration defined in game_durations. We can break the loop as soon as we have the proof that Client can get this Achievement.

We are done. Are we done? Let us look at some examples.

What about this Achievement: “in 10 days, play PUBG and/or Fortnite for total of 10 hours”. This means the Client can play PUBG 2 hours and Fortnite 8 hours, or can play just PUBG for 10 hours. How is this covered by Achievement’s 5 variables? Sure, days and days_consecutive are considered to be 1 and all data from 10 days is combined as data for 1 day. Sure, Games variable contains PUBG and Fortnite. Sure, games_unique is 1. But what with the game_durations? 5 + 5? 1 + 9? It must be just 10, says the everyday normal guy. And he is almost right. But our algorithm is not an everyday normal guy, and “almost” is not sufficient, as we will see with our next 2 examples.

Consider now the similar Achievement: “in 10 days, play PUBG and Fortnite for 10 hours each”. This subtle difference in language that is easily understandable for humans, but hard for computers must also be somehow solved with just our 5 variables. The 2 Achievements mentioned differ only in the game_durations variable. The second one has game_durations = 10 + 10. Therefore, if algorithm notices that game_durations is just one number and Games variable contains multiple Games, it assumes that this means the sum of their durations. Otherwise, If game_durations has enough values as there are Games in Games variable, algorithm knows it is dealing with and Achievement like the second one mentioned above. It considers each duration for corresponding Game.

Keen reader might have noticed that there is something that might mess with the algorithm, as described so far; yet another interesting, fundamentally different, ItemAchievement: “in 30 days, play PUBG or Fortnite or Apex Legends, but at least 2 of those, one for 2 hours and other for 1 hour”. It’s a little “pushing to the limits” example, but it is a valid Achievement. It would have games_unique = 2, game_durations = 2 + 1 and Games = PUBG, Fortnite, Apex Legends. The algorithm now cannot assume any above described case, and deals with this case by sorting the durations in descending order and finding the right ones.

Catchy unification

We mentioned a slight catch at the beginning of last paragraph and reader may have noticed we didn’t talk about ItemAchievement and AchievementAchievement types, although it was promised. There lies the unification catch.

The ItemAchievement (how many of some Items were bought during some period), ItemAchievement (how many seconds of some Game was played during some period) and AchievementAchievement (how many of some Achievements were won during some period) are very similar in their roots and algorithm described above can deal with that with its unification logic, simplifying the code base significantly. Of course, much work had to be done in data extraction to feed the algorithm with the right data and not confuse it.

To physical infinity and beyond

To wrap it up, let us observe some numbers. What have we achieved with this new Achievement system and how many possible Achievements can it now support?

We know that there is now 7 fundamentally different types of Achievements and within each there is at least 25 (32) fundamentally different Achievements. Within each of those, there is almost infinite amount of possible Achievements to define. But, nothing is really infinite in our observable universe. Infinity is just a concept that deals with abstract continuity and provides foundation for analytic computation. We know there is no more than 1080 atoms in the observable universe. That is 1 followed by 80 zeros; a 100 quinvigintillions; a 100 million billion billion billion billion billion billion billion billions.

How about the number of possible Achievement to define within our system? Let us examine just the GameAchievement type. Supposing some gaming center/arena offers just 20 different Games and has been operating for 1 year, there is at least a staggering 10109 possible GameAchievement Achievements to define. If one could in just 1 second create as many different mentioned Achievements as there is atoms in the observable universe, and do this procedure for 100 billion years, only 0.0000003% of all the different possibilities would be made.