Najlepszy sposób na kodowanie systemu osiągnięć

85

Zastanawiam się nad najlepszym sposobem zaprojektowania systemu osiągnięć do wykorzystania w mojej witrynie. Strukturę bazy danych można znaleźć w Najlepszym sposobie, aby stwierdzić brakujące 3 lub więcej kolejnych rekordów, a ten wątek jest naprawdę rozszerzeniem umożliwiającym uzyskanie pomysłów od programistów.

Problem, który mam z wieloma rozmowami o odznakach / systemach osiągnięć na tej stronie jest po prostu taki - wszystko to mówi i nie ma kodu. Gdzie są rzeczywiste przykłady implementacji kodu?

Proponuję tutaj projekt, do którego, mam nadzieję, ludzie mogliby się przyczynić i mam nadzieję, że stworzy dobry projekt do kodowania rozszerzalnych systemów osiągnięć. Nie mówię, że to jest najlepsze, dalekie od tego, ale to możliwy blok startowy.

Prosimy o zgłaszanie swoich pomysłów.


mój pomysł na projekt systemu

Wygląda na to, że panuje powszechna zgoda co do stworzenia „systemu opartego na zdarzeniach” - za każdym razem, gdy występuje znane zdarzenie, takie jak tworzenie, usuwanie postów itp., Wywołuje klasę zdarzenia w ten sposób.

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

Następnie klasa zdarzenia sprawdza, jakie odznaki „nasłuchują” tego zdarzenia, a następnie przechodzi do tego requirespliku i tworzy instancję tej klasy, na przykład:

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

Następnie wywołuje domyślne zdarzenie, przekazując dane odebrane, gdy triggerzostało wywołane;

$badge->default_event($data);

odznaki

To wtedy dzieje się prawdziwa magia. każda odznaka ma własne zapytanie / logikę w celu określenia, czy należy przyznać odznakę. Każda plakietka ma np. Następujący format:

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

 }
}

awardfunction pochodzi z klasy rozszerzonej, Badgektóra zasadniczo sprawdza, czy użytkownikowi przyznano już tę odznakę, a jeśli nie, zaktualizuje tabelę baz znaczników. Klasa badge zajmuje się również pobieraniem wszystkich identyfikatorów dla użytkownika i zwracaniem ich w tablicy itp. (Tak więc identyfikatory mogą być np. Wyświetlane w profilu użytkownika)

co się stanie, gdy system zostanie wdrożony po raz pierwszy w już działającej witrynie?

Istnieje również zapytanie o zadanie „cron”, które można dodać do każdej odznaki. Powodem tego jest to, że gdy system odznak jest po raz pierwszy wdrażany i uruchamiany, odznaki, które powinny już zostać zdobyte, nie zostały jeszcze przyznane, ponieważ jest to system oparty na wydarzeniach. Dlatego zadanie CRON jest uruchamiane na żądanie dla każdej odznaki, aby nagrodzić wszystko, co ma być. Na przykład zadanie CRON dla powyższego wyglądałoby następująco:

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

}

Ponieważ powyższa klasa cron rozszerza główną klasę badge, może ponownie użyć funkcji logicznej try_award

Powodem, dla którego tworzę wyspecjalizowane zapytanie, jest to, że mogliśmy "zasymulować" poprzednie zdarzenia, tj. Przejrzeć każdy wpis użytkownika i wywołać klasę zdarzenia tak $event->trigger(), jakby była bardzo powolna, szczególnie w przypadku wielu odznak. Dlatego zamiast tego tworzymy zoptymalizowane zapytanie.

jaki użytkownik otrzyma nagrodę? wszystko o nagradzaniu innych użytkowników na podstawie wydarzenia

Funkcja Badgeklasowa awarddziała user_id- zawsze otrzymają nagrodę. Domyślnie odznaka jest przyznawana osobie, która spowodowała wystąpienie zdarzenia, tj. Identyfikator użytkownika sesji (dotyczy to default_eventfunkcji, chociaż zadanie CRON oczywiście przechodzi przez wszystkich użytkowników i nagradza oddzielnych użytkowników)

Weźmy więc przykład, w przypadku wyzwania z kodowaniem, użytkownicy witryny przesyłają swój kod. Administrator następnie ocenia zgłoszenia i po zakończeniu publikuje wyniki na stronie wyzwania, aby wszyscy mogli je zobaczyć. W takim przypadku wywoływane jest zdarzenie POSTED_RESULTS.

Jeśli chcesz przyznać odznaki użytkownikom za wszystkie opublikowane wpisy, powiedzmy, że jeśli znaleźli się w pierwszej piątce, powinieneś użyć zadania cron (chociaż pamiętaj, że zaktualizuje się to dla wszystkich użytkowników, a nie tylko dla tego wyzwania wyniki zostały opublikowane dla)

Jeśli chcesz wybrać bardziej konkretny obszar do aktualizacji za pomocą zadania cron, zobaczmy, czy istnieje sposób na dodanie parametrów filtrowania do obiektu zadania cron i uzyskanie funkcji cron_job, aby z nich korzystać. Na przykład:

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

Funkcja cron będzie nadal działać, nawet jeśli parametr nie zostanie podany.

Gary Green
źródło
Powiązane (może zduplikowane): stackoverflow.com/questions/1744747/achievements-badges-system
Gordon
2
Jest powiązany, ale nie jest zduplikowany. Przeczytaj drugi akapit. „Problem, który mam z wieloma rozmowami o odznakach / systemach osiągnięć na tej stronie jest po prostu taki - wszystko to mówi i nie ma kodu. Gdzie są rzeczywiste przykłady implementacji kodu?”
Gary Green,
1
cóż, napisanie działającego kodu jest wykonalne tylko do pewnego stopnia. Powiedziałbym, że to raczej normalne, że ludzie podają teorię tylko wtedy, gdy jakakolwiek implementacja byłaby zbyt złożona.
Gordon

Odpowiedzi:

9

Wdrożyłem kiedyś system nagród w czymś, co można by nazwać bazą danych zorientowaną na dokumenty (to było błoto dla graczy). Niektóre najważniejsze informacje z mojej implementacji, przetłumaczone na PHP i MySQL:

  • Każdy szczegół dotyczący identyfikatora jest przechowywany w danych użytkownika. Jeśli używasz MySQL, upewniłbym się, że dane te znajdują się w jednym rekordzie na użytkownika w bazie danych w celu zapewnienia wydajności.

  • Za każdym razem, gdy dana osoba coś robi, kod uruchamia kod odznaki z daną flagą, na przykład flagą ('POST_MESSAGE').

  • Jedno zdarzenie może również wyzwolić licznik, na przykład liczbę postów. zwiększ_liczbę ('POST_MESSAGE'). Tutaj możesz sprawdzić (albo przez hak, albo po prostu mając test w tej metodzie), że jeśli liczba POST_MESSAGE jest> 300, powinieneś otrzymać odznakę, na przykład: flag ("300_POST").

  • W metodzie flag umieściłbym kod nagradzający odznaki. Na przykład, jeśli zostanie wysłana flaga 300_POST, wówczas należy wywołać odznakę nagrodę_nagroda („300_POST”).

  • W metodzie flag powinieneś również mieć obecne flagi użytkowników. więc możesz powiedzieć, że gdy użytkownik ma FIRST_COMMENT, FIRST_POST, FIRST_READ, przyznajesz plakietkę („NOWY UŻYTKOWNIK”), a gdy otrzymasz 100_COMMENT, 100_POST, 300_READ, możesz przyznać plakietkę („EXPERIENCED_USER”)

  • Wszystkie te flagi i odznaki muszą być w jakiś sposób przechowywane. Użyj sposobu, w którym myślisz o flagach jako bitach. Jeśli chcesz, aby było to naprawdę wydajnie przechowywane, myśl o nich jako o bitach i użyj poniższego kodu: (Lub możesz po prostu użyć czystego ciągu znaków „000000001111000”, jeśli nie chcesz takiej złożoności).

$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";
  • Dobrym sposobem na przechowywanie dokumentu przez użytkownika jest użycie json i przechowywanie danych użytkowników w jednej kolumnie tekstowej. Użyj json_encode i json_decode do przechowywania / pobierania danych.

  • Aby śledzić aktywność niektórych danych użytkowników, którymi manipulował inny użytkownik, dodaj strukturę danych do elementu i tam również użyj liczników. Na przykład liczba odczytów. Użyj tej samej techniki, co opisana powyżej, do przyznawania odznak, ale aktualizacja powinna oczywiście zostać umieszczona w poście ich właścicieli. (Na przykład artykuł przeczytany 1000 razy znaczek).

Knubo
źródło
1
Klasycznym trendem w systemach odznak jest dodanie nowego pola dla nowej statystyki do tabeli. Wydaje mi się, że jest to trochę łatwe wyjście i zły pomysł, ponieważ przechowywanie lustrzanych danych, które można obliczyć z danych już w tabeli (może proste COUNT (), które jest BARDZO szybkie w tabelach MyISAM, będzie w 100% dokładny). Jeśli Twoim celem była wydajność, musisz dokonać aktualizacji ORAZ wybrać aktualną wartość, np. Post_count, aby sprawdzić, czy odznaka powinna zostać przyznana. Wystarczy jedno zapytanie, COUNT (*). Zgadzam się na bardziej złożone dane, ale byłby dobry powód, aby dodać pole
Gary Green
5
@Gary Green To nie tylko łatwe wyjście, ale także sposób skalowalny i kompatybilny z bazami danych dokumentów. Co do poprawności, masz rację, chociaż dla systemu odznak wolałbym, żeby był szybki i najprawdopodobniej poprawny niż w 100% poprawny i wolny. Pojedyncze liczenie jest prawdopodobnie szybkie, ale kiedy system się skaluje i masz wielu użytkowników, nie jest to możliwe.
Knubo
1
Podoba mi się pomysł posiadania po prostu tabeli definicji znaczków i tabeli linków do łączenia użytkowników z odznakami i ich bieżącymi postępami. Robiąc to, noSQL blokuje Cię w dowolnym schemacie w danym momencie i nie można go konserwować, gdy nagle zostaną znalezione literówki w odznakach lub zostanie dodanych 1000 nowych odznak. Zawsze możesz mieć pamięć podręczną przetwarzania wsadowego, aby przechowywać je w większej części magazynu dokumentów w celu szybkiego pobrania, ale zostawiłbym rzeczy powiązane.
FlavorScape
2

UserInfuser to platforma grywalizacji typu open source, która implementuje usługę oznaczania / punktów. Możesz sprawdzić jego API tutaj: http://code.google.com/p/userinfuser/wiki/API_Documentation

Zaimplementowałem to i starałem się, aby liczba funkcji była minimalna. Oto API dla klienta 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);
}

Efektem końcowym jest przedstawienie danych w znaczący sposób za pomocą widżetów. Te widżety obejmują: skrzynię trofeów, tabelę wyników, kamienie milowe, powiadomienia na żywo, rangę i punkty.

Implementację API można znaleźć tutaj: http://code.google.com/p/userinfuser/source/browse/trunk/serverside/api/api.py

Navraj Chohan
źródło
1
czy to jest oparte na PHP? Pytanie bazuje na PHP
Lenin Raj Rajasekaran,
1
Ma powiązania PHP, ale kod po stronie serwera jest napisany w Pythonie.
Navraj Chohan
0

Osiągnięcia mogą być uciążliwe, a tym bardziej, jeśli będziesz musiał je później dodać, chyba że masz dobrze uformowaną Eventklasę.

To wpisuje się w moją technikę realizacji osiągnięć.

Lubię najpierw dzielić je na „kategorie”, które mają różne poziomy spełnienia. tj. killskategoria w grze może mieć 1 nagrodę za pierwsze zabójstwo, 10 dziesięciu zabójstw, 1000 tysięcy zabójstw itd.

Następnie do kręgosłupa każdej dobrej aplikacji, klasy obsługującej Twoje zdarzenia. Ponownie wyobrażając sobie grę z zabójstwami; kiedy gracz coś zabije, coś się dzieje. Zabójstwo jest odnotowywane itp. I najlepiej jest Eventsto zrobić w scentralizowanej lokalizacji, takiej jak i klasa, która może wysyłać informacje do innych zaangażowanych miejsc.

Idealnie pasuje do tego, że we właściwej metodzie utwórz instancję swojej Achievementsklasy i sprawdź, czy graczowi należy się jeden.

Podczas budowania Achievementsklasy jest to trywialne, po prostu coś, co sprawdza bazę danych, aby zobaczyć, czy gracz ma tyle zabójstw, ile potrzeba do następnego osiągnięcia.

Lubię przechowywać osiągnięcia użytkownika w BitField przy użyciu Redis, ale ta sama technika może być używana w MySQL. Oznacza to, że możesz zapisać osiągnięcia gracza jako wartość, inta następnie andtę wartość z bitem, który zdefiniowałeś jako to osiągnięcie, aby sprawdzić, czy już je zdobył. W ten sposób używa tylko jednej intkolumny w bazie danych.

Wadą tego jest to, że musisz je dobrze zorganizować i prawdopodobnie będziesz musiał wprowadzić kilka komentarzy w swoim kodzie, abyś pamiętał, co odpowiada 2 ^ 14 później. Jeśli twoje osiągnięcia są wyliczone we własnej tabeli, możesz po prostu zrobić 2 ^ pk, gdzie pkjest klucz główny w tabeli osiągnięć. To sprawia, że ​​czek wygląda jak

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

W ten sposób możesz później dodać osiągnięcia i wszystko będzie dobrze pasować, po prostu NIGDY nie zmieniaj klucza głównego już przyznanych osiągnięć.

NappingRabbit
źródło