Vyvážiť hodnotenie hráčov v súťažnej kartovej hre nie je jednoduché. Vyvíjame vlastné Elo pre hru inšpirovanú Sedmou a Sedmice s osobitnými výkonnostnými ukazovateľmi.
Čo je nové?
Dynamický faktor K určuje rýchlosť zmien podľa aktuálneho hodnotenia:
- Pod 1200 Elo: K = 40, noví hráči sa prispôsobujú rýchlejšie.
- 1200–1800 Elo: K = 32, bežná úprava.
- 1800–2200 Elo: K = 24, stabilnejšia úprava.
- Nad 2200 Elo: K = 16, veľmi stabilná úprava.
Bonusy podľa výkonu: výpočet nie je iba o výhre a prehre.
- +0.05 za kolo vyhrané 16 bodmi, najviac +0.15.
- +0.1 za kolo vyhrané 24 bodmi, najviac +0.2.
- +0.03 za výhru so štyrmi bodmi a posledným zdvihom, najviac +0.09.
Penalizácia zlých ťahov: každý zlý ťah znamená –0.02, celkovo najviac –0.3.
Úprava bonusov: pri prehre po dobrom výkone bonusy zostávajú, ale iba na 10 % svojej hodnoty.
Prečo tento prístup?
Elo má odrážať, ako vyhráte alebo prehráte. Dobrý výkon pri prehre má mať hodnotu a neuvážené výhry s mnohými chybami nemajú neprimerane zvyšovať hodnotenie.
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
}
Čo bude ďalej?
Systém je experimentálny. Dolaďujeme koeficienty a môžeme meniť:
- Váhu bonusov a penalizácií
- Hranice zmeny K
- Spracovanie tímových zápasov a remíz
Napíšte nám, či hodnotenie pôsobí férovo a či vám niektorá zmena pripadala neprimeraná. Vaša spätná väzba pomôže systém zdokonaliť.
