php - pattern - La mejor forma de codificar el sistema de Logros
php design patterns (3)
Implementé un sistema de recompensas una vez en lo que llamarías 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 activa 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 bandera, también debe tener presentes los indicadores anteriores de los usuarios. para que pueda decir cuándo el usuario tiene FIRST_COMMENT, FIRST_POST, FIRST_READ, otorga una insignia ("NEW USER") y cuando obtiene 100_COMMENT, 100_POST, 300_READ, puede otorgar una insignia ("EXPERIENCED_USER")
Todas estas banderas y distintivos 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 forma de almacenar un documento para el usuario es usar json y almacenar los datos de los usuarios en una sola 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 use los 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).
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 de la mejor manera para decir 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 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 un poco de 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éntase libre de contribuir con sus ideas.
mi idea de diseño del sistema
Parece que el consenso general es crear un "sistema basado en eventos": cuando se produce un evento conocido, como que se crea, se elimina una publicación, etc. llama a la clase de evento como tal ...
$event->trigger(''POST_CREATED'', array(''id'' => 8));
La clase de evento luego descubre qué insignias están "escuchando" para este evento, luego requires
ese archivo y crea una instancia de esa clase, de esta manera:
require ''/badges/'' . $file;
$badge = new $class;
Luego llama al evento predeterminado que pasa los datos recibidos cuando se trigger
;
$badge->default_event($data);
las insignias
Aquí es donde sucede la verdadera magia. cada insignia tiene su propia consulta / lógica para determinar si se debe otorgar una insignia. Cada insignia se establece, 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
función de award
proviene de una Badge
clase extendida que básicamente verifica si el usuario ya recibió esa insignia, de lo contrario, actualizará la tabla db de la insignia. La clase de insignia también se ocupa de recuperar todas las insignias para un usuario y devolverlas en una matriz, etc. (para que las insignias se puedan mostrar, por ejemplo, en el perfil del usuario)
¿Qué pasa cuando el sistema se implementa por primera vez en un sitio ya 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 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 amplía 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 que aunque podríamos "simular" eventos previos, 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 acerca de premiar a otros usuarios en función del evento
La función de award
clase Badge
actúa en user_id
: siempre recibirán 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 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. 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 distintivos para los usuarios de todas las entradas publicadas, digamos que, si se clasificaron dentro de los 5 principales, debe usar el trabajo cron (aunque teniendo en cuenta que esto se actualizará para todos los usuarios, no solo para ese desafío, los resultados fueron publicados para)
Si desea apuntar a un área más específica para actualizar con el trabajo cron, veamos si hay una forma de agregar parámetros de filtrado en el objeto cron job, y obtenga 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 el parámetro no se proporciona.
Los logros pueden ser gravosos y aún más si tiene que agregarlos más adelante, a menos que tenga una clase de 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 de asesinatos en un juego puede tener un premio en 1 para el primer sacrificio, 10 diez asesinatos, 1000 mil asesinatos, etc.
Luego, a la columna vertebral 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.
Se coloca perfectamente en su lugar allí, que en el método apropiado, crea una instancia de tu clase de Achievements
y verifica que el jugador es dueña de uno.
Como construir 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 un int
y luego and
ese int con el bit que ha definido como ese logro para ver si ya lo obtuvieron. De esa forma, usa solo una sola columna int
en la base de datos.
La desventaja de esto es que tienes que tenerlos bien organizados y es probable que necesites hacer algunos comentarios en tu código para que recuerdes a qué corresponde 2 ^ 14 más tarde. 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 sea algo así como
if(((2**$pk) & ($usersAchInt)) > 0){
// fire off the giveAchievement() event
}
De esta forma, puede agregar logros más adelante y encajará bien, simplemente NUNCA cambie la clave principal de los logros ya otorgados.
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. Aquí está la API para un cliente de 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: 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