アチーブメントシステムをコーディングするための最良の方法


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基本的に、ユーザーにバッジが既に付与されているかどうかを確認し、付与されていない場合は、バッジデータベーステーブルを更新します。バッジクラスは、ユーザーのすべてのバッジを取得して配列で返すなどの処理も​​行います(バッジをユーザープロファイルに表示するなど)。

システムがすでに稼働中のサイトに最初に実装されたときはどうですか?

各バッジに追加できる「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-彼らはいつも賞を与えられます。デフォルトでは、バッジはイベントを発生させた人、つまりセッションユーザーIDに授与されます(これはdefault_event関数にも当てはまりますが、CRONジョブは明らかにすべてのユーザーをループし、個別のユーザーを授与します)

それでは、例を見てみましょう。コーディングチャレンジのWebサイトで、ユーザーはコーディングエントリを送信します。次に、管理者はエントリを判断し、完了すると、すべての人が見ることができるように結果をチャレンジページに投稿します。これが発生すると、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関数は引き続き機能します。



2
関連していますが、重複していません。2番目の段落をお読みください。「このウェブサイトでバッジ/達成システムについて多くの話をしているときに私が抱えている問題は、それだけです。それはすべて話であり、コードはありません。実際のコード実装例はどこにありますか?」
ゲイリーグリーン

1
まあ、動作するコードを書くことはある程度までしか実行可能ではありません。実装が複雑になりすぎると、人々が理論だけを提供するのはかなり普通のことだと思います。
ゴードン

回答:


9

私は、ドキュメント指向データベースと呼ばれるものに報酬システムを一度実装しました(これはプレイヤーにとって泥でした)。PHPとMySQLに翻訳された私の実装からのいくつかのハイライト:

  • バッジに関するすべての詳細は、ユーザーデータに保存されます。MySQLを使用する場合、パフォーマンスのために、このデータがデータベース内のユーザーごとに1つのレコードにあることを確認します。

  • 問題の人物が何かをするたびに、コードは指定されたフラグ、たとえばflag( 'POST_MESSAGE')でバッジコードをトリガーします。

  • 1つのイベントがカウンターをトリガーすることもあります。たとえば、投稿数のカウントです。増加カウント( 'POST_MESSAGE')。ここでは、POST_MESSAGEカウントが300を超える場合、たとえば、flag( "300_POST")のように、バッジに報酬を与える必要があることを(フックによって、またはこのメソッドでテストするだけで)チェックできます。

  • フラグ方式では、バッジに報酬を与えるためのコードを配置します。たとえば、フラグ300_POSTが送信された場合、バッジreward_badge( "300_POST")を呼び出す必要があります。

  • flagメソッドでは、ユーザーに以前のフラグも存在させる必要があります。したがって、ユーザーがFIRST_COMMENT、FIRST_POST、FIRST_READを持っている場合はバッジ( "NEW USER")を付与し、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
バッジシステムの古典的な傾向は、新しい統計の新しいフィールドをテーブルに追加することです。すでにテーブルにあるデータから計算できるミラーリングされたデータ(MyISAMテーブルで非常に高速な単純なCOUNT())を格納すると、100%になるため、これは少し簡単な方法であり、悪い考えのように思えます。正確)。パフォーマンスが目標である場合は、更新を行い、現在のたとえばpost_count値を取得して、バッジを付与する必要があるかどうかを確認する必要があります。必要なクエリはCOUNT(*)だけです。より複雑なデータについては、フィールドを追加する正当な理由があることに同意します
Gary Green

5
@Gary Green簡単な方法であるだけでなく、スケーラブルな方法であり、ドキュメントデータベースと互換性があります。正しさに関しては、あなたは正しいですが、バッジシステムの場合、100%正しくて遅いよりも、速くておそらく正しいほうがいいです。1つのカウントはおそらく迅速ですが、システムが拡張され、ユーザーが多い場合、その戦略が成り立つことはありません。
knubo 2010年

1
バッジ定義テーブルと、ユーザーをバッジと現在の進捗状況にリンクするためのリンクテーブルを用意するというアイデアが好きです。noSQLを実行すると、その時点で任意のスキーマにロックされ、バッジのタイプミスが突然見つかった場合、または1000個の新しいバッジが追加された場合に保守できなくなります。バッチプロセスでこれらをより多くのドキュメントストアにキャッシュしてすばやく取得することもできますが、リンクしたままにしておきます。
FlavourScape 2015年

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


1
これはPHPベースですか?質問はPHPに基づいています
Lenin Raj Rajasekaran 2011年

1
PHPバインディングがありますが、サーバー側のコードはPythonで記述されています。
Navraj Chohan 2011

0

成果は、整形式のEventクラスがない限り、後で追加する必要がある場合は、負担が大きくなる可能性があります。

これは、成果を実装する私のテクニックに影響を与えます。

私はそれらを最初に「カテゴリー」に分割するのが好きで、それらの中に達成の層があります。つまり、killsゲームのカテゴリには、最初のキル、10の10のキル、10万のキルなどに対して1の賞が与えられる場合があります。

次に、優れたアプリケーションの背骨に、イベントを処理するクラスがあります。再びキルのあるゲームを想像してみてください。プレイヤーが何かを殺すと、何かが起こります。キルが記録されるなど、Events関係する他の場所に情報をディスパッチできるクラスなど、一元化された場所で処理するのが最適です。

適切な方法でAchievementsクラスをインスタンス化し、プレーヤーが正当なものであることを確認します。

Achievementsクラスを構築するのは簡単です。データベースをチェックして、プレーヤーが次のアチーブメントに必要な数のキルを持っているかどうかを確認するだけです。

Redisを使用してユーザーの実績をBitFieldに保存するのが好きですが、MySQLでも同じ手法を使用できます。つまり、プレーヤーのアチーブメントをとして保存し、int次にandそのintをそのアチーブメントとして定義したビットとともに保存して、プレーヤーがすでにそれを獲得しているかどうかを確認できます。そうすればint、データベース内の1つの列のみを使用します。

これの欠点は、それらを適切に整理する必要があり、2 ^ 14が後で何に対応するかを思い出せるように、コードにコメントを付ける必要がある可能性が高いことです。アチーブメントが独自のテーブルに列挙されている場合pkは、アチーブメントテーブルの主キーである2 ^ pkを実行できます。それはチェックを次のようにします

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

このようにして、後でアチーブメントを追加することができ、それはうまくダブテールになります。すでに授与されたアチーブメントの主キーを変更しないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.