web-dev-qa-db-fra.com

Meilleur moyen de coder le système Réalisations

Je pense à la meilleure façon de concevoir un système de réalisations à utiliser sur mon site. La structure de la base de données peut être trouvée à Le meilleur moyen de signaler 3 ou plusieurs enregistrements consécutifs manquants et ce fil est vraiment une extension pour obtenir les idées des développeurs.

Le problème que j'ai avec beaucoup de discussions sur les systèmes de badges/réalisations sur ce site Web est simplement que - c'est tout parler et pas de code. Où sont les exemples concrets d'implémentation de code?

Je propose ici un design auquel j'espère que les gens pourront contribuer et créer, espérons-le, un bon design pour coder des systèmes de réalisation extensibles. Je ne dis pas que c'est le meilleur, loin de là, mais c'est un bloc de départ possible.

S'il vous plaît n'hésitez pas à contribuer vos idées.


mon idée de conception de système

Il semble que le consensus général soit de créer un "système basé sur les événements" - chaque fois qu'un événement connu se produit, une publication est créée, supprimée, etc., elle appelle la classe d'événements de la même manière.

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

La classe d'événements découvre ensuite quels badges "écoutent" pour cet événement, puis requires ce fichier et crée une instance de cette classe, comme suit:

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

Il appelle ensuite l'événement par défaut transmettant les données reçues lorsque trigger a été appelé.

$badge->default_event($data);

les badges

C'est alors que la vraie magie se produit. chaque badge a sa propre requête/logique pour déterminer si un badge doit être attribué. Chaque badge est indiqué dans, par exemple, ce 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);
  }

 }
}

La fonction award provient d'une classe étendue Badge qui vérifie si l'utilisateur s'est déjà vu attribuer ce badge, sinon, il mettra à jour la table de la base de données des badges. La classe de badges s’occupe également de récupérer tous les badges pour un utilisateur et de les renvoyer dans un tableau, etc. (ainsi, les badges peuvent être affichés, par exemple, sur le profil utilisateur).

qu'en est-il lorsque le système est implémenté pour la première fois sur un site déjà actif?

Il existe également une requête "cron" qui peut être ajoutée à chaque badge. La raison en est que, lorsque le système de badges est tout d'abord implémenté et initié, les badges qui auraient déjà dû être gagnés ne sont pas encore attribués car il s'agit d'un système basé sur des événements. Ainsi, un travail CRON est exécuté à la demande pour chaque badge afin d’attribuer tout ce qui doit être fait. Par exemple, le travail CRON pour ce qui précède ressemble à ceci:

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);
  }
 }

}

Comme la classe cron ci-dessus étend la classe de badge principale, elle peut réutiliser la fonction logique try_award

La raison pour laquelle je crée une requête spécialisée à cet effet est que nous pouvons "simuler" les événements précédents, c’est-à-dire passer en revue chaque message utilisateur et déclencher la classe d’événement telle que $event->trigger(), ce serait très lent, en particulier pour de nombreux badges. Nous avons donc créé une requête optimisée.

quel utilisateur obtient le prix? tout sur l'attribution de autres utilisateurs en fonction de l'événement

La fonction Badge class award agit sur user_id - ils recevront toujours la récompense. Par défaut, le badge est attribué à la personne qui a provoqué l’événement, c’est-à-dire l’ID utilisateur de la session (c’est le cas de la fonction default_event, bien que le travail CRON parcourt évidemment tous les utilisateurs et attribue des utilisateurs distincts).

Prenons donc un exemple: lors d’un défi de codage, les utilisateurs de sites Web soumettent leur saisie de codage. L'administrateur évalue ensuite les entrées et, une fois terminé, affiche les résultats sur la page de challenge à la vue de tous. Lorsque cela se produit, un événement POSTED_RESULTS est appelé.

Si vous voulez attribuer des badges aux utilisateurs pour toutes les entrées postées, disons, si elles sont classées dans le top 5, vous devez utiliser le travail cron (bien que cela soit clair, cette mise à jour sera mise à jour pour tous les utilisateurs, et pas seulement pour le défi. les résultats ont été affichés pour)

Si vous souhaitez cibler une zone plus spécifique à mettre à jour avec le travail cron, voyons s'il existe un moyen d'ajouter des paramètres de filtrage à l'objet travail cron et demandez à la fonction cron_job de les utiliser. Par exemple:

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);
   }
}

La fonction cron fonctionnera toujours même si le paramètre n'est pas fourni.

85
Gary Green

J'ai implémenté un système de récompense une fois dans ce que vous appelleriez une base de données orientée document (c'était une boue pour les joueurs). Quelques faits saillants de mon implémentation, traduits en PHP et en MySQL:

  • Chaque détail concernant le badge est stocké dans les données des utilisateurs. Si vous utilisez MySQL, je me serais assuré que ces données sont dans un enregistrement par utilisateur dans la base de données pour des performances optimales. 

  • Chaque fois que la personne en question fait quelque chose, le code déclenche le code du badge avec un indicateur donné, par exemple un indicateur ('POST_MESSAGE'). 

  • Un événement peut également déclencher un compteur, par exemple un nombre de publications. augmentation_compte ('POST_MESSAGE'). Ici, vous pouvez vérifier (soit par un crochet, soit simplement par un test avec cette méthode) que si le nombre de POST_MESSAGE est supérieur à 300, vous devriez alors avoir un badge de récompense, par exemple: flag ("300_POST"). 

  • Dans la méthode du drapeau, je mettrais le code pour récompenser les badges. Par exemple, si le drapeau 300_POST est envoyé, le badge récompense_badge ("300_POST") doit être appelé.

  • Dans la méthode des indicateurs, les indicateurs précédents des utilisateurs doivent également être présents. vous pouvez donc dire que lorsque l'utilisateur a FIRST_COMMENT, FIRST_POST, FIRST_READ, vous accordez le badge ("NOUVEL UTILISATEUR") et lorsque vous obtenez 100_COMMENT, 100_POST, 300_READ, vous pouvez accorder le badge ("EXPERIENCED_USER").

  • Tous ces drapeaux et badges doivent être stockés d’une manière ou d’une autre. Utilisez un moyen où vous pensez que les drapeaux sont des bits. Si vous voulez que cela soit stocké de manière vraiment efficace, vous les considérez comme des bits et utilisez le code ci-dessous: (Ou vous pouvez simplement utiliser une chaîne nue "000000001111000" si vous ne voulez pas cette complexité.

$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";
  • Un bon moyen de stocker un document pour l'utilisateur consiste à utiliser json et à stocker les données de l'utilisateur dans une seule colonne de texte. Utilisez json_encode et json_decode pour stocker/récupérer les données.

  • Pour suivre l'activité de certaines données utilisateur manipulées par un autre utilisateur, ajoutez une structure de données à l'élément et utilisez-y également des compteurs. Par exemple, le nombre de lectures. Utilisez la même technique que celle décrite ci-dessus pour l’attribution de badges, mais la mise à jour devrait bien sûr aller dans la publication de l’utilisateur propriétaire. (Par exemple, article lu 1000 fois badge).

9
Knubo

UserInfuser est une plateforme de gamification open source qui implémente un service de badging/points. Vous pouvez consulter son API ici: http://code.google.com/p/userinfuser/wiki/API_Documentation

Je l'ai implémenté et j'ai essayé de limiter le nombre de fonctions. Voici l'API pour un client php:

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);
}

Le résultat final consiste à afficher les données de manière significative via l'utilisation de widgets. Ces widgets comprennent: la boîte à trophées, le classement, les jalons, les notifications en direct, le rang et les points. 

Vous trouverez l’implémentation de l’API ici: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py

2
Navraj Chohan

Les réalisations peuvent être lourdes et encore plus si vous devez les ajouter plus tard, à moins que vous n'ayez une classe Event bien formée.

Cela s'inscrit dans ma technique de mise en œuvre des réalisations.

J'aime les diviser d'abord en "catégories" et au sein de celles-ci ont des niveaux d'accomplissement. c'est-à-dire qu'une catégorie kills dans une partie peut avoir une récompense à 1 pour la première mise à mort, 10 dix mises à mort, 1 000 000 de mises à mort, etc.

Puis, au cœur de toute bonne application, la classe qui gère vos événements. Encore une fois imaginer un jeu avec tue; quand un joueur tue quelque chose, il se passe des choses. Le kill est noté, etc., et il est préférable de le gérer dans un emplacement centralisé, tel que la classe Events, qui peut envoyer des informations à d’autres endroits concernés. 

Il s’inscrit parfaitement là-dedans: dans la méthode appropriée, instanciez votre classe Achievements et cochez-la au lecteur.

En tant que construction de la classe Achievements, il est simple, il suffit de regarder la base de données pour voir si le joueur a autant de victimes qu’il en faudra pour la prochaine réalisation.

J'aime stocker les réalisations de l'utilisateur dans un champ BitField à l'aide de Redis, mais la même technique peut être utilisée dans MySQL. Autrement dit, vous pouvez stocker les réalisations du joueur sous la forme int, puis and avec le bit que vous avez défini comme cette réussite, pour voir s’ils l’ont déjà acquise. De cette façon, il utilise une seule colonne int dans la base de données.

L'inconvénient est que vous devez les organiser correctement et que vous aurez probablement besoin de faire quelques commentaires dans votre code pour que vous vous souveniez de ce que 2 ^ 14 correspond à plus tard. Si vos réalisations sont énumérées dans leur propre tableau, vous pouvez simplement faire 2 ^ pk où pk est la clé primaire du tableau des réalisations. Cela rend le chèque quelque chose comme 

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

De cette façon, vous pourrez ajouter des réalisations plus tard et cela ira bien, ne changez JAMAIS la clé primaire des réalisations déjà attribuées.

0
NappingRabbit