← すべてのガイド

MotoGP ファンタジーリーグのスコアリング

独自のスコアリングルールでレース結果をファンタジーポイントに変換し、各ラウンド後にリーグのチームをランキングします。

10 分で読めます

ファンタジーゲームは、レースデータの最良の活用例の 1 つです。プレイヤーがライダーを選び、結果が出るたびにリーグ表が動きます。このガイドでは、自分で管理できるルールを使って、JavaScript でリーグをスコアリングします。

ルール

各ファンタジーチームは 3 人のライダーを選びます。各ラウンドの後、ライダーごとに次のポイントが付きます。

イベントファンタジーポイント
レースで獲得した選手権ポイント1 ポイントにつき 1
優勝+10 ボーナス
表彰台(P1〜P3)+5 ボーナス
リタイア−5

ルールはコードではなくデータとして持っておくと、シーズン間で調整しやすくなります。

// scoring.js
export const RULES = Object.freeze({
  perPoint: 1,
  win: 10,
  podium: 5,
  dnf: -5,
});

/** Fantasy points for one rider's race result. Pure: same input, same output. */
export function scoreResult(result, rules = RULES) {
  let score = result.points * rules.perPoint;
  if (result.status === "FINISHED") {
    if (result.position === 1) score += rules.win;
    if (result.position <= 3) score += rules.podium;
  } else if (result.status === "DNF") {
    score += rules.dnf;
  }
  return score;
}

ラウンドをスコアリングする

GET /races/seasons/{year}/round/{round}/results は、ライダーごとに 1 行ずつ、position、points、status、rider_name、そして安定した rider_id を返します。行をライダーでインデックス化し、各チームの選択を合計します。

// round.js
import { scoreResult } from "./scoring.js";

const HOST = "motogp-api-championship-data-statistics.p.rapidapi.com";
const headers = { "X-RapidAPI-Key": process.env.RAPIDAPI_KEY, "X-RapidAPI-Host": HOST };

async function get(path) {
  const res = await fetch(`https://${HOST}${path}`, { headers });
  if (!res.ok) throw new Error(`${path}: ${res.status}`);
  return (await res.json()).data;
}

export async function scoreRound(year, round, teams) {
  const results = await get(`/races/seasons/${year}/round/${round}/results?category=MOTOGP`);
  const byRider = new Map(results.map((r) => [r.rider_name, r]));

  return teams
    .map((team) => ({
      name: team.name,
      points: team.riders.reduce((sum, rider) => {
        const result = byRider.get(rider);
        return sum + (result ? scoreResult(result) : 0); // didn't start: 0
      }, 0),
    }))
    .sort((a, b) => b.points - a.points);
}
const league = [
  { name: "Late Brakers", riders: ["Marco Bezzecchi", "Raúl Fernández", "Fabio Di Giannantonio"] },
  { name: "Apex Hunters", riders: ["Marc Márquez", "Jorge Martín", "Pedro Acosta"] },
];

console.table(await scoreRound(2026, 1, league));

この例では、読みやすさのために名前で選択を照合しています。本番では、プレイヤーがチームを作るときに GET /riders/seasons/{year} から各選択の rider_id を保存し、結果を rider_id でインデックス化してください。

シーズン合計

終了したラウンドをすべてスコアリングして合算します。まだ開催されていないラウンドは空のリストを返し、全員が 0 点になります。

export async function seasonTable(year, teams, lastRound) {
  const totals = new Map(teams.map((t) => [t.name, 0]));
  for (let round = 1; round <= lastRound; round++) {
    for (const { name, points } of await scoreRound(year, round, teams)) {
      totals.set(name, totals.get(name) + points);
    }
  }
  return [...totals].map(([name, points]) => ({ name, points })).sort((a, b) => b.points - a.points);
}

結果が確定したら、各ラウンドのスコアをデータベースに保存してください。ラウンドが確定した後はスコアが変わらないため、API を呼ぶのは最新のラウンドだけで済みます。

選択リストと価格設定

  • 選べるライダー: GET /riders/seasons/{year}?category=MOTOGP でシーズンのグリッドを一覧できます。
  • ライダーの価格: 現在の選手権(GET /standings/seasons/{year}/riders)から算出し、上位ライダーほど高くなるようにします。