对成就系统进行编码的最佳方式

2022-08-30 08:08:21

我正在考虑设计一个在我的网站上使用的成就系统的最佳方法。数据库结构可以在告诉丢失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()

哪个用户获得奖励?所有关于根据事件奖励其他用户

类函数的作用 - 它们将始终获得奖励。默认情况下,徽章被授予导致事件发生的人,即会话用户ID(对于该功能也是如此,尽管CRON作业显然会遍历所有用户并奖励单独的用户)Badgeawarduser_iddefault_event

因此,让我们举个例子,在编码挑战网站上,用户提交他们的编码条目。然后,管理员判断参赛作品,完成后,将结果发布到挑战页面,供所有人查看。发生这种情况时,将调用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 函数仍将工作。


答案 1

我曾经在所谓的面向文档的数据库中实现了一个奖励系统(这对玩家来说是一个泥泞)。我的实现中的一些亮点,被翻译成PHP和MySQL:

  • 有关徽章的每个详细信息都存储在用户数据中。如果您使用MySQL,我会确保此数据位于数据库中每个用户的一条记录中,以提高性能。

  • 每当有问题的人做某事时,代码就会触发带有给定标志的徽章代码,例如flag('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

UserInfuser是一个开源游戏化平台,它实现了徽章/积分服务。您可以在此处查看其API:http://code.google.com/p/userinfuser/wiki/API_Documentation

我实现了它,并试图将函数的数量保持在最低限度。下面是一个 php 客户端的 API:

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


推荐