วิธีที่ดีที่สุดในการเขียนโค้ดระบบ Achievements


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- พวกเขามักจะได้รับรางวัล โดยค่าเริ่มต้นป้ายจะมอบให้กับผู้ที่ทำให้เหตุการณ์เกิดขึ้นเช่นรหัสผู้ใช้เซสชัน (ซึ่งเป็นจริงสำหรับ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 จะยังคงทำงานแม้ว่าจะไม่ได้ระบุพารามิเตอร์ก็ตาม


ที่เกี่ยวข้อง (อาจจะซ้ำกัน): stackoverflow.com/questions/1744747/achievements-badges-system
Gordon

2
มีความเกี่ยวข้องกัน แต่ไม่ซ้ำกัน โปรดอ่านย่อหน้าที่สอง "ปัญหาที่ฉันมีจากการพูดคุยมากมายเกี่ยวกับระบบตรา / รางวัลพิเศษในเว็บไซต์นี้มีเพียงแค่นั้นทั้งหมดคือการพูดคุยและไม่มีโค้ดตัวอย่างการติดตั้งโค้ดจริงๆอยู่ที่ไหน"
Gary Green

1
การเขียนโค้ดที่ใช้งานได้นั้นเป็นไปได้ในระดับหนึ่งเท่านั้น ฉันว่ามันเป็นเรื่องปกติที่ผู้คนจะให้ทฤษฎีกับคุณเพียงครั้งเดียวเมื่อการนำไปใช้งานใด ๆ จะซับซ้อนเกินไป
Gordon

คำตอบ:


9

ฉันได้ติดตั้งระบบรางวัลหนึ่งครั้งในสิ่งที่คุณเรียกว่าฐานข้อมูลเชิงเอกสาร (นี่คือโคลนสำหรับผู้เล่น) ไฮไลท์บางส่วนจากการใช้งานของฉันแปลเป็น PHP และ MySQL:

  • ทุกรายละเอียดเกี่ยวกับตราจะถูกเก็บไว้ในข้อมูลผู้ใช้ หากคุณใช้ MySQL ฉันจะต้องแน่ใจว่าข้อมูลนี้อยู่ในหนึ่งระเบียนต่อผู้ใช้ในฐานข้อมูลเพื่อประสิทธิภาพ

  • ทุกครั้งที่บุคคลที่มีปัญหาทำบางสิ่งบางอย่างรหัสจะเรียกใช้รหัสป้ายด้วยธงที่กำหนดเช่นแฟล็ก ('POST_MESSAGE')

  • เหตุการณ์หนึ่งอาจทำให้เกิดการโต้กลับได้เช่นการนับจำนวนโพสต์ Incre_count ('POST_MESSAGE') ที่นี่คุณสามารถตรวจสอบได้ (ไม่ว่าจะโดยใช้ตะขอหรือเพียงแค่มีการทดสอบในวิธีนี้) ว่าหากจำนวน POST_MESSAGE เท่ากับ> 300 คุณควรได้รับป้ายเช่นธง ("300_POST")

  • ในวิธีการตั้งค่าสถานะฉันจะใส่รหัสเพื่อให้รางวัลป้าย ตัวอย่างเช่นหากแฟล็ก 300_POST ถูกส่งไปควรเรียก badge 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 (*) ฉันเห็นด้วยสำหรับข้อมูลที่ซับซ้อนมากขึ้น แต่ก็มีเหตุผลที่ดีที่จะเพิ่มฟิลด์
Gary Green

5
@ แกรี่กรีนไม่ใช่แค่วิธีง่ายๆเท่านั้น แต่ยังเป็นวิธีที่ปรับขนาดได้และเข้ากันได้กับฐานข้อมูลเอกสาร สำหรับความถูกต้องคุณพูดถูก แต่สำหรับระบบตราฉันอยากให้มันรวดเร็วและน่าจะถูกต้องมากกว่า 100% ถูกต้องและช้า การนับหนึ่งครั้งอาจเป็นไปอย่างรวดเร็ว แต่เมื่อระบบของคุณขยายและคุณมีผู้ใช้จำนวนมากกลยุทธ์นั้นจะไม่ได้รับ
Knubo

1
ฉันชอบแนวคิดของการมีตารางคำจำกัดความของป้ายและตารางลิงก์เพื่อเชื่อมโยงผู้ใช้กับป้ายและความคืบหน้าในปัจจุบัน การทำ noSQL จะล็อกคุณเข้ากับสคีมาใด ๆ ในเวลานั้นและไม่สามารถบำรุงรักษาได้เมื่อพบการพิมพ์ผิดในทันทีหรือมีการเพิ่มป้ายใหม่ 1,000 รายการ คุณสามารถมีแคชของกระบวนการแบทช์เหล่านี้ไว้ในที่เก็บเอกสารเพิ่มเติมได้เสมอเพื่อการเรียกค้นอย่างรวดเร็ว แต่ฉันจะปล่อยให้สิ่งต่างๆเชื่อมโยงกัน
FlavourScape

2

UserInfuser เป็นแพลตฟอร์ม gamification แบบโอเพ่นซอร์สซึ่งใช้บริการการทำเครื่องหมาย / จุด คุณสามารถตรวจสอบ 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
Lenin Raj Rajasekaran

1
มีการผูก PHP แต่โค้ดฝั่งเซิร์ฟเวอร์เขียนด้วย Python
Navraj Chohan

0

ความสำเร็จอาจเป็นภาระและมากยิ่งขึ้นดังนั้นหากคุณต้องเพิ่มในภายหลังเว้นแต่คุณจะมีEventคลาสที่มีรูปร่างดี

ต่อไปนี้เป็นเทคนิคของฉันในการนำความสำเร็จไปใช้

ฉันชอบที่จะแยกพวกเขาออกเป็น 'หมวดหมู่' ก่อนและในนั้นก็มีระดับความสำเร็จ กล่าวคือkillsหมวดหมู่ในเกมอาจมีรางวัลที่ 1 สำหรับการสังหารครั้งแรก 10 การสังหาร 1,000,000 การฆ่าเป็นต้น

จากนั้นไปที่กระดูกสันหลังของแอปพลิเคชันที่ดีชั้นเรียนจะจัดการกิจกรรมของคุณ จินตนาการถึงเกมที่มีการฆ่าอีกครั้ง เมื่อผู้เล่นฆ่าบางสิ่งบางอย่างจะเกิดขึ้น มีการบันทึกการฆ่า ฯลฯ และจัดการได้ดีที่สุดในตำแหน่งส่วนกลางเช่นและEventsชั้นเรียนที่สามารถส่งข้อมูลไปยังสถานที่อื่น ๆ ที่เกี่ยวข้องได้

มันเข้าที่อย่างสมบูรณ์แบบในวิธีการที่เหมาะสมสร้างอินสแตนซ์Achievementsชั้นเรียนของคุณและตรวจสอบว่าผู้เล่นครบกำหนด

เนื่องจากการสร้างAchievementsคลาสนั้นเป็นเรื่องเล็กน้อยมีเพียงบางสิ่งที่ตรวจสอบฐานข้อมูลเพื่อดูว่าผู้เล่นฆ่าได้มากเท่าที่จำเป็นสำหรับความสำเร็จครั้งต่อไปหรือไม่

ฉันชอบเก็บความสำเร็จของผู้ใช้ใน BitField โดยใช้ Redis แต่สามารถใช้เทคนิคเดียวกันนี้ใน MySQL ได้ นั่นคือคุณสามารถจัดเก็บความสำเร็จของผู้เล่นในรูปแบบ an intแล้วandint ด้วยบิตที่คุณกำหนดให้เป็นความสำเร็จนั้นเพื่อดูว่าพวกเขาได้รับแล้ว ด้วยวิธีนี้จะใช้เพียงintคอลัมน์เดียวในฐานข้อมูล

ข้อเสียคือคุณต้องจัดระเบียบให้ดีและคุณอาจต้องแสดงความคิดเห็นในโค้ดของคุณเพื่อที่คุณจะได้จำสิ่งที่ 2 ^ 14 สอดคล้องกันในภายหลัง หากรางวัลพิเศษของคุณถูกระบุไว้ในตารางของพวกเขาคุณสามารถทำได้เพียง 2 ^ pk ซึ่งpkเป็นคีย์หลักของตารางความสำเร็จ นั่นทำให้การตรวจสอบบางอย่างเช่น

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

ด้วยวิธีนี้คุณสามารถเพิ่มรางวัลพิเศษในภายหลังและมันจะประกบกันได้ดีเพียงไม่ต้องเปลี่ยนคีย์หลักของรางวัลพิเศษที่ได้รับไปแล้ว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.