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;
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;
The cron function will work even if the parameter is not specified.