在竞技纸牌游戏中,平衡等级分并不容易。我们正在为这款受 Sedma 与 Sedmice 启发的游戏开发定制 Elo 系统,并加入特定的表现指标。
Elo 系统有哪些新内容?
动态 K 系数决定等级分变化的速度。Seven 会根据当前等级分调整 K:
- 低于 1200 Elo:K = 40,新玩家调整更快。
- 1200–1800 Elo:K = 32,常规调整。
- 1800–2200 Elo:K = 24,变化更加稳定。
- 高于 2200 Elo:K = 16,变化非常稳定。
表现奖励:等级分变化不仅取决于胜负。
- 每次赢得 16 分的局增加 +0.05,最多 +0.15。
- 每次赢得 24 分的局增加 +0.1,最多 +0.2。
- 每次获得四分并赢得最后一墩增加 +0.03,最多 +0.09。
失误惩罚:每次错误出牌带来 –0.02 的小幅惩罚,累计最多 –0.3。
奖励调整:如果你输了但发挥不错,奖励仍然生效,不过仅按原数值的 10% 计算。
为什么采用这种方式?
我们希望 Elo 体现你如何获胜或落败,而不仅是结果。输了时的良好表现应该有价值;失误很多、侥幸获胜的对局,则不该过度提高等级分。
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
}
下一步
这个系统仍是实验性方案。我们正在调整系数,也可能改变:
- 奖励与惩罚的影响程度
- K 值发生变化的时机
- 团队对局或平局的处理方式
欢迎分享反馈!你觉得等级分公平吗?有没有觉得某场比赛后的变化不合理?欢迎评论或联系我们,你的意见会帮助完善系统。
