JavaScriptmomentoSaaS / FaaS

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 support@momentohq.com"
    }
  },
  "result2": {
    "is_success": true
  }
}

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

終わりに

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

参考記事

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

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

Related Category posts