VulCode

RimShot

Platforma humoru, na której treści walczą w parach: użytkownik widzi dwa memy, gify albo żarty z tej samej kategorii i wybiera zabawniejszy. Każda kategoria ma własny ranking Elo, a obok niego ranking oficjalny, który codziennie traci 20 procent punktów, żeby stary hit nie siedział na szczycie wiecznie. Zaproszenia, zgłoszenia z automatycznym ukrywaniem i kolejka moderacji pilnują, co trafia do głosowania.

TypPlatforma z treściami użytkowników
FrontendReact 19, TypeScript, Vite
BackendExpress, TypeScript, Prisma
BazaSQLite
RankingElo w każdej kategorii
RimShot

Wprowadzenie

RimShot to platforma, na którą ludzie wrzucają śmieszne rzeczy, memy, gify, obrazki i żarty, a o tym, co jest najzabawniejsze, decydują pojedynki. Nie ma tu gwiazdek ani lajków. Użytkownik dostaje dwie treści z tej samej kategorii, wybiera zabawniejszą, a zwycięzca zyskuje punkty kosztem przegranego, tak jak w rankingu szachowym.

Nazwa pochodzi od uderzenia w perkusję, które pada po puencie dowcipu. Każde starcie kończy się właśnie takim werdyktem: ten żart wypalił, tamten nie.

Dlaczego pojedynki, a nie gwiazdki

Ocena w skali gwiazdek mówi więcej o oceniającym niż o treści: jeden daje piątki wszystkiemu, drugi nigdy. Polubienia premiują to, co zobaczyło najwięcej osób, więc stary hit rośnie bez końca, a świeży mem ginie, zanim ktokolwiek go zobaczy.

Porównanie dwóch rzeczy obok siebie jest prostsze i uczciwsze. Nie trzeba niczego kalibrować ani zastanawiać się, czy coś zasługuje na trzy, czy na cztery gwiazdki, wystarczy wskazać, która z dwóch rzeczy jest zabawniejsza. Z wielu takich prostych decyzji wychodzi ranking, którego nie da się ustawić jednym głosem.

Elo w każdej kategorii osobno

Każda treść ma ranking Elo tylko w swojej kategorii, bo humoru gifa nie da się sensownie porównać z humorem jednolinijkowego żartu. Wynik pojedynku zmienia ranking obu stron o tyle, na ile był niespodziewany: wygrana z faworytem daje dużo, wygrana ze słabszym niewiele.

Nowe treści mają wyższy współczynnik K, więc szybko trafiają w okolice swojego miejsca. Po zebraniu wystarczającej liczby porównań współczynnik spada, wynik się stabilizuje, a jeden viralowy wieczór nie wynosi treści na szczyt na stałe.

elo.ts · ts
const PROVISIONAL_VOTES = 30;
const PROVISIONAL_K = 40;
const STABLE_K = 18;

export function kFactor(votesCount: number): number {
  return votesCount < PROVISIONAL_VOTES ? PROVISIONAL_K : STABLE_K;
}

export function expectedScore(ratingSelf: number, ratingOpponent: number): number {
  return 1 / (1 + 10 ** ((ratingOpponent - ratingSelf) / 400));
}

export function applyEloUpdate(eloA: number, votesA: number, eloB: number, votesB: number, winner: 'A' | 'B') {
  const expectedA = expectedScore(eloA, eloB);
  const expectedB = 1 - expectedA;
  const scoreA = winner === 'A' ? 1 : 0;
  const scoreB = 1 - scoreA;
  const deltaA = kFactor(votesA) * (scoreA - expectedA);
  const deltaB = kFactor(votesB) * (scoreB - expectedB);
  return { deltaA, deltaB, newEloA: eloA + deltaA, newEloB: eloB + deltaB };
}

Dobór par, który daje szansę nowym

Gdyby pary losować zupełnie przypadkowo, nowa treść długo czekałaby na pierwsze porównania, a starcia treści z dołu rankingu z tymi ze szczytu niczego by nie rozstrzygały i nudziłyby głosującego. Dlatego dobór par ma dwie wagi.

Pierwsza treść

treści z małą liczbą porównań są losowane częściej, dopóki nie zbiorą ich dość.

Przeciwnik

z tej samej kategorii, wybierany tym chętniej, im bliżej jest jego wynik.

Głos

użytkownik wskazuje zabawniejszą, a obie strony dostają nowy wynik w jednej transakcji.

Wychodzą z tego pojedynki, które coś rozstrzygają, a nowa treść ma realną szansę wyjść z punktu startowego.

Dwa rankingi: wszech czasów i oficjalny

Ranking wszech czasów to czyste Elo, które nigdy nie spada: uczciwa historia tego, co było najzabawniejsze. Obok niego działa ranking oficjalny z osobną punktacją. Każdy pojedynek dodaje zwycięzcy i odejmuje przegranemu tyle, ile zmieniło się Elo, punktów nie da się zejść poniżej zera, a codziennie o północy wszystkie tracą 20 procent. Hit sprzed miesiąca nie siedzi więc na szczycie wiecznie, a o pierwszym miejscu decyduje to, co bawi teraz.

Rozdzielenie tych dwóch liczb jest celowe. Gdyby codziennie ściskać samo Elo, przestałoby znaczyć to, co znaczy, bo oczekiwany wynik pojedynku zakłada stabilne rankingi obu stron. Osobna punktacja pozwala mieć jednocześnie uczciwą historię i świeży ranking.

Dwa rankingi obok siebie

RankingJak działa
Wszech czasówElo w kategorii, nigdy nie spada
Oficjalnypunkty z pojedynków, codziennie minus 20 procent, nigdy poniżej zera

Uczciwe głosowanie i moderacja

Wrzucanie treści wymaga konta, a rejestracja przechodzi przez kod zaproszenia i captchę. Każdy członek może wygenerować zaproszenia dla kolejnych osób, więc społeczność rośnie przez ludzi, którzy już w niej są. Liczba uploadów na dzień i liczba głosów na minutę mają limity, nie da się głosować w pojedynku, w którym bierze udział własna treść, a obie treści muszą pochodzić z tej samej kategorii. Adres, z którego przyszedł głos, jest zapisywany wyłącznie jako skrót, do wykrywania nadużyć.

Każdą treść da się zgłosić z podaniem powodu. Po kilku zgłoszeniach treść sama znika z puli głosowania, ale nie jest kasowana, tylko czeka w kolejce moderacji, gdzie administrator ją przywraca albo usuwa. W tym samym panelu administrator dodaje kategorie, ustawia ich rodzaj treści i kolejność oraz wyłącza je bez zmiany kodu.

Jaki jest finalny efekt?

Użytkownik dostaje prostą, wciągającą pętlę: dwie rzeczy, jedno kliknięcie, następna para. Twórca widzi, jak jego mem radzi sobie w uczciwym rankingu, w którym liczy się to, co bawi ludzi, a nie to, kto wrzucił pierwszy.

Platforma od początku ma to, czego publiczny upload wymaga: konta, limity, ochronę przed nakręcaniem własnych wyników, zgłoszenia i kolejkę moderacji. Ranking oficjalny zostaje świeży, a ranking wszech czasów pamięta wszystko, co kiedykolwiek wygrało.

Pytania o RimShot

Więcej projektów

Inne realizacje z tej samej kategorii - zobacz, jak podchodzimy do podobnych wyzwań.

Masz podobny projekt?

Napisz do nas - wycena jest bezpłatna i wraca w godzinę.