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 la mejor manera de saber que faltan 3 o más registros consecutivos y este hilo es realmente una extensión para obtener las ideas de los desarrolladores.
El problema que tengo cuando se habla mucho sobre insignias / sistemas de logros en este sitio web es solo eso: todo habla y no hay código. ¿Dónde están los ejemplos reales de implementación de código?
Propongo aquí un diseño en el que espero que la gente pueda contribuir y, con suerte, cree un buen diseño para codificar sistemas de logros extensibles. No digo que esto sea lo mejor, ni mucho menos, pero es un posible bloque de partida.
No dude en contribuir con sus ideas.
mi idea de diseño de sistema
Parece que el consenso general es crear un "sistema basado en eventos": cada vez que ocurre un evento conocido, como una publicación, se crea, elimina, etc., llama a la clase de evento así.
$event->trigger('POST_CREATED', array('id' => 8));
Luego, la clase de evento descubre qué insignias están "escuchando" para este evento, luego requires
ese archivo y crea una instancia de esa clase, así:
require '/badges/' . $file;
$badge = new $class;
Luego llama al evento predeterminado que pasa los datos recibidos cuando trigger
fue llamado;
$badge->default_event($data);
las insignias
Aquí es donde ocurre la verdadera magia. cada insignia tiene su propia consulta / lógica para determinar si se debe otorgar una insignia. Cada insignia se presenta, por ejemplo, en 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
La función proviene de una clase extendida Badge
que básicamente verifica si el usuario ya recibió esa insignia, si no, actualizará la tabla db de la insignia. La clase de credencial también se encarga de recuperar todas las credenciales de un usuario y devolverlas en una matriz, etc. (para que las credenciales se puedan mostrar, por ejemplo, en el perfil del usuario)
¿Qué pasa cuando el sistema se implementa por primera vez en un sitio que ya está activo?
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 insignias se implementa e inicia por primera vez, las insignias que ya deberían haberse ganado aún no se han otorgado porque se trata de un sistema basado en eventos. Por lo tanto, se ejecuta un trabajo CRON a pedido para cada insignia para otorgar lo que sea necesario. Por ejemplo, el trabajo 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);
}
}
}
Como la clase cron anterior extiende la clase de insignia principal, puede reutilizar la función lógica try_award
La razón por la que creo una consulta especializada para esto es aunque podríamos "simular" eventos anteriores, es decir, revisar cada publicación de usuario y activar la clase de evento como $event->trigger()
si fuera muy lento, especialmente para muchas insignias. Entonces, en su lugar, creamos una consulta optimizada.
¿Qué usuario recibe el premio? todo sobre premiar a otros usuarios según el evento
La función de Badge
clase award
sigue actuando user_id
: siempre se les otorgará el premio. Por defecto, la insignia se otorga a la persona que CAUSÓ que ocurriera el evento, es decir, la identificación del usuario de la sesión (esto es cierto para la default_event
función, aunque el trabajo CRON obviamente recorre todos los usuarios y otorga premios 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. Luego, el administrador juzga las entradas y, cuando está completo, publica los resultados en la página del desafío para que todos los vean. Cuando esto sucede, se llama a un evento POSTED_RESULTS.
Si desea otorgar insignias a los usuarios por todas las entradas publicadas, digamos, si se clasificaron entre los 5 primeros, debe usar el trabajo cron (aunque tenga en cuenta que esto se actualizará para todos los usuarios, no solo para ese desafío el se publicaron 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 del trabajo cron y obtener la función cron_job 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 no se proporciona el parámetro.
fuente
Respuestas:
Implementé un sistema de recompensas una vez en lo que llamarías una base de datos orientada a documentos (esto fue un lodo para los jugadores). Algunos aspectos destacados de mi implementación, traducidos a PHP y MySQL:
Cada detalle de la insignia se almacena en los datos de los usuarios. Si usa MySQL, me habría 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 activa el código de la insignia con una bandera determinada, por ejemplo, la bandera ('POST_MESSAGE').
Un evento también podría activar un contador, por ejemplo, un recuento del número de publicaciones. Incrementar_cuenta ('POST_MESSAGE'). Aquí puede verificar (ya sea mediante un gancho o simplemente haciendo una prueba en este método) de 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, pondría el código para recompensar las insignias. Por ejemplo, si se envía la Bandera 300_POST, entonces se debe llamar a la insignia Prize_badge ("300_POST").
En el método de la bandera, también debe tener presentes las banderas anteriores del usuario. por lo que podría decir que cuando el usuario tiene FIRST_COMMENT, FIRST_POST, FIRST_READ, usted otorga la insignia ("NUEVO USUARIO"), y cuando obtiene 100_COMMENT, 100_POST, 300_READ puede otorgar la insignia ("EXPERIENCED_USER")
Todas estas banderas e insignias deben almacenarse de alguna manera. Use alguna forma en la que piense en las banderas como bits. Si desea que esto se almacene de manera realmente eficiente, piense en ellos como bits y use el siguiente código: (O simplemente podría usar una cadena simple "000000001111000" si no desea esta complejidad.
Una buena forma de almacenar un documento para el usuario es usar json y almacenar los datos de los usuarios en una sola columna de texto. Utilice json_encode y json_decode para almacenar / recuperar los datos.
Para rastrear la actividad en algunos de los datos de los usuarios manipulados por otro usuario, agregue una estructura de datos en el artículo y use contadores allí también. Por ejemplo, lea el recuento. Utilice la misma técnica descrita anteriormente para otorgar insignias, pero la actualización, por supuesto, debe ir a la publicación de los usuarios propietarios. (Por ejemplo, el artículo lee 1000 veces la insignia).
fuente
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é y traté de mantener al mínimo el número de funciones. Aquí está la API para un cliente 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); }
El resultado final es mostrar los datos de una manera significativa mediante el uso de widgets. Estos widgets incluyen: vitrina de trofeos, clasificación, hitos, notificaciones en vivo, clasificación 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
fuente
Los logros pueden ser gravosos y aún más si tienes que agregarlos más tarde, a menos que tengas una
Event
clase bien formada .Esto da paso a mi técnica de implementación de logros.
Me gusta dividirlos primero en 'categorías' y dentro de ellos tengo niveles de logro. es decir, una
kills
categoría en un juego puede tener un premio de 1 por primera muerte, 10 diez muertes, 1000 mil muertes, etc.Luego, a la columna vertebral de cualquier buena aplicación, la clase que maneja sus eventos. De nuevo imaginando un juego con asesinatos; cuando un jugador mata algo, suceden cosas. Se anota la muerte, etc., y eso se maneja mejor en una ubicación centralizada, como una
Events
clase que puede enviar información a otros lugares involucrados.Cae perfectamente en su lugar allí, que en el método adecuado, instancia tu
Achievements
clase y verifica si el jugador tiene una.Como construir la
Achievements
clase, es trivial, solo algo que verifica la base de datos para ver si el jugador tiene tantos asesinatos como se requieren para el próximo logro.Me gusta almacenar los logros de los usuarios en un BitField usando Redis, pero la misma técnica se puede usar en MySQL. Es decir, puedes almacenar los logros del jugador como un
int
y luegoand
ese int con el bit que has definido como ese logro para ver si ya lo han ganado. De esa manera, usa solo unaint
columna en la base de datos.La desventaja de esto es que debes tenerlos bien organizados y probablemente necesitarás hacer algunos comentarios en tu código para recordar a qué corresponde 2 ^ 14 más adelante. Si sus logros están enumerados en su propia tabla, entonces puede hacer 2 ^ pk donde
pk
es la clave principal de la tabla de logros. Eso hace que el cheque sea algo comoif(((2**$pk) & ($usersAchInt)) > 0){ // fire off the giveAchievement() event }
De esta manera, puede agregar logros más tarde y se combinará bien, solo NUNCA cambie la clave principal de los logros ya otorgados.
fuente