Başarı sistemini kodlamanın en iyi yolu


85

Sitemde kullanmak üzere bir başarı sistemi tasarlamanın en iyi yolunu düşünüyorum. Veritabanı yapısı, 3 veya daha fazla ardışık kaydın eksik olduğunu söylemenin en iyi yolu olarak bulunabilir ve bu iş parçacığı, geliştiricilerden fikir almak için gerçekten bir uzantıdır.

Bu web sitesinde rozetler / başarı sistemleri hakkında birçok konuşmayla ilgili sorunum sadece bu - hepsi konuşma ve kod yok. Gerçek kod uygulama örnekleri nerede?

Burada, insanların katkıda bulunabileceğini ve genişletilebilir başarı sistemlerini kodlamak için iyi bir tasarım oluşturmasını umduğum bir tasarım öneriyorum. Bunun en iyisi olduğunu söylemiyorum, ondan çok uzak, ama bu olası bir başlangıç ​​bloğu.

Lütfen fikirlerinizle katkıda bulunmaktan çekinmeyin.


sistem tasarım fikrim

Görünüşe göre genel fikir birliği "olay tabanlı bir sistem" oluşturmaktır - bir gönderi oluşturulduğunda, silindiğinde vb. Bilinen bir olay meydana geldiğinde, olay sınıfını böyle çağırır ..

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

Olay sınıfı daha sonra bu olay için hangi rozetlerin "dinlediğini", sonra o requiresdosyayı bulur ve şu şekilde bu sınıfın bir örneğini oluşturur:

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

Daha sonra trigger, çağrıldığında alınan verileri geçen varsayılan olayı çağırır ;

$badge->default_event($data);

rozetler

İşte o zaman gerçek sihrin gerçekleştiği yer burası. her rozetin, bir rozetin verilip verilmeyeceğini belirlemek için kendi sorgusu / mantığı vardır. Her rozet, örneğin şu biçimde düzenlenmiştir:

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

 }
}

awardişlev, Badgetemelde kullanıcıya rozet verilip verilmediğini kontrol eden, yoksa rozet db tablosunu güncelleyecek olan genişletilmiş bir sınıftan gelir . Rozet sınıfı aynı zamanda bir kullanıcı için tüm rozetleri geri alma ve bir dizi içinde iade etme vb. İle ilgilenir (böylece rozetler örneğin kullanıcı profilinde görüntülenebilir)

Sistem halihazırda yayında olan bir sitede ilk ne zaman uygulandığında ne olacak?

Her rozete eklenebilecek bir "cron" iş sorgusu da vardır. Bunun nedeni, rozet sistemi ilk uygulandığında ve başlatıldığında, zaten kazanılmış olması gereken rozetlerin henüz ödüllendirilmemiş olmasının nedeni, bu olay tabanlı bir sistem olmasıdır. Bu nedenle, olması gereken her şeyi ödüllendirmek için her rozet için talep üzerine bir CRON işi çalıştırılır. Örneğin, yukarıdaki CRON işi şöyle görünecektir:

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

}

Yukarıdaki cron sınıfı, ana rozet sınıfını genişlettiğinden, mantık işlevini yeniden kullanabilir try_award

Bunun için özel bir sorgu oluşturmamın nedeni, önceki olayları "simüle edebilmemize" rağmen, yani her kullanıcı gönderisini gözden geçirip olay sınıfını $event->trigger(), özellikle birçok rozet için çok yavaş olacakmış gibi tetikleyebilmemize rağmen . Bunun yerine optimize edilmiş bir sorgu oluşturuyoruz.

ödülü hangi kullanıcı alır? her şey diğer kullanıcıları etkinliğe göre ödüllendirmekle ilgili

BadgeSınıf awardişlevi görür user_id- her zaman ödül verilecektir. Varsayılan olarak, rozet olayın gerçekleşmesine NEDEN OLAN kişiye verilir, yani oturum kullanıcı kimliği ( default_eventCRON işi açıkça tüm kullanıcılar arasında döngü yapsa da ve ayrı kullanıcıları ödüllendirse de bu işlev için doğrudur )

Öyleyse bir örnek alalım, bir kodlama sorgulaması web sitesinde kullanıcılar kodlama girişlerini gönderirler. Yönetici daha sonra girişleri değerlendirir ve tamamlandığında, herkesin görmesi için sonuçları sorgulama sayfasına gönderir. Bu olduğunda, POSTED_RESULTS olayı çağrılır.

Yayınlanan tüm girişler için kullanıcılara rozet vermek istiyorsanız, diyelim ki, ilk 5 arasında yer almışlarsa, cron işini kullanmalısınız (ancak akılda tutulması gereken bu, yalnızca bu meydan okuma için değil tüm kullanıcılar için güncellenecektir sonuçlar yayınlandı)

Cron işi ile güncellemek için daha spesifik bir alanı hedeflemek istiyorsanız, cron iş nesnesine filtreleme parametreleri eklemenin bir yolu olup olmadığını görelim ve bunları kullanmak için cron_job işlevini alın. Örneğin:

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 işlevi, parametre sağlanmasa bile çalışmaya devam edecektir.



2
İlişkilendirilmiş ancak yinelenmemiş. Lütfen ikinci paragrafı okuyun. "Bu web sitesinde rozetler / başarı sistemleri hakkında pek çok konuşmayla ilgili yaşadığım sorun tam da bu - hepsi konuşma ve kod yok. Gerçek kod uygulama örnekleri nerede?"
Gary Green

1
iyi, çalışma kodu yazmak yalnızca belirli bir dereceye kadar uygulanabilir. İnsanların size sadece teoriyi vermesinin oldukça normal olduğunu söyleyebilirim, herhangi bir uygulama çok karmaşık olduğunda.
Gordon

Yanıtlar:


9

Belge odaklı veri tabanı dediğinizde bir ödül sistemi uyguladım (bu, oyuncular için bir çamurdu). PHP ve MySQL'e çevrilmiş uygulamamdan bazı önemli noktalar:

  • Rozetle ilgili her ayrıntı, kullanıcı verilerinde saklanır. MySQL kullanırsanız, bu verilerin performans için veritabanında kullanıcı başına bir kayıtta olduğundan emin olurdum.

  • Söz konusu kişi her bir şey yaptığında, kod, rozet kodunu belirli bir bayrakla, örneğin bayrakla ('POST_MESSAGE') tetikler.

  • Bir olay, örneğin bir gönderi sayısı gibi bir sayacı da tetikleyebilir. artış_sayısı ('POST_MESSAGE'). Burada, POST_MESSAGE sayısının> 300 olması durumunda bir rozet ödülüne sahip olmanız gerektiğini (bir kancayla veya sadece bu yöntemde bir test yaptırarak) kontrol edebilirsiniz, örneğin: bayrak ("300_POST").

  • Bayrak yönteminde, rozetleri ödüllendirmek için kodu koyardım. Örneğin, 300_POST Bayrak gönderilirse, rozet reward_badge ("300_POST") çağrılmalıdır.

  • Bayrak yönteminde, kullanıcıların önceki bayraklarının da mevcut olması gerekir. Böylece, kullanıcı FIRST_COMMENT, FIRST_POST, FIRST_READ olduğunda rozet ("YENİ KULLANICI") ve 100_COMMENT, 100_POST, 300_READ aldığınızda rozet ("EXPERIENCED_USER") verebilirsiniz.

  • Tüm bu bayrak ve rozetlerin bir şekilde saklanması gerekiyor. Bayrakları bit olarak düşündüğünüz bir yol kullanın. Bunun gerçekten verimli bir şekilde depolanmasını istiyorsanız, bunları bitler olarak düşünür ve aşağıdaki kodu kullanırsınız: (Ya da bu karmaşıklığı istemiyorsanız sadece "000000001111000" çıplak bir dizge kullanabilirsiniz.

$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";
  • Kullanıcı için bir belgeyi saklamanın güzel bir yolu, json kullanmak ve kullanıcı verilerini tek bir metin sütununda saklamaktır. Verileri saklamak / almak için json_encode ve json_decode kullanın.

  • Başka bir kullanıcı tarafından işlenen bazı kullanıcı verilerindeki etkinliği izlemek için, öğeye bir veri yapısı ekleyin ve oradaki sayaçları da kullanın. Örneğin okuma sayısı. Rozet vermek için yukarıda açıklananla aynı tekniği kullanın, ancak güncelleme elbette sahip olan kullanıcıların gönderisine gitmelidir. (Örneğin makale 1000 kez rozet okudu).


1
Rozet sistemlerindeki klasik eğilim, tablonuza yeni istatistik için yeni bir alan eklemektir. Bana göre, bu biraz kolay bir çıkış yolu ve kötü bir fikir gibi görünüyor çünkü tablodaki verilerden hesaplanabilen yansıtılmış verileri depolamanız (MyISAM tablolarında ÇOK hızlı olan basit bir COUNT ()% 100 olacaktır. doğru). Performans hedefinizse, bir rozet verilip verilmeyeceğini kontrol etmek için bir güncelleme yapmanız VE geçerli örneğin post_count değerini almak için seçim yapmanız gerekir. Yalnızca bir sorguya ihtiyacınız olabilir, COUNT (*). Daha karmaşık veriler için bir alan eklemek için iyi bir neden olacağına katılıyorum
Gary Green

5
@Gary Green Sadece kolay bir çıkış yolu değil, aynı zamanda ölçeklenebilir bir yoldur ve belge veritabanları ile uyumludur. Doğruluk konusunda haklısınız, ancak bir rozet sistemi için% 100 doğru ve yavaş olmaktansa hızlı ve büyük olasılıkla doğru olmasını tercih ederim. Tek bir sayım muhtemelen hızlıdır, ancak sisteminiz ölçeklendiğinde ve çok sayıda kullanıcınız olduğunda, bu stratejinin geçerli olduğu verilmez.
Knubo

1
Sadece rozet tanımlama tablosu ve kullanıcıları rozetlere ve mevcut ilerlemelerine bağlamak için bir bağlantı tablosuna sahip olma fikrini seviyorum. Bunu yapmak, noSQL sizi o anda hangi şemaya kilitler ve aniden rozetlerde yazım hataları bulunduğunda veya 1000 yeni rozet eklendiğinde sürdürülemez. Hızlı erişim için her zaman bir toplu işlem önbelleğine bunları daha fazla belge deposuna kaydedebilirsiniz, ancak işleri bağlantılı bırakırım.
FlavourScape

2

UserInfuser, rozet / puan hizmeti uygulayan açık kaynaklı bir oyunlaştırma platformudur. API'sini buradan kontrol edebilirsiniz: http://code.google.com/p/userinfuser/wiki/API_Documentation

Bunu uyguladım ve işlev sayısını minimumda tutmaya çalıştım. İşte bir php istemcisi için 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);
}

Sonuç, verileri widget kullanarak anlamlı bir şekilde göstermektir. Bu widget'lar şunları içerir: kupa durumu, liderlik tablosu, kilometre taşları, canlı bildirimler, rütbe ve puanlar.

API'nin uygulaması şu adreste bulunabilir: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py


1
bu PHP tabanlı mı? Soru PHP'ye dayanıyor
Lenin Raj Rajasekaran

1
PHP bağlamalarına sahiptir, ancak sunucu tarafı kodu Python'da yazılmıştır.
Navraj Chohan

0

Başarılar külfetli olabilir ve iyi biçimlendirilmiş bir Eventsınıfınız yoksa daha sonra eklemeniz gerekirse daha da zor olabilir .

Bu, başarıları uygulama tekniğime ayrılıyor.

Bunları önce 'kategorilere' ayırmayı seviyorum ve bunların içinde başarı aşamaları var. yani killsoyundaki bir kategori ilk öldürme, 10 on öldürme, 1000 bin öldürme vb. için 1'de ödül alabilir.

Sonra herhangi bir iyi uygulamanın omurgasına, olaylarınızı ele alan sınıf. Yine öldürmelerle bir oyun hayal etmek; bir oyuncu bir şeyi öldürdüğünde işler olur. Öldürme not edilir, vb ve bu en iyi şekilde merkezi bir konumda ele alınır, örneğin veEvents dahil olan diğer yerlere bilgi gönderebilen bir sınıf .

Burada, uygun yöntemle, Achievementssınıfınızı somutlaştırın ve oyuncunun vadesi dolduğunu kontrol edin.

AchievementsSınıfı inşa ederken bu önemsizdir, oyuncunun bir sonraki başarı için gerekli olan sayıda öldürme yapıp yapmadığını görmek için veritabanını kontrol eden bir şey.

Kullanıcıların başarılarını Redis kullanarak bir BitField'da saklamayı seviyorum, ancak aynı teknik MySQL'de de kullanılabilir. Yani, oyuncunun başarılarını bir intve sonra ando başarı olarak tanımladığınız bit ile o int olarak kaydedip kazanmış olup olmadıklarını görebilirsiniz. Bu şekilde int, veritabanında yalnızca tek bir sütun kullanır .

Bunun dezavantajı, onları iyi organize etmek zorunda olmanız ve büyük olasılıkla kodunuzda bazı yorumlar yapmanız gerekecek, böylece 2 ^ 14'ün neye karşılık geldiğini daha sonra hatırlayacaksınız. Başarılarınız kendi tablosunda numaralandırılmışsa pk, başarılar tablosunun birincil anahtarı olan 2 ^ pk'yi yapabilirsiniz . Bu, çeki şunun gibi yapar:

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

Bu şekilde, daha sonra başarılar ekleyebilir ve başarılı olur, sadece zaten ödüllendirilmiş olan başarıların birincil anahtarını ASLA değiştirmeyin.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.