8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

シーエー・アドバンスAdvent Calendar 2024

Day 11

Next.jsとRiot Games APIを使用してLeague of Legendsのサモナー統計ダッシュボードアプリを作成する

Posted at

自己紹介

はじめまして。
株式会社シーエー・アドバンスに勤めている當銘塁と申します。
最近 League of Legends にハマりまくっています!
どのくらいハマりまくっているかというと、

一日のスケジュールの三分の一が League of Legends になるほどハマりまくっています!

image.png

なぜLeague of Legendsのサモナー統計ダッシュボードアプリを作成したのか?

入社後半年が経過し、普段Next.jsで開発していたので、自分の趣味であるLeague of Legends(LoL)を題材に何か作れないかと考えました。

アプリの概要

このアプリでは、Riot Games APIを使って以下の情報を取得して表示します

  • サモナーの基本情報
    • サモナー名、レベルなど
  • ランク情報
    • ソロランクやフレックスランクのティア、LP、勝敗など
  • チャンピオン統計
    • 使用頻度、勝率、KDA
  • レーン統計
    • レーン別のプレイ状況(勝率やKDA)

Riot Games APIを取得する

1. 上記のURLにアクセスすると、ログインが求めらるので、ログインする

2. ログインすると下記の画面が表示されるので、APIキーの生成ボタンを押す

image.png

APIキー取得完了🎉🎉🎉

上記の方法で取得したAPIキーは、開発環境でのみ使用して良いとのことなので、注意してください!

サモナー名とタグラインをサーバーに送信する

formタグの中にサモナー名とタグ名のinputを用意し、統計情報を取得ボタンを押すと、handleSubmit関数が走り、サーバーサイドに送信されて、サーバーサイドで処理されたデータが返ってくるという仕組みです!

 const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
   e.preventDefault();
   setLoading(true);
   setError(null);

   try {
     const data = await fetchSummonerStats(gameName, tagLine);
     setStats(data);
   } catch (err) {
     console.error(err);
     setError(
       err instanceof Error
         ? err.message
         : "サモナーの統計情報の取得に失敗しました"
     );
   } finally {
     setLoading(false);
   }
 };

 return (
   <div className="container mx-auto p-4 bg-gray-100 min-h-screen">
     <h1 className="text-4xl font-bold text-center text-blue-600 mb-8">
       サモナー統計
     </h1>

     <form
       onSubmit={handleSubmit}
       className="mb-8 bg-white shadow-md rounded-lg p-6"
     >
       <div className="flex flex-col sm:flex-row gap-4">
         <input
           type="text"
           value={gameName}
           onChange={(e) => setGameName(e.target.value)}
           placeholder="サモナー名を入力"
           required
           className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
         />
         <div className="mt-3 font-bold">#</div>
         <input
           type="text"
           value={tagLine}
           onChange={(e) => setTagLine(e.target.value)}
           placeholder="タグラインを入力"
           required
           className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
         />
         <button
           type="submit"
           disabled={loading}
           className="bg-blue-500 text-white p-3 rounded-lg hover:bg-blue-600 disabled:bg-gray-400 transition duration-300 ease-in-out"
         >
           {loading ? "読み込み中..." : "統計情報を取得"}
         </button>
       </div>
     </form>

Riot Games API を使った統計情報の取得

サーバーサイドの関数 fetchSummonerStats は以下のように実装しています。

概要

1. サモナー名とタグラインからPUUID(プレイヤー固有ID)を取得。
2. PUUIDを使ってサモナーの基本情報(レベル、ランク)を取得。
3. 最新のマッチ履歴を取得して試合結果を解析。
4. チャンピオンごとの統計(使用頻度、勝率、KDA)を計算。
5. レーンごとの統計(勝率、KDA)を計算。

実装の流れ

1. Riot IDからPUUIDを取得
Riot Games APIでは、Riot ID(サモナー名 + タグライン)を元にPUUIDを取得できます。

const accountResponse = await fetch(
 `${RIOT_ACCOUNT_URL}/riot/account/v1/accounts/by-riot-id/${encodeURIComponent(
   gameName
 )}/${encodeURIComponent(tagLine)}`,
 { headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const accountData = await accountResponse.json();
const puuid = accountData.puuid;


2. サモナー情報の取得
PUUIDを使用して、サモナーのレベルなどの基本情報を取得します。

const summonerResponse = await fetch(
  `${RIOT_API_BASE_URL}/lol/summoner/v4/summoners/by-puuid/${puuid}`,
  { headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const summonerData = await summonerResponse.json();

3. ランク情報の取得
サモナーのランクデータを取得します。

const rankResponse = await fetch(
  `${RIOT_API_BASE_URL}/lol/league/v4/entries/by-summoner/${summonerData.id}`,
  { headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const rankData = await rankResponse.json();

4. マッチ履歴の取得と解析
最新10試合のマッチデータを取得し、それを元にチャンピオン統計やレーン統計を計算します。

const matchListResponse = await fetch(
  `${RIOT_MATCH_URL}/lol/match/v5/matches/by-puuid/${puuid}/ids?start=0&count=10`,
  { headers: { "X-Riot-Token": RIOT_API_KEY! } }
);
const matchIds = await matchListResponse.json();

const matchDataPromises = matchIds.map((matchId: string) =>
  fetch(`${RIOT_MATCH_URL}/lol/match/v5/matches/${matchId}`, {
    headers: { "X-Riot-Token": RIOT_API_KEY! },
  }).then((res) => res.json())
);
const matchesData = await Promise.all(matchDataPromises);

5. チャンピオン統計とレーン統計の計算
各試合のデータから、チャンピオンごとの使用頻度、勝率、KDA、またレーンごとの統計を計算します。

matchesData.forEach((match: any) => {
  const participant = match.info.participants.find(
    (p: any) => p.puuid === puuid
  );
  if (participant) {
    totalGames++;
    if (participant.win) totalWins++;

    // チャンピオン統計
    const championName = participant.championName;
    if (!championStats[championName]) {
      championStats[championName] = {
        games: 0,
        wins: 0,
        kills: 0,
        deaths: 0,
        assists: 0,
      };
    }
    championStats[championName].games++;
    championStats[championName].wins += participant.win ? 1 : 0;
    championStats[championName].kills += participant.kills;
    championStats[championName].deaths += participant.deaths;
    championStats[championName].assists += participant.assists;

    // レーン統計
    const lane = participant.teamPosition || "UNKNOWN";
    if (!laneStats[lane]) {
      laneStats[lane] = {
        games: 0,
        wins: 0,
        kills: 0,
        deaths: 0,
        assists: 0,
      };
    }
    laneStats[lane].games++;
    laneStats[lane].wins += participant.win ? 1 : 0;
    laneStats[lane].kills += participant.kills;
    laneStats[lane].deaths += participant.deaths;
    laneStats[lane].assists += participant.assists;
  }
});

クライアントサイドでの表示

取得した統計情報をReactのコンポーネントで表示します。

基本情報の表示

<StatsCard
  title="基本情報"
  icon={<FaUser className="text-blue-500" />}
  content={
    <>
      <p className="text-lg">
        <span className="font-medium">名前:</span>
        {stats.accountData.gameName}#{stats.accountData.tagLine}
      </p>
      <p className="text-lg">
        <span className="font-medium">レベル:</span>
        {stats.summonerData.summonerLevel}
      </p>
    </>
  }
/>

チャンピオン統計の表示

<StatsCard
  title="チャンピオン統計"
  icon={<FaGamepad className="text-purple-500" />}
  content={
    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
      {Object.entries(stats.championStats).map(([champion, data]) => (
        <div
          key={champion}
          className="bg-gray-100 p-3 rounded-lg hover:shadow-md transition duration-300"
        >
          <h4 className="font-medium text-lg">{champion}</h4>
          <p>ゲーム数: {data.games}</p>
          <p>勝率: {((data.wins / data.games) * 100).toFixed(2)}%</p>
          <p>
            KDA: {((data.kills + data.assists) / data.deaths).toFixed(2)}
          </p>
        </div>
      ))}
    </div>
  }
/>

そして、ついに完成したものがこちら🎉🎉🎉

image.png

自分のアカウントを検索してみると....

image.png

正常に表示されました🎉🎉🎉

自分のプレイヤー名が厨二病ってことと、TOPレーナーってことやイラオイOTPってことがバレました!!

まとめ

自分の大好きなLeague of Legends(LoL)を題材に開発ができて、よかったです。
下記のイベントに参加することから、急いでChatGPTを活用して開発をしました。
1日で完成することができたので、上出来なのではないかと思っています。
今後も追加したい機能があるので、趣味で開発していきたいと思います!

参考

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?