Der beste Weg, um das Achievements-System zu codieren


85

Ich denke über den besten Weg nach, ein Leistungssystem für die Verwendung auf meiner Website zu entwerfen. Die Datenbankstruktur finden Sie unter Bester Weg, um 3 oder mehr fehlende aufeinanderfolgende Datensätze zu erkennen, 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 genau das - es ist alles Gerede und kein Code. Wo sind die tatsächlichen Beispiele für die Codeimplementierung?

Ich schlage hier ein Design vor, zu dem die Leute hoffentlich 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 Systemdesign-Idee

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

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

Die Ereignisklasse findet dann heraus, welche Badges auf dieses Ereignis "warten", dann auf diese requiresDatei und erstellt eine Instanz dieser Klasse wie folgt:

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

Anschließend wird das Standardereignis triggeraufgerufen, das die beim Aufruf empfangenen Daten übergibt .

$badge->default_event($data);

die Abzeichen

Hier geschieht dann die wahre Magie. Jedes Abzeichen hat eine eigene Abfrage / Logik, um zu bestimmen, ob ein Abzeichen vergeben werden soll. Jedes Abzeichen ist zB in folgendem Format angegeben:

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

 }
}

awardDie Funktion stammt aus einer erweiterten Klasse, Badgedie im Grunde prüft, ob dem Benutzer dieses Badge bereits zuerkannt wurde. Wenn nicht, wird die Badge-DB-Tabelle aktualisiert. Die Ausweisklasse kümmert sich auch darum, alle Ausweise für einen Benutzer abzurufen und in einem Array usw. zurückzugeben (so können Ausweise z. B. im Benutzerprofil angezeigt werden).

Was ist, wenn das System zum ersten Mal auf einer bereits aktiven Site implementiert wird?

Es gibt auch eine "Cron" -Jobabfrage, die jedem Ausweis hinzugefügt werden kann. Der Grund dafür ist, dass bei der erstmaligen Implementierung und Initiierung des Ausweissystems die bereits erworbenen Ausweise noch nicht vergeben wurden, da es sich um ein ereignisbasiertes System handelt. Daher wird bei Bedarf für jedes Abzeichen ein CRON-Job 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 wiederverwenden try_award

Der Grund, warum ich eine spezielle Abfrage dafür erstelle, ist, dass wir frühere Ereignisse "simulieren" könnten, dh jeden Benutzerbeitrag durchgehen und die Ereignisklasse auslösen, als $event->trigger()wäre sie sehr langsam, insbesondere für viele Abzeichen. Also erstellen wir stattdessen eine optimierte Abfrage.

Welcher Benutzer erhält die Auszeichnung? Alles über die Vergabe anderer Benutzer basierend auf dem Ereignis

Die BadgeKlassenfunktion awardwirkt auf user_id- sie erhalten immer die Auszeichnung. Standardmäßig wird der Ausweis an die Person vergeben, die das Ereignis verursacht hat, dh die Sitzungsbenutzer-ID (dies gilt für die default_eventFunktion, obwohl der CRON-Job offensichtlich alle Benutzer durchläuft und separate Benutzer vergibt).

Nehmen wir also ein Beispiel für eine Codierungs-Challenge-Website, auf der Benutzer ihren Codierungseintrag einreichen. Der Administrator beurteilt dann die Einträge und veröffentlicht die Ergebnisse nach Abschluss auf der Herausforderungsseite, damit alle sie sehen können. In diesem Fall wird ein POSTED_RESULTS-Ereignis aufgerufen.

Wenn Sie für alle veröffentlichten Einträge Ausweise für Benutzer vergeben möchten, sollten Sie beispielsweise den Cron-Job verwenden, wenn diese unter den Top 5 stehen (obwohl dies nicht nur für diese Herausforderung, sondern auch für alle Benutzer aktualisiert wird Ergebnisse wurden veröffentlicht für)

Wenn Sie auf einen bestimmten Bereich abzielen möchten, der mit dem Cron-Job aktualisiert werden soll, prüfen Sie, ob es eine Möglichkeit gibt, dem Cron-Jobobjekt Filterparameter hinzuzufügen, und lassen Sie die Funktion cron_job diese verwenden. Beispielsweise:

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 noch, wenn der Parameter nicht angegeben wird.



2
Es ist verwandt, aber nicht doppelt. Bitte lesen Sie den zweiten Absatz. "Das Problem, das ich mit vielen Gesprächen über Abzeichen / Leistungssysteme auf dieser Website habe, ist nur das - es ist alles Gerede und kein Code. Wo sind die tatsächlichen Beispiele für die Codeimplementierung?"
Gary Green

1
Nun, das Schreiben von Arbeitscode ist nur bis zu einem gewissen Grad möglich. Ich würde sagen, es ist ziemlich normal, dass die Leute Ihnen nur die Theorie geben, wenn eine Implementierung zu komplex wäre.
Gordon

Antworten:


9

Ich habe einmal ein Belohnungssystem in einer so genannten dokumentenorientierten Datenbank implementiert (dies war ein Schlamm für die 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 aus Leistungsgründen in einem Datensatz pro Benutzer in der Datenbank befinden.

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

  • Ein Ereignis kann auch einen Zähler auslösen, beispielsweise die Anzahl der Beiträge. erhöhen_anzahl ('POST_MESSAGE'). Hier können Sie überprüfen (entweder durch einen Haken oder nur durch einen Test in dieser Methode), ob Sie bei einem POST_MESSAGE-Wert> 300 ein Abzeichen belohnen sollten, zum Beispiel: flag ("300_POST").

  • Bei der Flag-Methode habe ich den Code eingegeben, um Abzeichen zu belohnen. Wenn beispielsweise das Flag 300_POST gesendet wird, sollte das Badge Reward_Badge ("300_POST") aufgerufen werden.

  • In der Flag-Methode sollten auch die vorherigen Flags des Benutzers vorhanden sein. Sie können also sagen, wenn der Benutzer FIRST_COMMENT, FIRST_POST, FIRST_READ hat, gewähren Sie ein Abzeichen ("NEW USER"), und wenn Sie 100_COMMENT, 100_POST, 300_READ erhalten, können Sie ein Abzeichen erteilen ("EXPERIENCED_USER").

  • Alle diese Flaggen und Abzeichen müssen irgendwie gespeichert werden. Verwenden Sie eine Methode, bei der 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 den folgenden Code: (Oder Sie können einfach eine bloße Zeichenfolge "000000001111000" verwenden, wenn Sie diese Komplexität nicht möchten.

$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 einzigen Textspalte zu speichern. Verwenden Sie json_encode und json_decode, um die Daten zu speichern / abzurufen.

  • Fügen Sie zum Verfolgen der Aktivität einiger Benutzerdaten, die von einem anderen Benutzer bearbeitet wurden, eine Datenstruktur zum Element hinzu und verwenden Sie dort auch Zähler. Zum Beispiel Lesezahl. Verwenden Sie für die Vergabe von Abzeichen dieselbe Technik wie oben beschrieben, aber das Update sollte natürlich in den Beitrag des Besitzers aufgenommen werden. (Zum Beispiel Artikel 1000 mal Abzeichen gelesen).


1
Der klassische Trend bei Ausweissystemen besteht darin, Ihrer Tabelle ein neues Feld für die neue Statistik hinzuzufügen. Für mich scheint das ein einfacher Ausweg und eine schlechte Idee zu sein, da das Speichern gespiegelter Daten, die aus Daten berechnet werden können, die bereits in der Tabelle enthalten sind (möglicherweise ein einfaches COUNT (), das in MyISAM-Tabellen SEHR schnell ist, 100% beträgt) genau). Wenn Leistung Ihr Ziel war, müssen Sie ein Update durchführen UND auswählen, um den aktuellen Wert zu erhalten, z. B. post_count, um zu überprüfen, ob ein Ausweis vergeben werden soll. Sie könnten nur eine Abfrage benötigen, COUNT (*). Ich bin damit einverstanden, dass es für komplexere Daten einen guten Grund gibt, ein Feld hinzuzufügen
Gary Green,

5
@ Gary Green Es ist nicht nur ein einfacher Ausweg, es ist auch skalierbar und mit Dokumentendatenbanken kompatibel. Was die Korrektheit betrifft, haben Sie Recht, obwohl ich es für ein Ausweissystem lieber schnell und höchstwahrscheinlich korrekt als zu 100% korrekt und langsam haben möchte. Eine einzige Zählung ist wahrscheinlich schnell, aber wenn Ihr System skaliert und Sie viele Benutzer haben, ist diese Strategie nicht gegeben.
Knubo

1
Ich mag die Idee, nur eine Ausweisdefinitionstabelle und eine Verknüpfungstabelle zu haben, um Benutzer mit Ausweisen und deren aktuellem Fortschritt zu verknüpfen. Wenn Sie dies tun, sperrt noSQL Sie in das jeweilige Schema und kann nicht gewartet werden, wenn plötzlich Tippfehler in Ausweisen gefunden werden oder 1000 neue Ausweise hinzugefügt werden. Sie könnten diese jederzeit in einem Stapelprozess zwischenspeichern, um sie schneller abzurufen, aber ich würde die Dinge verknüpft lassen.
FlavorScape

2

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 ist, dass die Daten mithilfe von Widgets auf sinnvolle Weise angezeigt werden. Diese Widgets umfassen: Trophäenetui, Rangliste, 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


1
basiert das auf PHP? Die Frage basiert auf PHP
Lenin Raj Rajasekaran

1
Es hat PHP-Bindungen, aber der serverseitige Code ist in Python geschrieben.
Navraj Chohan

0

Erfolge können lästig sein, und dies umso mehr, wenn Sie sie später hinzufügen müssen, es sei denn, Sie haben eine gut ausgebildete EventKlasse.

Dies führt zu meiner Technik, Erfolge umzusetzen.

Ich mag es, sie zuerst in "Kategorien" aufzuteilen und innerhalb dieser Kategorien von Leistungen zu haben. dh eine killsKategorie in einem Spiel kann eine Auszeichnung von 1 für den ersten Kill, 10 zehn Kills, 1000.000 Kills usw. erhalten.

Dann auf den Rücken jeder guten Anwendung, die Klasse, die Ihre Ereignisse behandelt. Stellen Sie sich wieder ein Spiel mit Kills vor; Wenn ein Spieler etwas tötet, passiert etwas. Der Kill wird notiert usw. und dies wird am besten an einem zentralen Ort wie einer EventsKlasse gehandhabt , die Informationen an andere beteiligte Orte senden kann.

Dort passt es perfekt zusammen, dass Sie in der richtigen Methode Ihre AchievementsKlasse instanziieren und überprüfen, ob dem Spieler eine fällig ist.

Beim Aufbau der AchievementsKlasse ist es trivial, nur etwas, das die Datenbank überprüft, um festzustellen, ob der Spieler so viele Kills hat, wie für den nächsten Erfolg erforderlich sind.

Ich mag es, die Erfolge der Benutzer mit Redis in einem BitField zu speichern, aber die gleiche Technik kann in MySQL verwendet werden. Das heißt, Sie können die Erfolge des Spielers als intund dann andals int mit dem Bit speichern, das Sie als diesen Erfolg definiert haben, um zu sehen, ob er sie bereits erreicht hat. Auf diese Weise wird nur eine einzige intSpalte in der Datenbank verwendet.

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

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

Auf diese Weise können Sie später Erfolge hinzufügen, und es passt gut zusammen. Ändern Sie NIEMALS den Primärschlüssel der bereits vergebenen Erfolge.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.