対戦ゲームのレーティング:Eloの仕組みと実装
遊びをつくる執筆にClaudeを使用しています。

対戦ゲームに「勝ち負けの記録」だけでなく「強さの指標」を持たせたいとき、最初に検討するのがEloレーティングです。チェスの世界で生まれたこの方式は、計算がシンプルで、対戦相手の強さを考慮した公平な評価ができるため、ブラウザゲームの規模でも十分に機能します。 この記事では、Eloの計算式がなぜそう設計されているかを理解したうえで、JavaScriptで動く実装コードと、初期値・K係数の決め方という「実際に困る部分」を順に説明します。アルゴリズムの概念だけでなく、自分のゲームに組み込んで動かせる状態を目標にしています。
Eloが「何を計算しているか」を先に理解する
Eloレーティングの核心は「期待勝率」です。プレイヤーAのレーティングをRa、プレイヤーBをRbとしたとき、AがBに勝つ期待勝率Eaは次の式で求めます。
Ea = 1 / (1 + 10^((Rb - Ra) / 400))この式はロジスティック曲線を使っています。Ra と Rb が等しければ Ea は 0.5(五分五分)になります。Raが400高ければ Ea は約0.909、つまり約9割の確率で勝つと見込むわけです。底を10にして400で割る設計は、チェスの実戦データに合わせてチューニングされた定数であり、理論的に唯一の答えというわけではありません。
対戦後、実際の結果(勝ち=1、負け=0、引き分け=0.5)と期待勝率の差に定数Kをかけた値をレーティングに加減します。
Ra(新) = Ra(旧) + K × (Sa - Ea)Saが実際の結果、Eaが期待勝率です。格上に勝てば Sa > Ea となり大きく増え、格下に勝っても Sa ≒ Ea なので変化は小さくなります。この「驚き度」に比例して動く設計が、Eloの直感的な公平感を生んでいます。
K係数の選び方
K係数はレーティングの「動きやすさ」を決めます。大きいほど一試合の影響が強く、小さいほど安定します。
実用上の目安は次のとおりです。
- 試合数が少ない新規プレイヤー、K係数の目安:40〜64
- 一般プレイヤー、K係数の目安:20〜32
- 試合数が多い上位プレイヤー、K係数の目安:10〜16
チェスの国際機関FIDEは、2400以上の上位者にK=10、2400未満の一般にK=20、新規や若手にK=40を使い分けています。ゲームジャンルや対戦頻度によって最適値は変わるため、最初は全員K=32で始め、一定試合数(例:30試合)を超えたらK=16に切り替える「段階的K」が扱いやすい出発点です。
Kを固定しすぎると、長くプレイしているユーザーのレーティングが「動かない」ストレスにつながります。逆にKを大きくしすぎると、連敗で急落したユーザーが離脱しやすくなります。プレイヤーの感情コストも設計の一部として意識してください。
JavaScriptでの実装手順
1. 期待勝率と新レーティングを返す関数を作る
function expectedScore(ratingA, ratingB) {
return 1 / (1 + Math.pow(10, (ratingB - ratingA) / 400));
}
function updateRatings(ratingA, ratingB, scoreA, kA = 32, kB = 32) {
const ea = expectedScore(ratingA, ratingB);
const eb = 1 - ea; // Bの期待勝率はAの補数
const scoreB = 1 - scoreA;
return {
newRatingA: Math.round(ratingA + kA * (scoreA - ea)),
newRatingB: Math.round(ratingB + kB * (scoreB - eb)),
};
}scoreA には勝ちなら1、負けなら0、引き分けなら0.5を渡します。kA・kBはプレイヤーごとに試合数に応じた値を渡せるようにしておくと、段階的Kへの移行が後から楽になります。
2. 初期レーティングを決める
一般的な初期値は1000〜1500です。チェスは1200が多く、オンラインゲームでは1000や1500を使うケースも多く見られます。絶対的な基準はなく、「プレイヤー同士の相対差」だけが意味を持つため、全員が同じ値から始まれば機能します。
注意すべきは、初期値が低すぎると最初の数試合でレーティングが大きく動き、上位プレイヤーとの差が早期につきすぎる点です。1000スタートでK=32の場合、10連勝すると約320上がり、格差が一気に広がります。初期値1200・K=32は安定した出発点として使いやすいです。
3. サーバー側で計算を行う
レーティングの更新はかならずサーバー(またはバックエンド)で行ってください。クライアント側(ブラウザ)で計算して送信する設計では、送信値の改ざんを防ぐことができません。Node.jsのAPIエンドポイントや、FirebaseのCloud Functionsなどに上記の関数を置き、対戦結果を受け取ったらDBを更新する流れが基本です。
試合結果を受け取るAPIは「試合ID・勝者・敗者」を受け付け、同じ試合IDが二重送信された場合は処理をスキップするべき等冪(べきとう)設計にしておくと、通信エラー時の不正加算を防げます。
4. フロアとシーリングを設ける
レーティングが0や負の値になると表示・計算が扱いにくくなります。最低値を100や500に固定するフロア処理を入れておくと安心です。
const MIN_RATING = 100;
newRatingA = Math.max(MIN_RATING, newRatingA);
newRatingB = Math.max(MIN_RATING, newRatingB);上限(シーリング)は設けないことが多いですが、テストアカウントや不正プレイによる異常値を検知するためのアラートラインとして「3000以上は管理者確認」といったルールを後から入れられるよう、ログだけは残しておくと便利です。
よくある失敗と対処
試合数が少ない段階での精度問題:Eloは試合を重ねるほど正確になります。新規プレイヤーは最初の10〜20試合でレーティングが大きく動き、ランキング上位に仮配置されることがあります。「プレイ数が一定以下はランキング非表示」か「仮レーティング期間」を設けると、既存プレイヤーへの不公平感を減らせます。
非アクティブプレイヤーの扱い:長期間試合をしないとレーティングが固定されたまま残り、復帰後に実力と乖離する問題があります。一定期間ログインがなければレーティングを平均に向けて少し引き戻す「減衰」を入れるゲームもあります。ただし減衰はプレイヤーの不満にもなるため、導入する場合は事前に告知を明確にすることが重要です。
引き分けの多いゲーム:チェスや囲碁のように引き分けが頻出するゲームでは、Eloはそのまま動作します。引き分けが存在しないゲームでは scoreA は常に0か1になるため、引き分けの処理を省略しても構いません。
次の行動
まず updateRatings 関数をローカルで動かし、レーティング差が100・200・400のときの変動幅を手で確認してみてください。数字の動きを体感すると、K係数と初期値の設定判断が格段にしやすくなります。対戦機能つきのゲームをアソバースに公開する際は、レーティング計算をサーバー側に置いた状態で投稿できると、プレイヤーへの信頼性が高まります。
参考資料
- FIDE Rating Regulations(国際チェス連盟 公式レーティング規定)
- Arpad Elo, *The Rating of Chessplayers, Past and Present*(Elo本人による原著)
- Chess Programming Wiki — Evaluation of Elo



