The best way to code the achievement system

I am thinking of a better way to develop an achievement system for use on my site. The database structure can be found in the Best way to talk about 3 or more consecutive entries , and this stream is really an extension for getting ideas from developers.

The problem that I encounter a lot of talk about the icon / achievement systems on this website is just all the talk and the lack of code. Where are the actual code implementation examples?

I propose here a design that I hope could contribute and hopefully create a good design for coding extensible achievement systems. I am not saying that this is the best, far from it, but it is a possible source block.

Please feel free to submit your ideas.




my idea of ​​system design

It seems that the general consensus is to create an "event-based system" - whenever a known event occurs, such as a message being created, deleted, etc. it calls a class of events, for example like this.

$event->trigger('POST_CREATED', array('id' => 8)); 

Then the event class finds out which icons β€œlisten” for this event, then it requires this file and creates an instance of this class, for example:

 require '/badges/' . $file; $badge = new $class; 

Then it raises a default event that passes data received by calling trigger ;

 $badge->default_event($data); 

badges

Then real magic happens. each icon has its own request / logic to determine if the icon should be marked. Each icon is listed in, for example, this format:

 class Badge_Name extends Badge { const _BADGE_500 = 'POST_500'; const _BADGE_300 = 'POST_300'; const _BADGE_100 = 'POST_100'; function get_user_post_count() { $escaped_user_id = mysql_real_escape_string($this->user_id); $r = mysql_query("SELECT COUNT(*) FROM posts WHERE userid='$escaped_user_id'"); if ($row = mysql_fetch_row($r)) { return $row[0]; } return 0; } function default_event($data) { $post_count = $this->get_user_post_count(); $this->try_award($post_count); } function try_award($post_count) { if ($post_count > 500) { $this->award(self::_BADGE_500); } else if ($post_count > 300) { $this->award(self::_BADGE_300); } else if ($post_count > 100) { $this->award(self::_BADGE_100); } } } 
Function

award comes from the extended Badge class, which basically checks if the user will already be assigned this badge, if not, update the dbbbge table. The icon class also searches for all the icons for the user and returns it to the array, etc. (Therefore, the icons can be displayed, for example, in the user profile).

how about when the system will be first implemented on an existing site?

There is also a cron job request that can be added to each icon. The reason for this is that when the badge system was first implemented and initialized, the badges that were supposed to be earned had not yet been awarded, since it was an event-based system. Thus, the CRON task is run on demand for each icon to reward everything that should be. For example, a CRON job for the above would look like this:

 class Badge_Name_Cron extends Badge_Name { function cron_job() { $r = mysql_query('SELECT COUNT(*) as post_count, user_id FROM posts'); while ($obj = mysql_fetch_object($r)) { $this->user_id = $obj->user_id; //make sure we're operating on the right user $this->try_award($obj->post_count); } } } 

As the cron class above extends the main icon class, it can reuse the boolean function try_award

The reason I create a specialized request for this is that we could "simulate" previous events, that is, go through each user entry and fire up an event class, for example $event->trigger() , that would be very slowly, especially for many icons. Therefore, we create an optimized query.

which user receives the reward? all about rewarding other event-based users

The function Badge class award acts on user_id - they will always receive a reward. By default, the icon is assigned to the person who REASON the event that occurred, that is, the session user ID (this is true for the default_event function, although the CRON job obviously goes through all users and gives it to individual users)

So, let's take an example, on a coding website, users send their coding record. The administrator then evaluates the entries and, upon completion, sends the results to the query page so that everyone can see. When this happens, the POSTED_RESULTS event is fired.

If you want to reward badges for users for all published entries, say, if they were ranked in the top five, you should use cron's work (although, of course, this will be updated for all users, and not just what the call results were published for)

If you want to target a more specific area for updating with the cron job, let's see if there is a way to add filtering parameters to the cron job object and get the cron_job function to use them. For example:

 class Badge_Top5 extends Badge { const _BADGE_NAME = 'top5'; function try_award($position) { if ($position <= 5) { $this->award(self::_BADGE_NAME); } } } class Badge_Top5_Cron extends Badge_Top5 { function cron_job($challenge_id = 0) { $where = ''; if ($challenge_id) { $escaped_challenge_id = mysql_real_escape_string($challenge_id); $where = "WHERE challenge_id = '$escaped_challenge_id'"; } $r = mysql_query("SELECT position, user_id FROM challenge_entries $where"); while ($obj = mysql_fetch_object($r)) { $this->user_id = $obj->user_id; //award the correct user! $this->try_award($obj->position); } } 

The cron function will work even if the parameter is not specified.

+82
database php design-patterns mysql
Nov 16 '10 at 9:26 a.m.
source share
3 answers

I implemented a reward system once in what you would call a document-oriented database (it was dirt for the players). Some highlights of my implementation translated into PHP and MySQL:

  • Each icon information is stored in user data. If you use MySQL, I would make sure that this data is in one record for each user in the database for performance.

  • Each time this person does something, the code runs the icon code with the specified flag, for example, the flag ("POST_MESSAGE").

  • A single event may also trigger a counter, such as a message count counter. increase_count ('POST_MESSAGE'). Here you can check (either with a hook or just pass the test in this method) that if the POST_MESSAGE counter is> 300, then you should get a reward for the icon, for example: flag ("300_POST").

  • In the flag method, I would put code to reward the badges. For example, if the 300_POST flag is sent, then the badge reward icon ("300_POST") should be called.

  • In the flag method, you should also be presented with the previous user flags. so you can say when the user has FIRST_COMMENT, FIRST_POST, FIRST_READ, you give out the badge ("NEW USER"), and when you get 100_COMMENT, 100_POST, 300_READ, you can give the badge ("EXPERIENCED_USER")

  • All of these flags and badges must be saved in some way. Use some way when you count flags as bits. If you want this to be stored efficiently, you consider them bits and use the following code: (Or you can just use the bare string "000000001111000" if you don't want this complexity.

 $achievments = 0; $bits = sprintf("%032b", $achievements); /* Set bit 10 */ $bits[10] = 1; $achievements = bindec($bits); print "Bits: $bits\n"; print "Achievements: $achievements\n"; /* Reload */ $bits = sprintf("%032b", $achievments); /* Set bit 5 */ $bits[5] = 1; $achievements = bindec($bits); print "Bits: $bits\n"; print "Achievements: $achievements\n"; 
  • A good way to store a document for a user is to use json and save user data in a single text column. Use json_encode and json_decode to store / retrieve data.

  • To track the activity of some user data that another user controls, add a data structure to the element and use counters as well. For example, count the countdown. Use the same method described above to reward the badges, but the update should, of course, go into the publication of users. (For example, an article read a 1000x icon).

+8
Nov 18 '10 at 18:00
source share

UserInfuser is an open source game platform that implements a badging / point service. You can check its API here: http://code.google.com/p/userinfuser/wiki/API_Documentation

I implemented it and tried to limit the number of functions. Here is the API for php client:

 class UserInfuser($account, $api_key) { public function get_user_data($user_id); public function update_user($user_id); public function award_badge($badge_id, $user_id); public function remove_badge($badge_id, $user_id); public function award_points($user_id, $points_awarded); public function award_badge_points($badge_id, $user_id, $points_awarded, $points_required); public function get_widget($user_id, $widget_type); } 

The end result is to show data in a meaningful way using widgets. These widgets include: trophy event, leaderboard, milestones, live notifications, rating and points.

The API implementation can be found here: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py

+2
Jun 15 '11 at 13:45
source share

Achievements can be burdensome, and even more so if you need to add them later if you don't have a well-formed Event class.

This goes into my technique for realizing achievements.

I like to break them down into categories first, and inside them there are levels of achievement. that is, the kills category in the game may have a reward of 1 for the first kill, 10 ten kills, 1000 thousand kills, etc.

Then to the spine of any good application, a class that processes your events. Again, I imagine a murder game; when a player kills something, everything happens. Murder is marked, etc., and it is best handled in a centralized place, for example, and an Events class that can send information to other places.

It fits perfectly into this place, that in the correct method, create an instance of the Achievements class and check it so that the player receives it.

Like creating an Achievements class, this is trivial, just checking the database for something to see if the player has as many kills as needed for the next achievement.

I like to store user achievements in BitField using Redis, but the same method can be used in MySQL. That is, you can save the player’s achievements as an int , and then and , which are int from the bit that you defined as this achievement, to find out if they have already received it. Thus, the database uses only one int column.

The disadvantage of this is that you have to organize them well and you probably need to make some comments in your code so that you remember what corresponds to 2 ^ 14. If your achievements are listed in their own table, you can just do 2 ^ pk, where pk is the primary key of the achievement table. This makes checking something like

 if(((2**$pk) & ($usersAchInt)) > 0){ // fire off the giveAchievement() event } 

Thus, you can add achievements later, and it will be good, just NEVER change the primary key of achievements already achieved.

0
Dec 22 '17 at 11:57
source share



All Articles