wake-up-neo.net

Der beste Weg, das Achievements-System zu codieren

Ich denke an den besten Weg, ein Leistungssystem für meine Website zu entwerfen. Die Datenbankstruktur befindet sich unter beste Methode, um drei oder mehr aufeinanderfolgende Datensätze zu vermissen und dieser Thread ist wirklich eine Erweiterung, um die Ideen von Entwicklern zu erhalten.

Das Problem, das ich mit vielen Gesprächen über Abzeichen/Leistungssysteme auf dieser Website habe, ist nur das - es wird nur gesprochen und kein Code. Wo sind die eigentlichen Code-Implementierungsbeispiele?

Ich schlage hier ein Design vor, von dem ich hoffe, dass die Leute dazu beitragen und hoffentlich ein gutes Design für die Codierung erweiterbarer Leistungssysteme erstellen können. Ich sage nicht, dass dies das Beste ist, weit davon entfernt, aber es ist ein möglicher Startblock.

Bitte zögern Sie nicht, Ihre Ideen einzubringen.


meine Systementwurfsidee

Es scheint, dass der allgemeine Konsens darin besteht, ein "ereignisbasiertes System" zu erstellen - wann immer ein bekanntes Ereignis auftritt, wenn ein Beitrag erstellt, gelöscht usw. wird, ruft er die Ereignisklasse wie folgt auf.

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

Die Ereignisklasse findet dann heraus, welche Abzeichen dieses Ereignis "überwachen", requires diese Datei und erstellt eine Instanz dieser Klasse wie folgt:

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

Es ruft dann das Standardereignis auf und übergibt die beim Aufruf von trigger erhaltenen Daten.

$badge->default_event($data);

die Abzeichen

Hier passiert dann die wahre Magie. Jedes Abzeichen verfügt über eine eigene Abfrage/Logik, um zu bestimmen, ob ein Abzeichen vergeben werden soll. Jedes Abzeichen ist in z. dieses 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);
  }

 }
}

Die award-Funktion stammt von einer erweiterten Klasse Badge, die im Grunde prüft, ob der Benutzer dieses Badge bereits erhalten hat. Andernfalls wird die Badge-Db-Tabelle aktualisiert. Die Badge-Klasse sorgt auch dafür, dass alle Badges für einen Benutzer abgerufen und in einem Array usw. zurückgegeben werden (so können Badges beispielsweise im Benutzerprofil angezeigt werden).

Wie sieht es aus, wenn das System auf einer bereits aktiven Site zum ersten Mal implementiert wird?

Es gibt auch eine "Cron" -Objektabfrage, die zu jedem Ausweis hinzugefügt werden kann. Der Grund dafür ist, dass, wenn das Badge-System zum ersten Mal implementiert und initiiert wird, die bereits erworbenen Badges noch nicht vergeben wurden, da dies ein ereignisbasiertes System ist. Ein CRON-Job wird also für jeden Ausweis nach Bedarf ausgeführt, um alles zu vergeben, was erforderlich ist. Zum Beispiel würde der CRON-Job für das Obige folgendermaßen aussehen:

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

}

Da die obige Cron-Klasse die Haupt-Badge-Klasse erweitert, kann sie die Logikfunktion try_award wiederverwenden.

Der Grund, warum ich eine spezielle Abfrage dafür erstelle, besteht darin, dass wir vorherige Ereignisse "simulieren" könnten, d. H. Jeden Benutzerbeitrag durchgehen und die Ereignisklasse auslösen, wie $event->trigger(). Dies wäre sehr langsam, insbesondere für viele Abzeichen. Wir erstellen stattdessen eine optimierte Abfrage.

welcher Benutzer erhält den Preis? Alles über die Vergabe von anderen Benutzern basierend auf Ereignis

Die Badge-Klasse award wirkt auf user_id - sie wird immer mit dem Preis ausgezeichnet. Standardmäßig wird das Abzeichen der Person verliehen, die das Ereignis ausgelöst hat, d. H. Die Sitzungsbenutzer-ID (dies gilt für die default_event-Funktion, obwohl der CRON-Job offensichtlich alle Benutzer durchläuft und separate Benutzer vergibt.)

Nehmen wir ein Beispiel: Bei einer Kodierungsherausforderung senden Website-Benutzer ihre Kodierungseinträge ein. Der Administrator beurteilt dann die Einträge und bucht die Ergebnisse, wenn sie abgeschlossen sind, auf der Challenge-Seite, damit alle sie sehen können. In diesem Fall wird ein POSTED_RESULTS-Ereignis aufgerufen.

Wenn Sie Abzeichen für Benutzer für alle veröffentlichten Einträge vergeben möchten, sagen wir, wenn sie in den Top 5 eingestuft wurden, sollten Sie den Cron-Job verwenden (obwohl dies unberücksichtigt bleibt, wird dies für alle Benutzer und nicht nur für diese Herausforderung der Fall sein Ergebnisse wurden veröffentlicht für)

Wenn Sie einen spezifischeren Bereich zum Aktualisieren mit dem Cron-Job anvisieren möchten, können Sie sehen, ob es eine Möglichkeit gibt, dem Cron-Job-Objekt Filterparameter hinzuzufügen, und die Funktion Cron_job verwenden, um sie zu verwenden. Zum Beispiel:

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

Die Cron-Funktion funktioniert auch dann, wenn der Parameter nicht angegeben wird.

85
Gary Green

Ich habe einmal ein Belohnungssystem in einer so genannten dokumentenorientierten Datenbank implementiert (dies war ein Schlamm für Spieler). Einige Highlights aus meiner Implementierung, übersetzt in PHP und MySQL:

  • Jedes Detail des Ausweises wird in den Benutzerdaten gespeichert. Wenn Sie MySQL verwenden, hätte ich sichergestellt, dass sich diese Daten in einem Datensatz pro Benutzer in der Datenbank befinden, um die Leistung zu verbessern. 

  • Jedes Mal, wenn die betreffende Person etwas unternimmt, löst der Code den Badge-Code mit einem bestimmten Flag aus, beispielsweise Flag ('POST_MESSAGE'). 

  • Ein Ereignis kann auch einen Zähler auslösen, beispielsweise die Anzahl der Beiträge. Zunahme_Zahl ('POST_MESSAGE'). Hier könnten Sie überprüfen (entweder durch einen Haken oder einfach einen Test in dieser Methode), dass, wenn der POST_MESSAGE-Zähler> 300 ist, Sie ein Badge erhalten sollten, zum Beispiel: flag ("300_POST"). 

  • Bei der Flag-Methode würde ich den Code zur Belohnung von Abzeichen verwenden. Wenn beispielsweise das Flag 300_POST gesendet wird, sollte das Badge belohnung_badge ("300_POST") aufgerufen werden.

  • Bei der Flag-Methode sollten Sie auch die vorherigen Flag der Benutzer vorhanden haben. Sie können also sagen, wenn der Benutzer FIRST_COMMENT, FIRST_POST, FIRST_READ hat, geben Sie das Badge ("NEW USER") ab und wenn Sie 100_COMMENT, 100_POST, 300_READ erhalten, können Sie das Badge ("EXPERIENCED_USER") vergeben.

  • Alle diese Flaggen und Abzeichen müssen irgendwie gespeichert werden. Verwenden Sie einen Weg, wo Sie die Flags als Bits betrachten. Wenn Sie möchten, dass dies wirklich effizient gespeichert wird, stellen Sie sich diese als Bits vor und verwenden Sie den folgenden Code: (Oder Sie können einfach eine leere Zeichenfolge "000000001111000" verwenden, wenn Sie diese Komplexität nicht wünschen.

$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";
  • Eine gute Möglichkeit, ein Dokument für den Benutzer zu speichern, besteht darin, json zu verwenden und die Benutzerdaten in einer einzelnen Textspalte zu speichern. Verwenden Sie json_encode und json_decode zum Speichern/Abrufen der Daten.

  • Um die Aktivität einiger Benutzerdaten zu verfolgen, die von einem anderen Benutzer bearbeitet wurden, fügen Sie dem Element eine Datenstruktur hinzu und verwenden Sie auch Zähler dort. Zum Beispiel lesen Sie Anzahl. Verwenden Sie das gleiche Verfahren wie oben für die Vergabe von Abzeichen beschrieben, aber das Update sollte natürlich in den Post der Eigentümer übernommen werden. (Zum Beispiel Artikel lesen 1000-mal Abzeichen).

9
Knubo

UserInfuser ist eine Open-Source-Gamification-Plattform, die einen Badging-/Punktedienst implementiert. Sie können die API hier überprüfen: http://code.google.com/p/userinfuser/wiki/API_Documentation

Ich habe es implementiert und versucht, die Anzahl der Funktionen minimal zu halten. Hier ist die API für einen 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);
}

Das Endergebnis besteht darin, die Daten durch die Verwendung von Widgets auf sinnvolle Weise darzustellen. Diese Widgets umfassen: Trophäenfall, Bestenliste, Meilensteine, Live-Benachrichtigungen, Rang und Punkte. 

Die Implementierung der API finden Sie hier: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py

2
Navraj Chohan

Erfolge können mühsam sein, wenn Sie sie später hinzufügen müssen, es sei denn, Sie haben eine wohlgeformte Event-Klasse.

Dies führt zu meiner Technik der Umsetzung von Errungenschaften.

Ich mag es, sie zuerst in "Kategorien" aufzuteilen, und innerhalb dieser Ebenen gibt es Leistungsstufen. eine kills-Kategorie in einem Spiel kann eine Auszeichnung von 1 für den ersten Kill, 10 10 Kills, 1000 Tausend Kills usw. erhalten.

Dann zum Rückgrat einer guten Anwendung, der Klasse, die Ihre Ereignisse behandelt. Wieder ein Spiel mit Tötungen vorstellen; Wenn ein Spieler etwas tötet, passiert etwas. Der Kill wird notiert usw. und wird am besten an einem zentralisierten Ort gehandhabt, z. B. der Klasse und der Variable Events, die Informationen an andere betroffene Stellen senden kann. 

Es fügt sich perfekt ein, dass in der richtigen Methode Ihre Achievements-Klasse instanziiert wird und überprüft wird, dass der Spieler eine fällig ist.

Beim Erstellen der Achievements-Klasse ist dies eine einfache Sache, die die Datenbank dahingehend überprüft, ob der Spieler so viele Kills hat, wie für den nächsten Erfolg erforderlich sind.

Ich mag es, Erfolge von Benutzern in einem BitField mit Redis zu speichern, aber dieselbe Technik kann in MySQL verwendet werden. Das heißt, Sie können die Erfolge des Spielers als int und dann and int mit dem von Ihnen definierten Bit als Errungenschaft speichern, um zu sehen, ob sie diese bereits gewonnen haben. Auf diese Weise wird nur eine einzige int-Spalte in der Datenbank verwendet.

Der Nachteil ist, dass Sie sie gut organisiert haben müssen und wahrscheinlich einige Kommentare in Ihrem Code vornehmen müssen, damit Sie sich daran erinnern, was 2 ^ 14 später entspricht. Wenn Ihre Erfolge in einer eigenen Tabelle aufgelistet sind, können Sie einfach 2 ^ pk ausführen, wobei pk der Primärschlüssel der Errungenstabelle ist. Das macht den Check so ähnlich 

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

Auf diese Weise können Sie Errungenschaften später hinzufügen und die Verzahnung wird fein verzahnt. Ändern Sie NIEMALS den Primärschlüssel der bereits vergebenen Erfolge.

0
NappingRabbit