Meilleur moyen de coder le système des réalisations


85

Je réfléchis à 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 à Meilleur moyen de dire 3 enregistrements consécutifs ou plus 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 badges / systèmes de réussite sur ce site Web est juste que - tout est parlé et pas de code. Où sont les exemples réels d'implémentation de code?

Je propose ici un design auquel j'espère que les gens pourront contribuer et j'espère créer 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 point de départ possible.

N'hésitez pas à apporter 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 comme une publication est créée, supprimée, etc., il appelle la classe d'événements comme ceci.

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

La classe d'événements découvre alors quels badges "écoutent" cet événement, puis dans ce requiresfichier, et crée une instance de cette classe, comme ceci:

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

Il appelle ensuite l'événement par défaut en transmettant les données reçues lors de l' triggerappel;

$badge->default_event($data);

les badges

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

 }
}

awardLa fonction provient d'une classe étendue Badgequi vérifie essentiellement si l'utilisateur a déjà reçu ce badge, sinon, mettra à jour la table de base de données des badges. La classe de badge s'occupe également de récupérer tous les badges d'un utilisateur et de les renvoyer dans un tableau, etc. (les badges peuvent donc être affichés par exemple sur le profil de l'utilisateur)

qu'en est-il lorsque le système est mis en œuvre pour la première fois sur un site déjà en ligne?

Il existe également une requête de tâche "cron" qui peut être ajoutée à chaque badge. La raison en est que lorsque le système de badges est mis en œuvre et lancé pour la première fois, les badges qui auraient déjà dû être gagnés n'ont pas encore été 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 l'être. Par exemple, le travail CRON pour ce qui précède ressemblerait à:

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 pour cela est que nous pourrions "simuler" les événements précédents, c'est-à-dire parcourir chaque message d'utilisateur et déclencher la classe d'événements comme si $event->trigger()elle serait très lente, en particulier pour de nombreux badges. Nous créons donc plutôt une requête optimisée.

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

La fonction de Badgeclasse awardagit sur user_id- ils recevront toujours le prix. Par défaut, le badge est attribué à la personne qui a CAUSÉ l'événement, c'est-à-dire l'ID utilisateur de la session (cela est vrai pour la default_eventfonction, bien que le travail CRON boucle évidemment à travers tous les utilisateurs et attribue des utilisateurs séparés)

Prenons donc un exemple, sur un site Web de défi de codage, les utilisateurs soumettent leur entrée de codage. L'administrateur évalue ensuite les entrées et une fois terminé, publie les résultats sur la page de défi pour que tous les voient. Lorsque cela se produit, un événement POSTED_RESULTS est appelé.

Si vous souhaitez attribuer des badges aux utilisateurs pour toutes les entrées publiées, disons, si elles étaient classées dans le top 5, vous devez utiliser le travail cron (bien que sachez que cela sera mis à jour pour tous les utilisateurs, pas seulement pour ce défi. les résultats ont été publié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 dans l'objet de travail cron et obtenez la fonction cron_job pour 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.



2
Il est lié mais pas dupliqué. Veuillez lire le deuxième paragraphe. «Le problème que j'ai avec beaucoup de discussions sur les badges / systèmes de réussite sur ce site Web est juste que - tout est parlé et pas de code. Où sont les exemples d'implémentation de code?
Gary Green

1
eh bien, l'écriture de code de travail n'est possible que dans une certaine mesure. Je dirais qu'il est plutôt normal que les gens ne vous donnent que la théorie, une fois qu'une mise en œuvre serait trop complexe.
Gordon

Réponses:


9

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 points forts de mon implémentation, traduits en PHP et MySQL:

  • Chaque détail du 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 les performances.

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

  • Un événement peut également déclencher un compteur, par exemple un décompte du nombre de publications. augmenter_compte ('POST_MESSAGE'). Ici, vous pouvez vérifier (soit par un crochet, soit simplement en effectuant un test dans cette méthode) que si le nombre de POST_MESSAGE est> 300, vous devriez avoir récompensé un badge, 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, vous devez également avoir les indicateurs précédents des utilisateurs présents. vous pouvez donc dire que lorsque l'utilisateur a FIRST_COMMENT, FIRST_POST, FIRST_READ vous accordez le badge ("NEW USER"), et lorsque vous obtenez 100_COMMENT, 100_POST, 300_READ, vous pouvez attribuer le badge ("EXPERIENCED_USER")

  • Tous ces drapeaux et badges doivent être stockés d'une manière ou d'une autre. Utilisez une manière où vous considérez les drapeaux comme des bits. Si vous voulez que cela soit stocké vraiment efficacement, 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";
  • Une bonne façon de stocker un document pour l'utilisateur est d'utiliser json et de stocker les données des utilisateurs dans une seule colonne de texte. Utilisez json_encode et json_decode pour stocker / récupérer les données.

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


1
La tendance classique des systèmes de badges consiste à ajouter un nouveau champ pour la nouvelle statistique à votre tableau. Pour moi, cela semble être un moyen facile et une mauvaise idée car votre stockage de données en miroir qui peuvent être calculées à partir de données déjà dans la table (peut-être un simple COUNT () qui est TRÈS rapide sur les tables MyISAM, sera de 100% précis). Si la performance était votre objectif, vous devrez effectuer une mise à jour ET sélectionner pour obtenir la valeur actuelle, par exemple post_count, pour vérifier si un badge doit être attribué. Vous ne pourriez avoir besoin que d'une seule requête, COUNT (*). Je suis d'accord pour des données plus complexes, il y aurait de bonnes raisons d'ajouter un champ
Gary Green

5
@Gary Green Ce n'est pas seulement une solution de facilité, c'est aussi une solution évolutive et compatible avec les bases de données de documents. En ce qui concerne l'exactitude, vous avez raison, mais pour un système de badges, je préférerais qu'il soit rapide et probablement correct que 100% correct et lent. Un seul décompte est probablement rapide, mais lorsque votre système évolue et que vous avez beaucoup d'utilisateurs, cette stratégie n'est pas valable.
Knubo

1
J'aime l'idée d'avoir simplement une table de définition des badges et une table de liens pour relier les utilisateurs aux badges et à leur progression actuelle. En le faisant, noSQL vous verrouille dans n'importe quel schéma du moment et n'est pas maintenable lorsque soudainement des fautes de frappe dans les badges sont trouvées ou que 1000 nouveaux badges sont ajoutés. Vous pouvez toujours avoir un processus par lots pour les mettre en cache dans plus de magasin de documents pour une récupération rapide, mais je laisserais les choses liées.
FlavorScape

2

UserInfuser est une plateforme de gamification open source qui implémente un service de badges / 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 garder le nombre de fonctions minimal. 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 est d'afficher les données de manière significative grâce à l'utilisation de widgets. Ces widgets incluent: cas de trophées, classement, jalons, notifications en direct, rang et points.

La mise en œuvre de l'API peut être trouvée ici: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py


1
est-ce basé sur PHP? La question est basée sur PHP
Lenin Raj Rajasekaran

1
Il a des liaisons PHP, mais le code côté serveur est écrit en Python.
Navraj Chohan

0

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

Cela rejoint ma technique de mise en œuvre des réalisations.

J'aime les diviser d'abord en «catégories» et à l'intérieur de celles-ci ont des niveaux de réalisation. c'est-à-dire qu'une killscatégorie dans un jeu peut avoir une récompense à 1 pour le premier kill, 10 dix kills, 1000 mille kills etc.

Puis à la colonne vertébrale de toute bonne application, la classe qui gère vos événements. Imaginez à nouveau un jeu avec des victoires; quand un joueur tue quelque chose, des choses arrivent. Le kill est noté, etc. et il est préférable de le gérer dans un emplacement centralisé, comme une Eventsclasse qui peut envoyer des informations à d'autres endroits impliqués.

Il se met parfaitement en place là, que dans la bonne méthode, instanciez votre Achievementsclasse et vérifiez que le joueur en a une.

En construisant la Achievementsclasse, c'est trivial, juste quelque chose qui vérifie la base de données pour voir si le joueur a autant de victimes que nécessaire pour la prochaine réalisation.

J'aime stocker les réalisations des utilisateurs dans un BitField en utilisant Redis mais la même technique peut être utilisée dans MySQL. Autrement dit, vous pouvez stocker les succès du joueur sous forme d'un int, puis andcet int avec le bit que vous avez défini comme cet exploit pour voir s'il l'a déjà gagné. De cette façon, il n'utilise qu'une seule intcolonne dans la base de données.

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

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

De cette façon, vous pouvez ajouter des succès plus tard et cela s'enchaînera très bien, ne changez JAMAIS la clé primaire des succès déjà attribués.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.