Vyvážit hodnocení v soutěžní karetní hře není snadné. Vyvíjíme vlastní Elo pro hru inspirovanou Sedmou a Sedmice se zvláštními výkonnostními ukazateli.
Co je nového?
Dynamický faktor K určuje rychlost změn podle aktuálního hodnocení:
- Pod 1200 Elo: K = 40, noví hráči se přizpůsobují rychleji.
- 1200–1800 Elo: K = 32, běžná úprava.
- 1800–2200 Elo: K = 24, stabilnější úprava.
- Nad 2200 Elo: K = 16, velmi stabilní úprava.
Bonusy podle výkonu: nejde jen o výhru a prohru.
- +0.05 za kolo vyhrané 16 body, nejvýše +0.15.
- +0.1 za kolo vyhrané 24 body, nejvýše +0.2.
- +0.03 za výhru se čtyřmi body a posledním zdvihem, nejvýše +0.09.
Penalizace špatných tahů: každý špatný tah znamená –0.02, celkem nejvýše –0.3.
Úprava bonusů: při prohře po dobrém výkonu bonusy zůstávají, ale pouze na 10 % své hodnoty.
Proč tento přístup?
Elo má odrážet, jak vyhrajete nebo prohrajete. Dobrý výkon při prohře má mít hodnotu a neuvážené výhry s mnoha chybami nemají nepřiměřeně zvyšovat hodnocení.
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 bude dál?
Systém je experimentální. Ladíme koeficienty a můžeme měnit:
- Váhu bonusů a penalizací
- Hranice změny K
- Zpracování týmových zápasů a remíz
Napište, zda hodnocení působí férově a zda vám některá změna připadala nepřiměřená. Vaše zpětná vazba pomůže systém zdokonalit.
