Przejdź do treści
Menu
← Wróć do bloga

Jak obliczamy Elo w naszej grze karcianej

Illustration of two card players with Elo rating calculations floating above, showing rating change formulas and stats

Zrównoważenie rankingu graczy w rywalizacyjnej grze karcianej nie jest proste. Dopracowujemy własny system Elo dla gry inspirowanej Sedmą i Sedmice, uwzględniający dodatkowe wskaźniki jakości gry.

Co zmieniło się w naszym systemie Elo?

Dynamiczny współczynnik K

Współczynnik K określa szybkość zmian rankingu. Dostosowujemy go do aktualnego Elo gracza:

  • Poniżej 1200 Elo: K = 40 — szybsze dopasowanie nowych graczy.
  • 1200–1800 Elo: K = 32 — standardowe dopasowanie.
  • 1800–2200 Elo: K = 24 — większa stabilność.
  • Powyżej 2200 Elo: K = 16 — bardzo stabilny ranking.

Premie za jakość gry

Zmiana Elo zależy od czegoś więcej niż zwycięstwa lub porażki:

  • +0.05 za rundę wygraną z 16 punktami, maksymalnie +0.15.
  • +0.1 za rundę wygraną z 24 punktami, maksymalnie +0.2.
  • +0.03 za rundę wygraną z czterema punktami i ostatnią lewą, maksymalnie +0.09.

Kary za błędne ruchy

Każdy błędny ruch oznacza karę −0.02, do łącznego limitu −0.3.

Premie przy porażce

Jeśli przegrasz, ale rozegrasz dobry mecz, premie nadal działają — z siłą równą 10% standardowej wartości.

Dlaczego wybraliśmy takie podejście?

Chcemy, aby Elo odzwierciedlało sposób gry, a nie tylko wynik. Dobra gra mimo porażki powinna się liczyć. Z kolei zwycięstwo z licznymi błędami nie powinno nadmiernie zawyżać rankingu.

public static void CalculateNewRatings(
ref int ratingA,
ref int ratingB,
bool playerAWon,
int badMovesA,
int badMovesB,
int wonBy16A,
int wonBy16B,
int wonBy24A,
int wonBy24B,
int wonBy4AndLastA,
int wonBy4AndLastB
)
{
    // K determines how sensitive the Elo rating is to each match outcome.
    // Higher K means faster rating changes (good for new players),
    // lower K means more stable ratings (good for experienced players).
    int kA = GetKFactor(ratingA);
    int kB = GetKFactor(ratingB);

    // Bonus and malus coefficients
    const float BonusPer16 = 0.05f;
    const float BonusPer24 = 0.1f;
    const float BonusPer4AndLast = 0.03f;
    const float MalusPerBadMove = 0.02f;

    // Caps
    const float MaxBonus16 = 0.15f;
    const float MaxBonus24 = 0.2f;
    const float MaxBonus4AndLast = 0.09f;
    const float MaxMalus = 0.3f;

    float baseScoreA = playerAWon ? 1f : 0f;
    float baseScoreB = 1f - baseScoreA;

    // Bonus for player A (full if won, 1/10 if lost)
    float multiplierA = playerAWon ? 1f : 0.1f;
    float bonusA =
        Mathf.Min(wonBy16A * BonusPer16, MaxBonus16) +
        Mathf.Min(wonBy24A * BonusPer24, MaxBonus24) +
        Mathf.Min(wonBy4AndLastA * BonusPer4AndLast, MaxBonus4AndLast);
    bonusA *= multiplierA;

    // Bonus for player B (full if won, 1/10 if lost)
    float multiplierB = playerAWon ? 0.1f : 1f;
    float bonusB =
        Mathf.Min(wonBy16B * BonusPer16, MaxBonus16) +
        Mathf.Min(wonBy24B * BonusPer24, MaxBonus24) +
        Mathf.Min(wonBy4AndLastB * BonusPer4AndLast, MaxBonus4AndLast);
    bonusB *= multiplierB;

    float malusA = Mathf.Min(badMovesA * MalusPerBadMove, MaxMalus);
    float malusB = Mathf.Min(badMovesB * MalusPerBadMove, MaxMalus);

    float adjustedScoreA = Mathf.Clamp(baseScoreA + bonusA - malusA, 0f, 1f);
    float adjustedScoreB = Mathf.Clamp(baseScoreB + bonusB - malusB, 0f, 1f);

    double expectedA = 1.0 / (1.0 + Math.Pow(10, (ratingB - ratingA) / 400.0));
    double expectedB = 1.0 / (1.0 + Math.Pow(10, (ratingA - ratingB) / 400.0));

    //Debug.Log($"[ELO CALCULATION]");
    //Debug.Log($"Player A: baseScore={baseScoreA}, bonus={bonusA}, malus={malusA}, adjustedScore={adjustedScoreA}, expected={expectedA}");
    //Debug.Log($"Player B: baseScore={baseScoreB}, bonus={bonusB}, malus={malusB}, adjustedScore={adjustedScoreB}, expected={expectedB}");
    //Debug.Log($"Old Ratings - A: {ratingA}, B: {ratingB}");


    ratingA = (int)Math.Round(ratingA + kA * (adjustedScoreA - expectedA));
    ratingB = (int)Math.Round(ratingB + kB * (adjustedScoreB - expectedB));

    //Debug.Log($"New Ratings - A: {ratingA}, B: {ratingB}");
}

private static int GetKFactor(int rating)
{
    if (rating < 1200) return 40;       // Fast adaptation for new players
    if (rating < 1800) return 32;       // Balanced default for most players
    if (rating < 2200) return 24;       // More stable for advanced players
    return 16;                          // Very stable for top-ranked
}

Co dalej?

System jest eksperymentalny. Nadal dostrajamy współczynniki. Możemy zmienić wagę premii i kar, progi współczynnika K oraz sposób obsługi meczów drużynowych i remisów.

Czekamy na opinie: czy ranking wydaje się sprawiedliwy? Czy zdarzył się mecz, po którym zmiana Elo była niezrozumiała? Napiszcie w komentarzach lub skontaktujcie się z nami — Wasze uwagi pomagają ulepszać system.

Leave a Comment

Scroll to Top