Лучший способ кодирования системы достижений - PullRequest
85 голосов
/ 16 ноября 2010

Я думаю о лучшем способе разработки системы достижений для использования на моем сайте. Структуру базы данных можно найти по адресу Лучший способ указать, что 3 или более последовательных записей отсутствуют , и этот поток действительно является расширением для получения идей от разработчиков.

Проблема, с которой я часто сталкиваюсь на бейджах / системах достижений на этом веб-сайте, заключается в том, что это все разговоры, а не код. Где реальные примеры реализации кода?

Я предлагаю здесь проект, который, я надеюсь, люди могли бы внести и надеяться создать хороший дизайн для кодирования расширяемых систем достижений. Я не говорю, что это лучший, далеко не так, но это возможный стартовый блок.

Пожалуйста, не стесняйтесь вносить свои идеи.


моя идея дизайна системы

Похоже, что общий консенсус заключается в том, чтобы создать "систему, основанную на событиях" - всякий раз, когда происходит известное событие, такое как сообщение, которое создается, удаляется и т. Д., Оно вызывает класс события следующим образом.

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

Затем класс события выясняет, какие значки «слушают» это событие, затем он requires этот файл и создает экземпляр этого класса, например:

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

Затем вызывается событие по умолчанию, передающее данные, полученные при вызове trigger;

$badge->default_event($data);

значки

Именно тогда происходит настоящее волшебство. каждый значок имеет свой собственный запрос / логику, чтобы определить, следует ли награждать значок. Каждый значок указан, например, в этот формат:

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 происходит из расширенного класса Badge, который в основном проверяет, получит ли пользователь этот значок, если нет, обновит ли его таблицу базы данных. Класс значков также заботится о получении всех значков для пользователя и их возврате в массив и т. Д. (Таким образом, значки могут быть, например, отображены в профиле пользователя)

как насчет того, когда система будет впервые внедрена на уже работающем сайте?

Существует также запрос задания cron, который можно добавить к каждому значку. Причина этого заключается в том, что когда система бейджей впервые внедряется и инициализируется, бейджи, которые должны были быть уже заработаны, еще не присуждаются, потому что это система, основанная на событиях. Таким образом, задание CRON запускается по требованию для каждого значка, чтобы награждать все, что должно быть. Например, задание CRON для вышеупомянутого будет выглядеть так:

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

}

Поскольку вышеуказанный класс cron расширяет основной класс значков, он может повторно использовать логическую функцию try_award

Причина, по которой я создаю специализированный запрос для этого, заключается в том, что мы могли «имитировать» предыдущие события, то есть проходить через каждую запись пользователя и запускать класс событий, такой как $event->trigger(), это было бы очень медленно, особенно для многих значков. Поэтому мы вместо этого создаем оптимизированный запрос.

Какой пользователь получает награду? все о награждении других пользователей на основе события

Функция Badge class award действует на user_id - они всегда будут получать награду. По умолчанию значок вручается тому, кто ПРИЧИНА событие произошло, т.е. идентификатор пользователя сеанса (это верно для функции default_event, хотя задание CRON, очевидно, проходит через всех пользователей и награждает отдельных пользователей)

Итак, давайте возьмем пример, на веб-сайте задачи кодирования пользователи отправляют свои записи кодирования. Затем администратор оценивает записи и по завершении публикует результаты на странице испытаний для всеобщего обозрения. Когда это происходит, вызывается событие POSTED_RESULTS.

Если вы хотите наградить пользователей значками за все опубликованные записи, скажем, если они были ранжированы в топ-5, вам следует использовать задание cron (хотя само по себе это будет обновляться для всех пользователей, а не только которые оспаривали результаты были опубликованы для)

Если вы хотите указать более конкретную область для обновления с помощью задания cron, давайте посмотрим, есть ли способ добавить параметры фильтрации в объект задания cron и получить функцию cron_job для их использования. Например:

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

Функция cron будет работать, даже если параметр не указан.

Ответы [ 3 ]

9 голосов
/ 18 ноября 2010

Я однажды внедрил систему вознаграждений в том, что вы бы назвали базой данных, ориентированной на документы (это было грязью для игроков).Некоторые основные моменты из моей реализации, переведенные на PHP и MySQL:

  • Каждая деталь о значке хранится в данных пользователя.Если вы используете MySQL, я бы удостоверился, что эти данные находятся в одной записи на пользователя в базе данных для производительности.

  • Каждый раз, когда рассматриваемый человек что-то делает, код запускает код значка с заданным флагом, например, флагом ('POST_MESSAGE').

  • Одно событие также может вызвать счетчик, например, количество сообщений.increase_count ( 'POST_MESSAGE').Здесь вы можете проверить (либо с помощью хука, либо просто с помощью теста в этом методе), что если счет POST_MESSAGE> 300, то вы должны получить награду, например: flag («300_POST»).

  • В методе флага я бы добавил код для награждения значков.Например, если отправлен флаг 300_POST, то должен быть вызван значок reward_badge ("300_POST").

  • В методе flag также должны быть представлены предыдущие флаги пользователей.так что вы можете сказать, что когда у пользователя есть FIRST_COMMENT, FIRST_POST, FIRST_READ, вы предоставляете значок («НОВЫЙ ПОЛЬЗОВАТЕЛЬ»), а когда вы получаете 100_COMMENT, 100_POST, 300_READ, вы можете предоставлять значок («EXPERIENCED_USER»)

  • Все эти флаги и значки нужно как-то хранить.Используйте какой-нибудь способ, где вы думаете о флагах как о битах.Если вы хотите, чтобы это было действительно эффективно сохранено, вы думаете о них как о битах и ​​используете следующий код: (Или вы можете просто использовать пустую строку «000000001111000», если вам не нужна эта сложность.

$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";
  • Хороший способ хранения документа для пользователя - использовать json и хранить данные пользователей в одном текстовом столбце. Используйте json_encode и json_decode длясохранять / извлекать данные.

  • Для отслеживания активности некоторых данных пользователей, которыми манипулирует какой-либо другой пользователь, добавьте структуру данных в элемент и также используйте там счетчики. Например, прочитайтеИспользуйте тот же метод, который описан выше для присуждения значков, но обновление, конечно же, должно быть в сообщении пользователя-владельца (например, статья прочитана 1000 раз, значок).

2 голосов
/ 15 июня 2011

UserInfuser - это игровая платформа с открытым исходным кодом, в которой реализован сервис бэйджинга / очков. Вы можете проверить его API здесь: http://code.google.com/p/userinfuser/wiki/API_Documentation

Я реализовал это и попытался сохранить минимальное количество функций. Вот API для 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);
}

Конечный результат - показать данные осмысленным образом с помощью виджетов. Эти виджеты включают в себя: трофей, список лидеров, вехи, живые уведомления, ранг и очки.

Реализацию API можно найти здесь: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py

0 голосов
/ 22 декабря 2017

Достижения могут быть обременительными и даже более, если вам придется добавить их позже, если у вас нет хорошо сформированного Event класса.

Это связано с моей техникой реализации достижений.

Мне нравится сначала разбивать их на «категории», и внутри них есть уровни достижений. то есть категория kills в игре может иметь награду 1 за первое убийство, 10 десяти убийств, 1000 тысяч убийств и т. д.

Затем, к любому хорошему приложению, классу, обрабатывающему ваши события. Снова воображая игру с убийствами; когда игрок что-то убивает, вещи случаются. Отмечено уничтожение и т. Д., И это лучше всего обрабатывать в централизованном месте, например, в классе Events, который может отправлять информацию в другие вовлеченные места.

Там все становится на свои места, что в надлежащем методе создайте экземпляр своего класса Achievements и проверьте, что игрок должен один.

Как и создание класса Achievements, это тривиально, просто то, что проверяет базу данных, чтобы увидеть, есть ли у игрока столько убийств, сколько требуется для следующего достижения.

Мне нравится хранить достижения пользователя в BitField с использованием Redis, но ту же технику можно использовать в MySQL. То есть вы можете сохранить достижения игрока как int, а затем and, что int, с битом, который вы определили как это достижение, чтобы увидеть, получили ли они его уже. Таким образом, он использует только один столбец int в базе данных.

Недостатком этого является то, что они должны быть хорошо организованы, и вам, вероятно, нужно будет сделать несколько комментариев в своем коде, чтобы вы помнили, что 2 ^ 14 соответствует позже. Если ваши достижения перечислены в их собственной таблице, то вы можете просто выполнить 2 ^ pk, где pk является первичным ключом таблицы достижений. Это делает проверку что-то вроде

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

Таким образом, вы можете добавить достижения позже, и это будет прекрасно, просто НИКОГДА не меняйте первичный ключ уже присужденных достижений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...