マガジン

スコアの順位づけ:同点のときにどう並べるか

遊びをつくる

執筆にClaudeを使用しています。

遊ばれた回数、好意、感想を表す三種類の小さな札を比べる人の手。
記事のテーマをもとに制作したAI生成イラスト。

自作のゲームに、遊んだ記録を並べる画面を付ける場面を考えてみます。3人の記録が、どれも80点だったとします。A、B、Cのうち、誰が1位に見えるべきでしょうか。何も決めていなければ、答えは並べ替え処理の内部で偶然決まります。登録した順に並ぶこともあれば、読み込みのたびに入れ替わることもあります。 この記事で確かめるのは、同点の並べ方は「あとから直すバグ」ではなく、先に決めておく規則だということです。規則は「どの値を、どの向きで、どの順に比べるか」という列として書き出せます。ランキングを例に、その考え方と手順を見ていきます。

この記事の目次
  1. 比べる順番を、キーの列として書き出す
  2. 同点の扱いは四つに分かれる
  3. 比較関数を書いて確かめる
  4. アソバースで規則を読む視点
  5. 次の行動
  6. 参考資料

比べる順番を、キーの列として書き出す

並べ替えは、二つの記録を渡されて「どちらが先か」を答える処理の繰り返しです。この答えを返す部分を比較関数と呼びます。比較関数は、比べる項目(キー)を優先度の高い順に見ていきます。上の項目で差がなければ次の項目へ進み、すべて同じなら「同じ」と答えます。辞書で単語を並べるときと同じ考え方です。

設計のとき、各キーには二つのことを決めます。

  • 向き:高いほど良いのか、低いほど良いのか
  • 優先度:何番目に見るのか

向きはゲームごとに違います。公式作品の「設計から読み取れること」として整理すると、次のようになります。

  • こだまの森、記録の種類:点数(聴き直しで15点減、間違いで30点減)、向き:高いほど良い
  • 小さな軌道、記録の種類:最速記録、向き:低いほど良い
  • 月夜の配達、記録の種類:各夜の最少区間数、向き:低いほど良い
  • プリズムの道、記録の種類:最少回転、向き:低いほど良い
  • 時計じかけの台所、記録の種類:料理ごとの100点評価、向き:高いほど良い

一つの一覧に「点数」と「時間」を混ぜるなら、向きが逆のキーを同じ比較関数に入れることになります。ここが取り違えの起きやすい点です。

もう一点、「並べる前に分ける」という判断があります。こだまの森には6・8・10ラウンドの3コースがあります。ラウンド数が違えば取れる点数の幅も違うはずなので、コースをまたいで一つの表に並べるのは筋が通りません。実際の配点の細部は作品を開いて確かめる必要がありますが、設計の考え方としては、コースで先にグループ分けし、各グループの中で並べるのが自然です。

同点の扱いは四つに分かれる

全キーが同じになったとき、取れる方針はおおむね四つです。

  • 先着優先、内容:先にその記録を出した人を上にする、向いている場面:早い者勝ちを尊重したいとき
  • 副指標で決める、内容:点数が同じなら時間が短い方、のように別の値で決める、向いている場面:競技性が高く、差をつけたいとき
  • 同順位にする、内容:両方を2位とし、次は4位にする、向いている場面:公平さを優先したいとき
  • 並びは任意と割り切る、内容:順位は同じで、表示順に意味を持たせない、向いている場面:小規模な記録一覧

同順位の数え方には2通りあります。2位が2人いたときに次を4位とする数え方(1, 2, 2, 4)と、3位とする数え方(1, 2, 2, 3)です。どちらが正しいということはなく、画面の説明と一致していれば問題ありません。

ここで押さえたいのは、表示する順位と並べる順序は別の話だという点です。比較関数が「同じ」と答えた二人は、順位の数字は同じにして、表示順だけ先着順などで安定させることができます。

比較関数を書いて確かめる

動かせる最小の例を用意します。HTMLファイルとして保存してブラウザで開くと動きます。5件の記録を、点数が高い順、同点なら時間が短い順に並べ、完全に同じなら同順位にします。

html
<!doctype html>
<meta charset="utf-8">
<pre id="out"></pre>
<script>
const rows = [
  { name: "A", score: 80, time: 41, at: 1 },
  { name: "B", score: 80, time: 38, at: 2 },
  { name: "C", score: 95, time: 50, at: 3 },
  { name: "D", score: 80, time: 38, at: 4 },
  { name: "E", score: 60, time: 30, at: 5 },
];

// 点数は高い順、時間は短い順
function compare(a, b) {
  if (a.score !== b.score) return b.score - a.score;
  if (a.time !== b.time) return a.time - b.time;
  return 0; // ここまで同じなら同点
}

function rankAll(list) {
  const sorted = [...list].sort(compare);
  let rank = 0;
  return sorted.map((row, i) => {
    if (i === 0 || compare(sorted[i - 1], row) !== 0) rank = i + 1;
    return { ...row, rank };
  });
}

document.getElementById("out").textContent = rankAll(rows)
  .map(r => r.rank + "位 " + r.name + " " + r.score + "点 " + r.time + "秒")
  .join("\n");
</script>

コードを読むと、Cが1位、BとDが同じ点数で同じ時間なので同じ2位、Aは80点でも時間が長いので4位、Eが5位になるはずです。実際に開いて、その通りに表示されるか確かめてください。

次に、return 0; の行を return a.at - b.at; に書き換えます。at は記録が登録された順を表す番号なので、これでBとDの間にも優先関係が生まれ、先着優先の表になります。そのうえで b.score - a.score の a と b を入れ替えると、点数が低い順に並びます。向きの取り違えがどう見えるかを、手元で確認できます。

このコードで注意したい点を挙げます。

  • 順序を決めるキーに小数を使うと、わずかな誤差で「同点のはず」が同点にならないことがあります。時間はミリ秒の整数で持つと安全です。
  • at に時刻を使う場合でも、同じ時刻の記録は起こり得ます。最後の決め手には、重複しないIDを置くと並びが毎回同じになります。
  • JavaScriptの sort は、現在の仕様では安定ソートです。つまり、比較関数が「同じ」と答えた要素は、元の並び順を保ちます。ただし、その元の並び順が偶然の産物なら、結果も偶然のままです。
  • 規則を公開後に変えると、過去の記録の順位が入れ替わります。副指標の追加は、公開前に決めておくのが無難です。

アソバースで規則を読む視点

この考え方を、アソバースの仕組みに当てはめてみます。確認済みの範囲で言えるのは、次のことです。

  • 作品のランキングは、プレイ回数・いいね数・レビュー平均の切り替えと、ジャンルの絞り込みができる。
  • 評価順は、レビューが3件以上の作品から掲載される。
  • 順位の対象は上位50作品である。
  • プレイ回数は作品の起動を実測した回数で、同じ端末・作品の30分以内の再起動は重複として除かれる。人数ではない。

レビュー平均では、3件という条件が、並べ替えの前の「足切り」として働いています。レビュー1件で5点の作品が、多数の評価を集めた作品より上に来る事態を避ける設計と読み取れます。並べる前に対象を絞るのも、規則の一部です。

一方、同じ値の作品が並んだときの扱いは、確認済みの案内に書かれていません。この部分は断定せず、自分で確かめてください。いいね数に切り替え、同じ数の作品が隣り合っている箇所を探します。そして、画面を読み込み直したり指標を切り替えて戻したりしたときに、その並びが保たれるかを見ます。同順位の表記があるかどうかも、あわせて確認できます。仕様の詳細が必要な場合は、現在の案内で確認してください。

もう一つ、ゲーム内スコアの共通ランキングは、現在アソバースには提供されていません。自作ゲームで記録の並びを見せたいなら、遊ぶ人自身の自己ベスト一覧のような、一人分の記録を並べる用途が現実的です。その場合でも、比較関数の考え方はそのまま使えます。公式の10作品の記録が画面を開いている間だけ残る設計なのも、一人分の記録を扱う範囲を割り切った例として読めます。

次の行動

手元のゲームで、記録に使う値を書き出し、それぞれに「高いほど良いか低いほど良いか」と「優先度」を決めてみてください。そのうえで上のコードを自作のデータに差し替え、同点のときに何位と表示したいかを一文で書き、return 0; の行をその方針に合わせて直します。

参考資料

  • MDN Web Docs「Array.prototype.sort」
  • ECMAScript言語仕様(Array.prototype.sort の安定性に関する規定)