Лучший способ кодировать систему достижений


85

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

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

Также к каждому значку можно добавить запрос о работе 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Класс awardфункция действует на user_id- они всегда будут даны награды. По умолчанию значок присуждается человеку, который ВЫЗВИЛ событие, т.е. идентификатор пользователя сеанса (это верно для default_eventфункции, хотя задание CRON, очевидно, проходит через всех пользователей и награждает отдельных пользователей)

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

Если вы хотите наградить пользователей значками за все опубликованные записи, скажем, если они попали в пятерку лучших, вам следует использовать задание 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 по-прежнему будет работать, даже если параметр не указан.


Связанные (возможно, дублированные): stackoverflow.com/questions/1744747/achievements-badges-system
Gordon

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

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

Ответы:


9

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

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

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

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

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

  • В методе флага вы также должны иметь предыдущие флаги пользователей. чтобы вы могли сказать, что когда у пользователя есть 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 раз).


1
Классическая тенденция в системах значков - добавить в таблицу новое поле для новой статистики. Мне это кажется легким выходом и плохой идеей, потому что вы храните зеркальные данные, которые можно вычислить на основе данных, уже находящихся в таблице (возможно, простой COUNT (), который ОЧЕНЬ быстро работает в таблицах MyISAM, будет на 100% точный). Если вашей целью была производительность, вам нужно выполнить обновление И выбрать, чтобы получить текущее, например, значение post_count, чтобы проверить, следует ли присуждать значок. Вам может понадобиться только один запрос, COUNT (*). Я согласен, что для более сложных данных была бы веская причина добавить поле
Гэри Грин

5
@Gary Green Это не только простой выход, это еще и масштабируемый способ, совместимый с базами данных документов. Что касается правильности, вы правы, хотя для системы значков я бы предпочел, чтобы она была быстрой и, скорее всего, правильной, чем 100% правильной и медленной. Один подсчет, вероятно, будет быстрым, но когда ваша система масштабируется и у вас много пользователей, это не означает, что стратегия работает.
Knubo

1
Мне нравится идея иметь просто таблицу определения значков и таблицу ссылок, чтобы связывать пользователей со значками и их текущим прогрессом. При этом noSQL блокирует вас в любой схеме в то время и не обслуживается, когда внезапно обнаруживаются опечатки в значках или добавляется 1000 новых значков. У вас всегда может быть пакетный процесс, кэширующий их в хранилище документов для быстрого поиска, но я бы оставил все связанными.
FlavorScape

2

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


1
это PHP на основе? Вопрос основан на PHP
Ленин Радж Раджасекаран

1
Он имеет привязку к PHP, но код на стороне сервера написан на Python.
Наврадж Чохан

0

Достижения могут быть обременительными, и тем более, если вам придется добавить их позже, если у вас нет хорошо сформированного 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 
} 

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.