Cách tốt nhất để mã hệ thống Thành tích


85

Tôi đang nghĩ đến cách tốt nhất để thiết kế hệ thống thành tích để sử dụng trên trang web của mình. Cấu trúc cơ sở dữ liệu có thể được tìm thấy tại Cách tốt nhất để biết 3 hoặc nhiều bản ghi liên tiếp bị thiếu và chủ đề này thực sự là một phần mở rộng để lấy ý tưởng từ các nhà phát triển.

Vấn đề mà tôi gặp phải khi nói nhiều về huy hiệu / hệ thống thành tích trên trang web này chỉ là - tất cả chỉ là nói và không có mã. Đâu là ví dụ triển khai mã thực tế?

Tôi đề xuất ở đây một thiết kế mà tôi hy vọng mọi người có thể đóng góp và hy vọng tạo ra một thiết kế tốt để mã hóa các hệ thống thành tích có thể mở rộng. Tôi không nói đây là điều tốt nhất, xa nó, nhưng đó là một bước khởi đầu khả thi.

Xin vui lòng đóng góp ý kiến ​​của bạn.


ý tưởng thiết kế hệ thống của tôi

Có vẻ như sự đồng thuận chung là tạo ra một "hệ thống dựa trên sự kiện" - bất cứ khi nào một sự kiện đã biết xảy ra như một bài đăng được tạo, bị xóa, v.v. nó sẽ gọi lớp sự kiện như vậy ..

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

Sau đó, lớp sự kiện sẽ tìm ra những huy hiệu nào đang "lắng nghe" sự kiện này, sau đó requireslà tệp đó và tạo một thể hiện của lớp đó, như sau:

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

Sau đó, nó gọi sự kiện mặc định truyền dữ liệu nhận được khi triggerđược gọi;

$badge->default_event($data);

những huy hiệu

Đây là nơi điều kỳ diệu thực sự xảy ra. mỗi huy hiệu có truy vấn / logic riêng để xác định xem có nên trao huy hiệu hay không. Mỗi huy hiệu được đặt ở dạng ví dụ như sau:

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

 }
}

awardhàm đến từ một lớp mở rộng Badgevề cơ bản sẽ kiểm tra xem liệu người dùng đã được trao huy hiệu đó chưa, nếu chưa, sẽ cập nhật bảng db huy hiệu. Lớp huy hiệu cũng đảm nhận việc truy xuất tất cả các huy hiệu cho một người dùng và trả lại nó trong một mảng, v.v. (vì vậy, các huy hiệu có thể được hiển thị trên hồ sơ người dùng)

còn khi hệ thống được triển khai lần đầu tiên trên một trang đã hoạt động thì sao?

Ngoài ra còn có một truy vấn công việc "cron" có thể được thêm vào mỗi huy hiệu. Lý do cho điều này là vì khi hệ thống huy hiệu được triển khai và bắt đầu lần đầu tiên, các huy hiệu đáng lẽ đã kiếm được vẫn chưa được trao vì đây là hệ thống dựa trên sự kiện. Vì vậy, một công việc CRON được thực hiện theo yêu cầu đối với mỗi huy hiệu để trao bất cứ thứ gì cần thiết. Ví dụ, công việc CRON ở trên sẽ giống như sau:

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

}

Khi lớp cron ở trên mở rộng lớp huy hiệu chính, nó có thể sử dụng lại hàm logic try_award

Lý do tại sao tôi tạo một truy vấn chuyên biệt cho việc này là mặc dù chúng tôi có thể "mô phỏng" các sự kiện trước đó, tức là đi qua mọi bài đăng của người dùng và kích hoạt lớp sự kiện giống như $event->trigger()nó sẽ rất chậm, đặc biệt là đối với nhiều huy hiệu. Vì vậy, thay vào đó chúng tôi tạo một truy vấn được tối ưu hóa.

người dùng nào nhận được giải thưởng? tất cả về giải thưởng cho người dùng khác dựa trên sự kiện

Các Badgelớp awardchức năng hoạt động trên user_id- họ sẽ luôn được trao giải thưởng. Theo mặc định, huy hiệu được trao cho người ĐÃ LÀM sự kiện xảy ra tức là id người dùng phiên (điều này đúng với default_eventhàm, mặc dù công việc CRON hiển nhiên lặp lại qua tất cả người dùng và giải thưởng cho người dùng riêng biệt)

Vì vậy, hãy lấy một ví dụ, trên một trang web thử thách mã hóa, người dùng gửi bài viết mã của họ. Sau đó, quản trị viên sẽ đánh giá các mục và khi hoàn thành, đăng kết quả lên trang thử thách để mọi người cùng xem. Khi điều này xảy ra, sự kiện POSTED_RESULTS được gọi.

Nếu bạn muốn trao huy hiệu cho người dùng cho tất cả các mục đã đăng, giả sử, nếu họ được xếp hạng trong top 5, bạn nên sử dụng cron job (mặc dù hãy nhớ rằng điều này sẽ cập nhật cho tất cả người dùng, không chỉ cho thử thách đó kết quả đã được đăng cho)

Nếu bạn muốn nhắm mục tiêu một khu vực cụ thể hơn để cập nhật cron job, hãy xem có cách nào để thêm các tham số lọc vào đối tượng cron job và lấy hàm cron_job để sử dụng chúng không. Ví dụ:

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

Hàm cron sẽ vẫn hoạt động ngay cả khi tham số không được cung cấp.



2
Nó có liên quan nhưng không trùng lặp. Mời bạn đọc đoạn thứ hai. "Vấn đề mà tôi gặp phải khi nói nhiều về huy hiệu / hệ thống thành tích trên trang web này chỉ là - tất cả chỉ là nói và không có mã. Ví dụ triển khai mã thực tế ở đâu?"
Gary Green

1
tốt, việc viết mã làm việc chỉ khả thi ở một mức độ nhất định. Tôi muốn nói rằng việc mọi người chỉ đưa cho bạn lý thuyết là điều bình thường, một khi mọi việc triển khai sẽ quá phức tạp.
Gordon

Câu trả lời:


9

Tôi đã triển khai một hệ thống phần thưởng một lần trong cái mà bạn sẽ gọi là cơ sở dữ liệu hướng tài liệu (đây là một vũng bùn cho người chơi). Một số điểm nổi bật từ việc triển khai của tôi, được dịch sang PHP và MySQL:

  • Mọi chi tiết về huy hiệu được lưu trữ trong dữ liệu người dùng. Nếu bạn sử dụng MySQL, tôi sẽ đảm bảo rằng dữ liệu này nằm trong một bản ghi cho mỗi người dùng trong cơ sở dữ liệu cho hiệu suất.

  • Mỗi khi người được đề cập làm điều gì đó, mã sẽ kích hoạt mã huy hiệu với một cờ nhất định, ví dụ: cờ ('POST_MESSAGE').

  • Một sự kiện cũng có thể kích hoạt bộ đếm, ví dụ như số lượng bài đăng. tăng_count ('POST_MESSAGE'). Tại đây, bạn có thể kiểm tra (bằng móc hoặc chỉ kiểm tra trong phương pháp này) rằng nếu số POST_MESSAGE> 300 thì bạn sẽ có phần thưởng là huy hiệu, ví dụ: flag ("300_POST").

  • Trong phương pháp cờ, tôi sẽ đặt mã để thưởng huy hiệu. Ví dụ: nếu Cờ 300_POST được gửi, thì huy hiệu Rew_badge ("300_POST") sẽ được gọi.

  • Trong phương thức cờ, bạn cũng nên có các cờ trước đó của người dùng. vì vậy bạn có thể nói khi người dùng có FIRST_COMMENT, FIRST_POST, FIRST_READ bạn sẽ cấp huy hiệu ("NGƯỜI DÙNG MỚI") và khi bạn nhận được 100_COMMENT, 100_POST, 300_READ, bạn có thể cấp huy hiệu ("EXPERIENCED_USER")

  • Tất cả các cờ và huy hiệu này cần được lưu trữ bằng cách nào đó. Sử dụng một số cách mà bạn nghĩ các cờ dưới dạng bit. Nếu bạn muốn điều này được lưu trữ thực sự hiệu quả, bạn hãy nghĩ về chúng dưới dạng các bit và sử dụng mã bên dưới: (Hoặc bạn có thể chỉ sử dụng một chuỗi trần "000000001111000" nếu bạn không muốn sự phức tạp này).

$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";
  • Một cách hay để lưu trữ tài liệu cho người dùng là sử dụng json và lưu trữ dữ liệu người dùng trong một cột văn bản. Sử dụng json_encode và json_decode để lưu trữ / truy xuất dữ liệu.

  • Để theo dõi hoạt động trên một số người dùng, dữ liệu do một số người dùng khác thao tác, hãy thêm cấu trúc dữ liệu trên mặt hàng và sử dụng bộ đếm ở đó. Ví dụ, số lần đọc. Sử dụng kỹ thuật tương tự như được mô tả ở trên để trao huy hiệu, nhưng bản cập nhật tất nhiên phải đi vào bài đăng của người dùng sở hữu. (Ví dụ bài báo đã đọc huy hiệu 1000 lần).


1
Xu hướng cổ điển trong hệ thống huy hiệu là thêm trường mới cho thống kê mới vào bảng của bạn. Đối với tôi, điều đó có vẻ là một cách dễ dàng và là một ý tưởng tồi vì việc lưu trữ dữ liệu được sao chép của bạn có thể được tính toán từ dữ liệu đã có trong bảng (có thể là COUNT () đơn giản, rất nhanh trên các bảng MyISAM, sẽ là 100% chính xác). Nếu hiệu suất là mục tiêu của bạn, bạn sẽ cần cập nhật VÀ chọn lấy giá trị hiện tại, ví dụ: post_count để kiểm tra xem có nên trao huy hiệu hay không. Bạn có thể chỉ cần một truy vấn, COUNT (*). Tôi đồng ý cho dữ liệu phức tạp hơn sẽ có lý do chính đáng để thêm một lĩnh vực mặc dù
Gary Xanh

5
@Gary Green Đây không chỉ là một lối thoát dễ dàng mà còn là cách có thể mở rộng và tương thích với cơ sở dữ liệu tài liệu. Đối với tính đúng đắn, bạn nói đúng, mặc dù đối với một hệ thống huy hiệu, tôi muốn nó nhanh chóng và rất có thể chính xác hơn là đúng và chậm 100%. Một lần đếm có thể nhanh chóng, nhưng khi hệ thống của bạn mở rộng quy mô và bạn có nhiều người dùng thì chiến lược đó không được giữ nguyên.
Knubo

1
Tôi thích ý tưởng chỉ có bảng định nghĩa huy hiệu và bảng liên kết để liên kết người dùng với huy hiệu và tiến trình hiện tại của họ. Làm điều đó noSQL sẽ khóa bạn vào bất kỳ lược đồ nào tại thời điểm đó và không thể bảo trì khi đột nhiên phát hiện thấy lỗi chính tả trong các huy hiệu hoặc 1000 huy hiệu mới được thêm vào. Bạn luôn có thể có một quá trình hàng loạt lưu vào bộ nhớ cache những thứ này vào nhiều kho tài liệu hơn để truy xuất nhanh chóng, nhưng tôi sẽ để mọi thứ được liên kết.
FlavorScape

2

UserInfuser là một nền tảng trò chơi mã nguồn mở triển khai dịch vụ huy hiệu / điểm. Bạn có thể xem API của nó tại đây: http://code.google.com/p/userinfuser/wiki/API_Documentation

Tôi đã triển khai nó và cố gắng giữ số lượng chức năng ở mức tối thiểu. Đây là API cho một ứng dụng khách 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);
}

Kết quả cuối cùng là hiển thị dữ liệu một cách có ý nghĩa thông qua việc sử dụng các widget. Các tiện ích này bao gồm: hộp đựng cúp, bảng thành tích, các cột mốc, thông báo trực tiếp, xếp hạng và điểm.

Việc triển khai API có thể được tìm thấy tại đây: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py


1
này có dựa trên PHP không? Câu hỏi đặt ra là dựa trên PHP
Lenin Raj Rajasekaran

1
Nó có các ràng buộc PHP, nhưng mã phía máy chủ được viết bằng Python.
Navraj Chohan

0

Thành tích có thể rất nặng nề và thậm chí còn hơn thế nữa nếu bạn phải bổ sung chúng sau này, trừ khi bạn có một Eventlớp học tốt.

Điều này đúc kết thành kỹ thuật thực hiện thành tựu của tôi.

Trước tiên, tôi muốn chia chúng thành 'danh mục' và trong những danh mục đó có các cấp thành tích. tức là một killshạng mục trong trò chơi có thể có giải thưởng ở mức 1 cho lần tiêu diệt đầu tiên, 10 lần tiêu diệt, 1000 nghìn lần tiêu diệt, v.v.

Sau đó, cốt lõi của bất kỳ ứng dụng tốt nào, lớp xử lý các sự kiện của bạn. Một lần nữa tưởng tượng về một trò chơi có giết; khi một người chơi giết thứ gì đó, mọi thứ sẽ xảy ra. Việc giết được ghi nhận, v.v. và điều đó được xử lý tốt nhất ở một vị trí tập trung, giống như và Eventslớp có thể gửi thông tin đến những nơi khác có liên quan.

Nó hoàn toàn đúng vào vị trí ở đó, theo phương pháp thích hợp, khởi tạo Achievementslớp của bạn và kiểm tra xem người chơi có phải là lớp hay không.

Vì việc xây dựng Achievementslớp học là một việc nhỏ, chỉ cần một thứ gì đó kiểm tra cơ sở dữ liệu để xem liệu người chơi có bao nhiêu lần giết được yêu cầu cho thành tích tiếp theo hay không.

Tôi muốn lưu trữ thành tích của người dùng trong BitField bằng Redis nhưng kỹ thuật tương tự có thể được sử dụng trong MySQL. Đó là, bạn có thể lưu trữ thành tích của người chơi dưới dạng một intvà sau đó andlà int với bit bạn đã xác định là thành tích đó để xem liệu họ đã đạt được chưa. Bằng cách đó, nó chỉ sử dụng một intcột duy nhất trong cơ sở dữ liệu.

Nhược điểm của điều này là bạn phải sắp xếp chúng thật tốt và bạn có thể sẽ cần phải đưa ra một số nhận xét trong mã của mình để sau này bạn sẽ nhớ 2 ^ 14 tương ứng với cái gì. Nếu thành tích của bạn được liệt kê trong bảng của riêng chúng thì bạn chỉ cần thực hiện 2 ^ pk đâu pklà khóa chính của bảng thành tích. Điều đó làm cho séc giống như

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

Bằng cách này, bạn có thể thêm thành tích sau này và nó sẽ hiệu quả, chỉ cần KHÔNG BAO GIỜ thay đổi khóa chính của những thành tích đã được trao.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.