1
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?

More than 1 year has passed since last update.

🦀ひとりWASM-4とRustでゲームをつくる⚙Advent Calendar 2022

Day 23

ラストのまほう 第23話『スコアランキングを実装したい』

Last updated at Posted at 2022-12-22

リーダーボードが欲しいよ

ゲームは個人で遊ぶのも面白いのですが、プレイヤーどうしの交流要素があると盛り上がったりしますよね。直接の対戦でなくとも、スコアや達成度を競ったりする仕組みも考えられます。

それで、GitHubのIssueを漁っていたところ、リーダーボード(プレイヤーのスコアランキング)のAPIが欲しいよ、というIssueがありました。

WASM-4の作者のブルーノ兄貴は、「個人的には、興味があるのはレトロなコンピューターゲームなんだ。ソーシャルな機能は別に反対じゃないけど、もっといろんなゲームが集まってからかな……」みたいな感じでした。まあそういうことなので公式APIは当分実装されないでしょうが、いろいろ抜け道はありそうです。

WASM-4のセーブデータを外部から読み取る

WASM-4にはバンドルしたときのHTMLをカスタムする方法が用意されています。w4 bundleコマンドに--html-templateオプションでテンプレートファイルを渡せばいいようです。

これを利用して、HTML側にネットワークアクセスのコードを仕込みます。ゲームのセーブデータはローカルストレージに書き込まれますので、これを読み取れば現在のハイスコアなどは外部から読み取ることができます。

HTML自体にもSNSログインの機能を付けて、プレイヤーを認証できるようにしたいと思います。これでツイッターのあの人がどこまでプレイしたかとかわかるようになります。

ローカルストレージは文字列しか格納できませんが、WASM-4ランタイムのコードを見ると、z85というフォーマットでバイナリ列を文字列に変換してからローカルストレージに格納しているようです。Z85というのがどのような形式なのかはよく知らないのですが、WASM-4のランタイムからデコーダーのコードをコピーしてきて、デコードしたらDataViewでデータを読み取ります。

<script async>

    // function decode (string: string, dest: number[] | Uint8Array | Uint8ClampedArray): number
    function decode (string, dest) {

      const DECODER = [
        0x00, 0x44, 0x00, 0x54, 0x53, 0x52, 0x48, 0x00,
        0x4B, 0x4C, 0x46, 0x41, 0x00, 0x3F, 0x3E, 0x45,
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x40, 0x00, 0x49, 0x42, 0x4A, 0x47,
        0x51, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
        0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
        0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
        0x3B, 0x3C, 0x3D, 0x4D, 0x00, 0x4E, 0x43, 0x00,
        0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
        0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
        0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
        0x21, 0x22, 0x23, 0x4F, 0x00, 0x50, 0x00, 0x00
      ];

      let byte_nbr = 0,
          char_nbr = 0,
          value = 0;
      const string_len = string.length,
          dest_len = dest.length;

      if ((string.length % 5) == 0) {
          while (char_nbr < string_len) {
              const idx = string.charCodeAt(char_nbr++) - 32;
              if ((idx < 0) || (idx >= DECODER.length)) {
                  return byte_nbr;
              }
              value = (value * 85) + DECODER[idx];
              if ((char_nbr % 5) == 0) {
                  let divisor = 256 * 256 * 256;
                  while (divisor >= 1) {
                      if (byte_nbr >= dest_len) {
                          return byte_nbr;
                      }
                      dest[byte_nbr++] = (value / divisor) % 256;
                      divisor /= 256;
                  }
                  value = 0;
              }
          }
      }

      return byte_nbr;
    }
  
    setInterval(() => {
      const str = localStorage.getItem("Tower Climber-disk");
      const array = new Uint8Array(1000);
      const byte_nbr = decode(str, array)

      const view = new DataView(array.buffer)
      const version = view.getUint8(0);
      const x = view.getFloat32(1, true);
      const y = view.getFloat32(5, true);      
      console.log(version, x, y);
    }, 1000)

  </script>

ちなみに、エンディアンに注意です。WASMではリトルエンディアンなのですが、JavaScriptのDataViewではビッグエンディアンがデフォルトです。クソっ!

なお、ローカルストレージに書き込まれるときのキーは、デフォルトではゲーム名-diskになるようです。これも、--html-disk-prefixというオプションを指定することで

これでセーブデータを外部から読み取ることができました。あとはこれを適当なデータベースに書き込めばOKです。

参考文献

次回予告

次回はマルチプレイヤー機能を試してみます。こんなに簡単にマルチプレイヤーゲームになるなんて驚きです。

1
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
1
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?