Il modo migliore per codificare il sistema degli obiettivi


85

Sto pensando al modo migliore per progettare un sistema di risultati da utilizzare sul mio sito. La struttura del database può essere trovata nel modo migliore per dire 3 o più record consecutivi mancanti e questo thread è davvero un'estensione per ottenere le idee dagli sviluppatori.

Il problema che ho con un sacco di discorsi su badge / sistemi di realizzazione su questo sito Web è proprio questo: sono solo chiacchiere e niente codice. Dove sono gli esempi effettivi di implementazione del codice?

Propongo qui un progetto a cui spero le persone possano contribuire e, si spera, creare un buon progetto per la codifica di sistemi di realizzazione estensibili. Non sto dicendo che questo sia il migliore, tutt'altro, ma è un possibile blocco di partenza.

Sentiti libero di contribuire con le tue idee.


la mia idea di progettazione del sistema

Sembra che il consenso generale sia quello di creare un "sistema basato su eventi" - ogni volta che si verifica un evento noto come la creazione, l'eliminazione di un post, ecc. Chiama la classe dell'evento in questo modo

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

La classe dell'evento quindi scopre quali badge sono "in ascolto" per questo evento, quindi su requiresquel file e crea un'istanza di quella classe, in questo modo:

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

Quindi chiama l'evento predefinito passando i dati ricevuti quando è triggerstato chiamato;

$badge->default_event($data);

i badge

È qui che avviene la vera magia. ogni badge ha una propria query / logica per determinare se un badge deve essere assegnato. Ogni badge è esposto ad esempio in questo formato:

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 funzione proviene da una classe estesa Badgeche fondamentalmente controlla per vedere se l'utente ha già ricevuto quel badge, in caso contrario aggiornerà la tabella db badge. La classe badge si occupa anche di recuperare tutti i badge per un utente e di restituirli in un array, ecc. (Così i badge possono essere visualizzati ad esempio sul profilo utente)

e quando il sistema viene implementato per la prima volta su un sito già attivo?

C'è anche una query di lavoro "cron" che può essere aggiunta a ciascun badge. Il motivo è che quando il sistema di badge è stato implementato e avviato per la prima volta, i badge che avrebbero dovuto essere già guadagnati non sono stati ancora assegnati perché si tratta di un sistema basato su eventi. Quindi un lavoro CRON viene eseguito su richiesta per ogni badge per assegnare tutto ciò che deve essere. Ad esempio, il lavoro CRON per quanto sopra sarebbe simile a:

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

}

Poiché la classe cron precedente estende la classe badge principale, può riutilizzare la funzione logica try_award

Il motivo per cui creo una query specializzata per questo è anche se potremmo "simulare" eventi precedenti, cioè passare attraverso ogni post degli utenti e attivare la classe dell'evento come $event->trigger()se fosse molto lento, specialmente per molti badge. Quindi creiamo invece una query ottimizzata.

quale utente ottiene il premio? tutto sulla premiazione di altri utenti in base all'evento

La funzione di Badgeclasse awardagisce su user_id: verrà sempre assegnato il premio. Per impostazione predefinita, il badge viene assegnato alla persona che HA CAUSATO l'evento, ovvero l'ID utente della sessione (questo è vero per la default_eventfunzione, anche se il lavoro CRON ovviamente passa attraverso tutti gli utenti e premia gli utenti separati)

Quindi, facciamo un esempio, su un sito Web di sfida di codifica gli utenti inviano la loro voce di codifica. L'amministratore quindi giudica le voci e, una volta completate, pubblica i risultati nella pagina della sfida affinché tutti possano vederli. Quando ciò accade, viene chiamato un evento POSTED_RESULTS.

Se vuoi assegnare badge per gli utenti per tutte le voci pubblicate, diciamo, se fossero classificate tra le prime 5, dovresti usare il cron job (anche se a mente questo verrà aggiornato per tutti gli utenti, non solo per quella sfida i risultati sono stati pubblicati per)

Se vuoi indirizzare un'area più specifica da aggiornare con il cron job, vediamo se esiste un modo per aggiungere parametri di filtro nell'oggetto cron job e ottenere la funzione cron_job per usarli. Per esempio:

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 funzione cron continuerà a funzionare anche se il parametro non viene fornito.



2
È correlato ma non duplicato. Si prega di leggere il secondo paragrafo. "Il problema che ho con un sacco di discorsi su badge / sistemi di realizzazione su questo sito web è proprio questo: sono solo chiacchiere e niente codice. Dove sono gli esempi di implementazione del codice?"
Gary Green

1
beh, scrivere codice funzionante è possibile solo fino a un certo punto. Direi che è piuttosto normale che le persone ti diano solo la teoria, una volta che qualsiasi implementazione sarebbe troppo complessa.
Gordon

Risposte:


9

Una volta ho implementato un sistema di ricompensa in quello che chiameresti un database orientato ai documenti (questo era un fango per i giocatori). Alcuni punti salienti della mia implementazione, tradotti in PHP e MySQL:

  • Ogni dettaglio sul badge viene memorizzato nei dati degli utenti. Se usi MySQL, mi sarei assicurato che questi dati fossero in un record per utente nel database per le prestazioni.

  • Ogni volta che la persona in questione fa qualcosa, il codice attiva il codice del badge con un determinato flag, ad esempio flag ("POST_MESSAGE").

  • Un evento potrebbe anche attivare un contatore, ad esempio un conteggio del numero di post. aumento_count ("POST_MESSAGE"). Qui potresti avere un controllo (tramite un hook, o semplicemente facendo un test in questo metodo) che se il conteggio POST_MESSAGE è> 300 allora dovresti ricevere un badge, ad esempio: flag ("300_POST").

  • Nel metodo flag, inserisco il codice per premiare i badge. Ad esempio, se viene inviato il Flag 300_POST, dovrebbe essere chiamato il badge reward_badge ("300_POST").

  • Nel metodo flag, dovresti anche avere i flag precedenti degli utenti presenti. così puoi dire quando l'utente ha FIRST_COMMENT, FIRST_POST, FIRST_READ concedi il badge ("NEW USER") e quando ottieni 100_COMMENT, 100_POST, 300_READ puoi concedere il badge ("EXPERIENCED_USER")

  • Tutte queste bandiere e badge devono essere archiviati in qualche modo. Usa un modo in cui pensi ai flag come bit. Se vuoi che questo sia memorizzato in modo davvero efficiente, considerali come bit e usa il codice seguente: (Oppure potresti semplicemente usare una semplice stringa "000000001111000" se non vuoi questa complessità.

$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 bel modo di memorizzare un documento per l'utente è usare json e memorizzare i dati degli utenti in una singola colonna di testo. Usa json_encode e json_decode per archiviare / recuperare i dati.

  • Per monitorare l'attività su alcuni dati degli utenti manipolati da altri utenti, aggiungi una struttura dati sull'elemento e utilizza anche i contatori. Ad esempio, read count. Usa la stessa tecnica descritta sopra per l'assegnazione dei badge, ma l'aggiornamento dovrebbe ovviamente andare nel post degli utenti proprietari. (Ad esempio l'articolo letto 1000 volte il badge).


1
La tendenza classica nei sistemi di badge è quella di aggiungere un nuovo campo per la nuova statistica alla tua tabella. Per me, sembra un po 'una via d'uscita facile e una cattiva idea perché la memorizzazione dei dati in mirroring che possono essere calcolati dai dati già nella tabella (forse un semplice COUNT () che è MOLTO veloce sulle tabelle MyISAM, sarà al 100% accurato). Se il rendimento era il tuo obiettivo, dovrai eseguire un aggiornamento E selezionare per ottenere il valore corrente ad es. Potresti aver bisogno di una sola query, COUNT (*). Sono d'accordo che per dati più complessi ci sarebbe una buona ragione per aggiungere un campo però
Gary Green

5
@Gary Green Non è solo una facile via d'uscita, è anche un modo scalabile e compatibile con i database dei documenti. Per quanto riguarda la correttezza, hai ragione, anche se per un sistema di badge preferirei che fosse veloce e molto probabilmente corretto piuttosto che corretto e lento al 100%. Un singolo conteggio è probabilmente veloce, ma quando il tuo sistema scala e hai molti utenti non è dato che la strategia sia valida.
Knubo

1
Mi piace l'idea di avere solo una tabella di definizione dei badge e una tabella di collegamento per collegare gli utenti ai badge e ai loro progressi attuali. In questo modo noSQL ti blocca in qualsiasi schema al momento e non è gestibile quando vengono trovati improvvisamente errori di battitura nei badge o vengono aggiunti 1000 nuovi badge. Potresti sempre avere un processo batch che li memorizza in più archivi di documenti per un rapido recupero, ma lascerei le cose collegate.
FlavorScape

2

UserInfuser è una piattaforma di gamification open source che implementa un servizio di badge / punti. Puoi controllare la sua API qui: http://code.google.com/p/userinfuser/wiki/API_Documentation

L'ho implementato e ho cercato di mantenere il numero di funzioni minimo. Ecco l'API per 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);
}

Il risultato finale è mostrare i dati in modo significativo attraverso l'uso di widget. Questi widget includono: porta trofei, classifica, traguardi, notifiche in tempo reale, classifica e punti.

L'implementazione dell'API può essere trovata qui: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py


1
è basato su PHP? La domanda è basata su PHP
Lenin Raj Rajasekaran

1
Ha collegamenti PHP, ma il codice lato server è scritto in Python.
Navraj Chohan

0

I risultati possono essere gravosi e lo sono ancora di più se devi aggiungerli in seguito, a meno che tu non abbia una Eventclasse ben formata .

Questo segue la mia tecnica di implementazione dei risultati.

Mi piace dividerli prima in "categorie" e all'interno di queste hanno livelli di realizzazione. cioè una killscategoria in un gioco può avere un premio a 1 per la prima uccisione, 10 dieci uccisioni, 1000 mila uccisioni ecc.

Quindi alla spina dorsale di ogni buona applicazione, la classe che gestisce i tuoi eventi. Immaginando di nuovo un gioco con uccisioni; quando un giocatore uccide qualcosa, succede qualcosa. L'uccisione è annotata, ecc. E questo è gestito al meglio in una posizione centralizzata, come una Eventsclasse che può inviare informazioni ad altri luoghi coinvolti.

È perfettamente a posto lì, che nel metodo corretto, istanzia la tua Achievementsclasse e controlla che al giocatore sia dovuta una.

Poiché la costruzione della Achievementsclasse è banale, è solo qualcosa che controlla il database per vedere se il giocatore ha tante uccisioni quante sono necessarie per il risultato successivo.

Mi piace memorizzare i risultati dell'utente in un BitField utilizzando Redis, ma la stessa tecnica può essere utilizzata in MySQL. Cioè, puoi memorizzare i risultati del giocatore come un inte poi andquell'int con il bit che hai definito come risultato per vedere se lo hanno già ottenuto. In questo modo utilizza solo una singola intcolonna nel database.

Lo svantaggio di questo è che devi averli organizzati bene e probabilmente dovrai fare alcuni commenti nel tuo codice in modo da ricordare a cosa corrisponde 2 ^ 14 in seguito. Se i tuoi risultati sono enumerati nella loro tabella, puoi semplicemente fare 2 ^ pk dovepk è la chiave primaria della tabella dei risultati. Questo rende il controllo qualcosa di simile

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

In questo modo puoi aggiungere risultati in un secondo momento e andrà bene, ma MAI cambiare la chiave primaria dei risultati già assegnati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.