2010-11-16 9 views
83

Estoy pensando en la mejor manera de diseñar un sistema de logros para usar en mi sitio. La estructura de la base de datos se puede encontrar en Best way to tell 3 or more consecutive records missing y este hilo es realmente una extensión para obtener las ideas de los desarrolladores.La mejor manera de programar el sistema de Logros

El problema que tengo con muchas conversaciones sobre insignias/sistemas de logros en este sitio web es simplemente eso: todo es hablar y no tener código. ¿Dónde están los ejemplos reales de implementación del código?

Propongo aquí un diseño que espero que las personas puedan contribuir y, con suerte, crear un buen diseño para codificar sistemas de logros extensibles. No digo que esto sea lo mejor, ni mucho menos, pero es un posible bloqueo inicial.

Por favor, siéntete libre de aportar tus ideas.


mi idea de diseño del sistema

Parece que el consenso general es crear un "sistema basado en eventos" - cada vez que un evento conocido se produce como un poste está creado, eliminado, etc que llama el clase de evento como tal ..

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

La clase de evento y luego se entera de lo insignias están "escuchando" para este evento, entonces requires ese archivo, y crea una instancia de esa clase, así:

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

Llama al evento predeterminado que pasa los datos recibidos cuando se llamó trigger;

$badge->default_event($data); 

las insignias

Ésta es entonces donde sucede la magia real. cada insignia tiene su propia consulta/lógica para determinar si se debe otorgar una insignia. Cada insignia se establece en, por ejemplo, este 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); 
    } 

} 
} 

award función proviene de una clase extendida Badge que básicamente comprueba si el usuario ya ha concedido ser esa placa, si no, se actualizará la tabla db insignia. La clase de insignia también se encarga de recuperar todas las insignias para un usuario y devolverlas en una matriz, etc. (para que las insignias se muestren en el perfil del usuario)

¿Qué ocurre cuando el sistema se implementa por primera vez en un sitio en vivo?

También hay una consulta de trabajo "cron" que se puede agregar a cada insignia. La razón de esto es porque cuando el sistema de credenciales se implementa y se inicia por primera vez, las insignias que ya deberían haberse ganado no se han adjudicado porque este es un sistema basado en eventos. Por lo tanto, se ejecuta a demanda un trabajo CRON para que cada insignia otorgue todo lo que debe ser. Por ejemplo, el trabajo de cron para lo anterior se vería así:

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

} 

medida que la clase cron anterior se extiende la clase principal insignia, se puede volver a utilizar la función lógica try_award

La razón por la cual se crea una especializada la consulta para esto es a pesar de que podríamos "simular" eventos anteriores, es decir,Revisar cada publicación de usuario y activar la clase de evento como $event->trigger() sería muy lento, especialmente para muchas insignias. Entonces creamos una consulta optimizada.

¿qué usuario recibe el premio? todo sobre la adjudicación de otros usuarios sobre la base de caso

Los Badge clase award función actúa sobre user_id - que siempre se les dará el premio. Por defecto, la insignia se otorga a la persona que CAUSÓ el evento, es decir, la identificación de usuario de la sesión (esto es cierto para la función default_event, aunque el trabajo CRON obviamente enlaza a todos los usuarios y adjudica a usuarios separados)

Así que tomemos un ejemplo, en un sitio web de desafío de codificación, los usuarios envían su entrada de codificación. El administrador juzga las entradas y, cuando se completa, publica los resultados en la página de desafío para que todos lo vean. Cuando esto sucede, se llama a un evento POSTED_RESULTS.

Si desea otorgar insignias para los usuarios de todas las entradas publicadas, digamos que si se clasificaron dentro de las 5 principales, debería usar el trabajo cron (aunque sin tener en cuenta esto se actualizará para todos los usuarios, no solo para ese desafío se publicaron los resultados para)

Si desea apuntar a un área más específica para actualizar con el trabajo cron, veamos si hay una manera de agregar parámetros de filtrado en el objeto cron job, y obtener el cron_job función para usarlos. Por ejemplo:

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 función cron seguirá funcionando incluso si el parámetro no se suministra.

+0

Relacionados (tal vez duplicados): http://stackoverflow.com/questions/1744747/achievements-badges-system – Gordon

+2

Está relacionado pero no duplicado. Por favor, lea el segundo párrafo. "El problema que tengo con muchas conversaciones sobre insignias/sistemas de logros en este sitio web es simplemente eso: todo es hablar y no tener código. ¿Dónde están los ejemplos de implementación del código?" –

+1

, escribir código de trabajo solo es factible hasta cierto punto. Diría que es normal que las personas te den la teoría solamente, una vez que cualquier implementación sea demasiado compleja. – Gordon

Respuesta

9

Implementé un sistema de recompensas una vez en lo que llamaría una base de datos orientada a documentos (esto era un lodo para los jugadores). Algunos aspectos destacados de mi implementación, traducidos a PHP y MySQL:

  • Cada detalle sobre la insignia se almacena en los datos de los usuarios. Si usa MySQL me hubiera asegurado de que estos datos estén en un registro por usuario en la base de datos para el rendimiento.

  • Cada vez que la persona en cuestión hace algo, el código desencadena el código de identificación con una bandera determinada, por ejemplo, bandera ('POST_MESSAGE').

  • Un evento también podría desencadenar un contador, por ejemplo, un recuento de la cantidad de publicaciones. increase_count ('POST_MESSAGE'). Aquí podría tener un cheque (ya sea por un gancho, o simplemente haciendo una prueba en este método) que si el recuento de POST_MESSAGE es> 300, entonces debería tener una insignia de recompensa, por ejemplo: flag ("300_POST").

  • En el método de la bandera, pongo el código para recompensar insignias. Por ejemplo, si se envía el Flag 300_POST, se debe llamar a la insignia reward_badge ("300_POST").

  • En el método de indicador, también debe tener los indicadores anteriores de los usuarios presentes. por lo que se podría decir cuando el usuario tiene FIRST_COMMENT, first_post, FIRST_READ le conceda insignia ("NUEVO USUARIO"), y cuando llegue 100_COMMENT, 100_POST, 300_READ puede conceder insignia ("EXPERIENCED_USER")

  • Todas estas banderas y las insignias deben almacenarse de alguna manera. Usa alguna forma en que pienses en las banderas como bits. Si desea que esto se almacene de manera muy eficiente, piense en ellos como bits y use el siguiente código: (O podría simplemente usar una cadena vacía "000000001111000" si no desea esta complejidad.

$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"; 
  • Una buena manera de almacenar un documento para el usuario es el uso de JSON y almacenar los datos de los usuarios en una columna de texto. Use json_encode y json_decode para almacenar/recuperar los datos.

  • Para rastrear la actividad en algunos de los datos de los usuarios manipulados por algún otro usuario, agregue una estructura de datos en el elemento y utilice contadores allí también. Por ejemplo, recuento de lectura. Use la misma técnica que se describe arriba para otorgar credenciales, pero la actualización debe, por supuesto, pertenecer a la publicación de los usuarios propietarios. (Por ejemplo, artículo leído 1000 veces insignia).

+1

La tendencia clásica en los sistemas de distintivos es agregar un nuevo campo para la nueva estadística a su tabla. Para mí, eso parece una salida fácil y una mala idea porque almacenar datos duplicados que se pueden calcular a partir de los datos que ya están en la tabla (tal vez un simple COUNT() que sea MUY rápido en las tablas MyISAM, será 100% preciso). Si su objetivo era el rendimiento, deberá hacer una actualización Y seleccionar para obtener el actual, p. valor de la cuenta postal para verificar si se debe otorgar una insignia. Solo podría necesitar una consulta, COUNT (*). Estoy de acuerdo en que para datos más complejos habría buenas razones para agregar un campo –

+5

@Gary Green. No solo es una salida fácil, también es escalable y compatible con bases de datos de documentos. En cuanto a la corrección, tienes razón, aunque para un sistema de distintivos preferiría que fuera rápido y probablemente correcto que 100% correcto y lento. Un solo conteo es probablemente rápido, pero cuando su sistema escala y tiene muchos usuarios, no se da esa estrategia. – Knubo

+1

Me gusta la idea de tener una tabla de definición de insignias y una tabla de enlaces para vincular a los usuarios con las insignias y su progreso actual. Al hacerlo, noSQL te bloquea en cualquier esquema en el momento y no se puede mantener cuando de repente se encuentran errores tipográficos en insignias, o se agregan 1000 nuevas insignias. Siempre se puede tener un proceso por lotes en la memoria caché de estos en más de la tienda de documentos para una recuperación rápida, pero dejaría las cosas vinculadas. – FlavorScape

2

UserInfuser es una plataforma de gamificación de código abierto que implementa un servicio de insignias/puntos. Puede consultar su API aquí: http://code.google.com/p/userinfuser/wiki/API_Documentation

Lo implementé e intenté mantener el número de funciones mínimo. Esta es la API para un cliente PHP:

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

El fin es mostrar los datos de una manera significativa mediante el uso de widgets. Estos widgets incluyen: caso de trofeo, tabla de líderes, hitos, notificaciones en vivo, rango y puntos.

La implementación de la API se puede encontrar aquí: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py

+1

está basado en PHP? La pregunta se basa en PHP – emaillenin

+1

Tiene enlaces de PHP, pero el código del lado del servidor está escrito en Python. –

0

logros pueden ser onerosos y más aún si tiene que añadirlos en adelante, a menos que tenga una clase Event bien formada.

Esto se traslada a mi técnica de implementación de logros.

Me gusta dividirlos primero en 'categorías' y dentro de esos tienen niveles de logro. es decir, una categoría kills en un juego puede tener un premio en 1 por primera muerte, 10 diez muertes, 1000 mil muertes, etc.

Luego, en la espina dorsal de cualquier buena aplicación, la clase que maneja sus eventos. Otra vez imaginando un juego con asesinatos; cuando un jugador mata algo, sucede algo. La anotación se anota, etc. y se maneja mejor en una ubicación centralizada, como la clase Events que puede enviar información a otros lugares involucrados.

Cae perfectamente en su lugar allí, que en el método apropiado, crea una instancia de tu clase Achievements y verifica que el reproductor es debido a uno.

Como la construcción de la clase Achievements es trivial, solo algo que verifica la base de datos para ver si el jugador tiene tantas muertes como se requieren para el próximo logro.

Me gusta almacenar los logros del usuario en un campo de bits utilizando Redis, pero la misma técnica se puede utilizar en MySQL. Es decir, puede almacenar los logros del jugador como int y luego and que int con el bit que ha definido como ese logro para ver si ya lo obtuvieron. De esta forma, solo utiliza una sola columna int en la base de datos.

La desventaja de esto es que tiene que tenerlos bien organizados y es probable que necesite hacer algunos comentarios en su código para que recuerde lo que 2^14 corresponde a más adelante. Si sus logros se enumeran en su propia tabla, puede hacer 2^pk donde pk es la clave principal de la tabla de logros. Eso hace que el cheque algo así como

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

De esta manera usted puede agregar los logros más tarde y se va a encajar bien, sólo NUNCA cambiar la clave principal de los logros ya adjudicados.

Cuestiones relacionadas