Momento Leaderboardでゲームのスコアを保存・取得できるか試してみた

Momento Advent Calendar 2023の14日目の記事では、MomentoのLeaderboardについての説明があります。Leaderboardはゲームなどでユーザーのスコアをランキング形式で表示するために使用されます。記事では、Leaderboardを作成し、スコアの保存や取得方法について詳しく解説しています。さらに、スコアの範囲や順位での取得方法も紹介されています。Leaderboardを使ったアプリケーションを開発する際に役立つ情報が得られます。参考記事:[https://docs.momentohq.com/ja/leaderboards](https://docs.momentohq.com/ja/leaderboards)

広告ここから
広告ここまで

目次

    この記事は「Momento Advent Calendar 2023」14日目の記事です。

    Momentoの製品一覧を見ていてずっと気になっていたのが、このLeaderboardでした。ドキュメントをみる限り、ゲームなどでユーザーのスコアをランキング形式で出したりするのに使える様子です。

    NodeでLeaderboardを作ってみる

    何はともあれ作って動かしてみましょう。

    Learderboardを操作するクライアントを立ち上げる

    SDKを利用してクライアントを作成します。2023/12時点ではプレビューの機能らしく、クラス名にPreviewがついていました。

        const client = new PreviewLeaderboardClient({
            configuration: Configurations.Laptop.latest(),
            credentialProvider: CredentialProvider.fromString({
                apiKey: process.env.MOMENT_API_KEY
            }),
        });

    LeaderboardはMomento Cacheを使う様子です。client.leaderboardの第一引数で利用するキャッシュの名前を指定しましょう。第二引数がLeaderboardの名前で、複数設定できるみたいです。

        const leaderboard = client.leaderboard('cache', 'my-leaderboard');

    スコアをLeaderboardに保存する

    Leaderboardへの保存は、まとめて行うこともできるようにMapで投入できます。配列の1番目がユーザーID(数字)、2番目がスコアになる様子です。また、更新についても同じ処理で行えました。

        const elements1: Map<number, number> = new Map([
            [123, 100.0],
            [234, 200.0],
            [345, 300.0],
            [456, 400.0],
        ]);
        const result1 = await leaderboard.upsert(elements1);
        const elements2: Map<number, number> = new Map([
            [567, 500],
            [678, 600],
            [789, 700],
            [890, 800],
        ]);
        const result2 = await leaderboard.upsert(elements2);

    ネストした配列が少し苦手という方は、Recordを使ってオブジェクト形式にすることもできます。この場合はキーがユーザーID、値がスコアになる様子でした。

    const elements2: Record<number, number> = {
      567: 500,
      678: 600,
      789: 700,
      890: 800,
    };

    Leaderboardからスコアを取得する

    スコアの取得は、fetchByScorefetchByRankの2つで行います。

    const result = await leaderboard.fetchByScore();

    ただしこれらのレスポンスは次の例のように生データで届きます。

    {
      "_elements": [
        {
          "wrappers_": null,
          "arrayIndexOffset_": -1,
          "array": [
            123,
            100
          ],
          "pivot_": 1.7976931348623157e+308,
          "convertedPrimitiveFields_": {}
        },
        {
          "wrappers_": null,
          "arrayIndexOffset_": -1,
          "array": [
            234,
            200,
            1
          ],
          "pivot_": 1.7976931348623157e+308,
          "convertedPrimitiveFields_": {}
        },

    アプリケーションコードで扱いやすくするには、次のようにvalues()でデータを取得して処理をかけるようにしましょう。TypeScriptの場合は、instanceofで取得に成功したときの型であることを確認することで、values()を利用できます。

        if (!(result instanceof LeaderboardFetch.Success)) {
            throw new Error("Failed to load the leaderboard");
        }
        const scores = result.values();

    values()を通すことで、読みやすいデータに変わりました。

    [
      {
        "id": 123,
        "score": 100,
        "rank": 0
      },
      {
        "id": 234,
        "score": 200,
        "rank": 1
      },
    ...

    スコアのMIN / MAXや並び順、件数も取ることができます。例えば「500点以下の上位3名を昇順で」だとこのように書きます。

        const result = await leaderboard.fetchByScore({
            maxScore: 500,
            order: LeaderboardOrder.Descending,
            count: 3
        });

    指定したスコアより小さい値で、件数や並び順の指定も効いていることがわかります。

    [
      {
        "id": 456,
        "score": 400,
        "rank": 4
      },
      {
        "id": 345,
        "score": 300,
        "rank": 5
      },
      {
        "id": 234,
        "score": 200,
        "rank": 6
      }
    ]

    順位で取得することもできる

    「1位から3位を昇順で」のような指定をしたい場合は、fetchByRank側を使いましょう。

        const result = await leaderboard.fetchByRank(0, 3, {
            order: LeaderboardOrder.Descending
        });
        const scores = result.values();

    レスポンスはどちらも同じような形です。

    [
      {
        "id": 789,
        "score": 700,
        "rank": 1
      },
      {
        "id": 678,
        "score": 600,
        "rank": 2
      }
    ]

    特定のユーザーのスコアを見る方法

    「あなたのスコアはx点です」「あなたのチームのスコアは・・・」などのデータを取得したい場合、getRankを使います。第一引数で取得したいユーザーのIDを配列にて渡しましょう。

        const result = await leaderboard.getRank([123, 234], {
            order: LeaderboardOrder.Descending
        })

    ユーザーIDは数字にする必要がある点に注意

    Momento Leaderboard (2023/12時点)を使う場合、Leaderboardに投入するユーザーIDを数字にする必要があるみたいでした。下のサンプルでは、文字列を渡してみようとしています。

        const elements1: Record<string, number> = {
            'user1': 100,
            'user2': 200,
        }
        const result1 = await leaderboard.upsert(elements1);
        const elements2: Map<number, number> = new Map([
            [567, 500],
            [678, 600],
            [789, 700],
            [890, 800],
        ]);
        const result2 = await leaderboard.upsert(elements2);

    実行結果はこちらで、文字列を使ったINSERTはエラーになっています。

    {
      "result1": {
        "_innerException": {
          "_transportDetails": {
            "grpc": {
              "code": 13,
              "details": "13 INTERNAL: Request message serialization failure: Assertion failed",
              "metadata": {}
            }
          },
          "_errorCode": "INTERNAL_SERVER_ERROR",
          "_messageWrapper": "An unexpected error occurred while trying to fulfill the request; please contact us at [email protected]"
        }
      },
      "result2": {
        "is_success": true
      }
    }

    多くの場合、ユーザーIDやSIDは文字列ではないかと思います。そのため、IDPのユーザーIDとゲームのユーザーiDをマップするKVSや DBテーブルが必要になるかもしれません。

    終わりに

    ゲーム系の開発はあまりする機会がないのですが、こういうAPIを知っているといざという時に捗りそうな予感がします。これを使うために何かアプリを作ってもいいのかも・・・?と思えるくらいにはシンプルなAPIでしたので、来年の個人開発目標になるかもしれません。

    参考記事

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark