このメモでやること

trisblocks の中で遊べる MAGI BLOCKS でも、自分で書いた AI にプレイさせることができるようになりました。 仕組みは単純で、ゲームが1フレームごとに盤面を渡してくれるので、こちらは「どう動かすか」を返すだけです。

このメモでは、40段ほどのファイルを1つ作って、ゲーム画面から動かすところまでをやります。 最後に、少しだけ盤面を読ませてみます。

受け取れる情報と送れる操作のすべては ② API一覧 に並べてあります。


MAGI BLOCKS はこういうゲームです

  • パネルが上からぶら下がっていて、時間がたつと1段ずつ下りてきます
  • 自分は盤面の下を左右に動き、真上の列のいちばん下のパネルを取って、別の列へ投げ返します
  • 投げた列で同じ色が縦に3つ並ぶと、つながった同じ色がまとめて消えます
  • 消すと、相手の盤面に段が下りてきます
  • パネルが下の線を越えたまま、次の段が下りてくると負けです

落ちてくるものを待つゲームではないので、AI は好きな時に、好きなだけ動けます。


用意するもの

ブラウザだけです。インストールも、アカウントも、開発環境も要りません。

  • メモ帳など、文字が打てるもの
  • Chrome や Edge などのブラウザ (パソコン)

AI は HTML ファイル1つとして書きます。書けたら、ゲームの画面からそのファイルを選ぶだけで動きます。 AI を動かす画面 (AI DEVELOP) はパソコン用で、スマホの画面には出ません。


いちばん大事な決まり : MAGI BLOCKS 用だと名乗る

AI は、ゲームに返事をする時に 「MAGI BLOCKS 用です」と名乗る必要があります。名乗りは game: 'magi' の一言です。

名乗らなかった AI は TRISBLOCKS 用として扱われ、MAGI BLOCKS の画面では 「読み込み失敗」と出て動きません。 盤面の形も操作もゲームごとに違うので、取り違えたまま動かさないための決まりです。

下のサンプルには、はじめから入れてあります。


ステップ1 : ファイルを作る

メモ帳を開いて、下の中身をまるごと貼り付けてください。 そして ファイル名を my-magi.html にして、デスクトップなど分かりやすい場所に保存します。

※ メモ帳で保存するときは「ファイルの種類」を すべてのファイル、「文字コード」を UTF-8 にしてください。

    <!DOCTYPE html>
    <meta charset="utf-8">
    <title>わたしのAI (MAGI BLOCKS)</title>
    <p id="log">まだ動いていません</p>

    <script>
    const CH = new BroadcastChannel('tb-ai');          // ゲームとの通話回線
    const WHO = { name: 'MyMagi', game: 'magi' };      // 名前と、どのゲーム用の AI か
    let lastF = -99;

    CH.onmessage = (ev) => {
      const m = ev.data;
      if (!m || m.from !== 'game') return;

      // 「そこに居る?」と聞かれたら、名乗って返事をする
      if (m.type === 'ping') {
        CH.postMessage({ from: 'ai', type: 'pong', name: WHO.name, game: WHO.game });
        return;
      }
      if (m.type !== 'state' || !m.me || !m.me.canAct) return;

      // 盤面は毎フレーム届く。 10フレーム (1/6秒) に1回だけ動く
      if (m.f - lastF < 10) return;
      lastF = m.f;

      // ここが AI の中身。 いまは「でたらめな列へ行って、手が空なら取る / 持っていれば投げる」だけ
      const x = Math.floor(Math.random() * 7);         // 0〜6 のどれかの列
      const act = [];
      for (let i = m.me.px; i > x; i--) act.push(['left']);
      for (let i = m.me.px; i < x; i++) act.push(['right']);
      act.push([m.me.held.n ? 'throw' : 'grab']);
      CH.postMessage({ from: 'ai', act: act });

      document.getElementById('log').textContent =
        m.me.thrown + ' 回投げた / ' + m.me.cleared + ' 個消した';
    };

    CH.postMessage({ from: 'ai', type: 'hello', name: WHO.name, game: WHO.game });   // 起動したことを知らせる
    </script>

やっていることは4つです。

  • 回線をつなぐ … BroadcastChannel('tb-ai') が、ゲームとの通話回線です
  • 名乗る … 起動した時の hello と、呼びかけへの返事 pong の両方に、名前と game: 'magi' を添えます
  • 話を聞く … onmessage に、ゲームから盤面 (state) が毎フレーム届きます
  • 返事をする … postMessage で、やりたい操作を送り返します

盤面の中で使っているのは4つだけです。

見ているもの意味
m.me.canActいま操作が効くか。始まる前や、負けたあとは false
m.fいま何フレーム目かの番号。落ちてくる「組」が無いので、時間で区切って考える
m.me.px自分のいる列 (左から 0〜6)
m.me.held手に持っているパネル。c が色、n が数。持っていなければ n が 0

操作は grab (いまの列のいちばん下のパネルを取る) と throw (持っているパネルをいまの列へ投げる) の2つです。 1回の返事にまとめて入れられ、「左へ2回、そのあと取る」のように並べた順に効きます。


ステップ2 : ゲーム画面から動かす

ここからはブラウザの操作だけです。

① trisblocks を開く

trisblocks を開いて、タイトル画面を出します。

② DEVELOP → AI DEVELOP → MAGI BLOCKS

メニューの DEVELOP → AI DEVELOP と進むと、「どのゲームで遊びますか。」と聞かれます。 ここで MAGI BLOCKS を選びます。開いた画面の見出しが AI DEVELOP / MAGI BLOCKS になっていれば合っています。

③ 「ファイルをえらぶ」で my-magi.html を指定する

いちばん上の あなたの AI のすぐ下に ファイルをえらぶ があります。これを押して、保存した my-magi.html を選びます。

選んだ瞬間にランプが緑になり、「MyMagi が動いています」と出れば成功です。 ファイルはゲームの中の隔離された枠で動くので、どこに置いてあっても構いませんし、アップロードも要りません。 書き直したときは、選び直すだけで新しい中身に入れ替わります。

④ 対戦相手を確かめて「決定」

同じ画面の 対戦相手 には、CPU HARD と AI と対戦 (投稿された AI からえらぶ) が並んでいます。 はじめは CPU HARD のままで構いません。ランプが緑なら 決定 が押せるので、押すと試合が始まります。

盤面の下のキャラクターが左右へ走り回って、パネルを取っては投げていれば成功です。画面の左上には、選んだファイルの名前と、効いた操作の数 (入力) が出ます。

ただし、この AI は盤面を一切見ていません。パネルをあちこちへ運ぶだけなので、手元で試した時は 28秒ほどで、下りてくる段に押し切られて負けました。たまたま3つ並んで消えることもありますが、それで正常です。


うまくいかないとき

あなたの AI の所に出る文字・症状見るところ
読み込み失敗: この AI は TRISBLOCKS 用ですgame を名乗っていません。hello と pong の両方に game: 'magi' を添えてください
読み込み失敗: この AI は MAGI BLOCKS 用ですゲームの選び違いです。AI DEVELOP を開き直して、MAGI BLOCKS を選び直してください
この AI から返事がありませんpong を返していないか、途中でエラーになって止まっています
動いているのに、何も取らないact の中身が二重の配列 ([['grab']]) になっているか。操作の名前が合っているか
文字化けする保存時の文字コードが UTF-8 になっているか

ブラウザの F12 を押すと開発者ツールが出ます。Console タブに赤い文字が出ていれば、それが原因です。

ファイルをダブルクリックして直接開いても、ゲームにはつながりません。かならず AI DEVELOP の ファイルをえらぶ から指定してください。


ステップ3 : 盤面を読ませてみる

動くことが分かったら、今度は盤面を見て決めるように直してみます。 書き替えるのは「ここが AI の中身」のすぐ下にある const x = … の1段だけです。

盤面は m.me.board という文字列で届きます。board[y * 7 + x] が、左から x 列目・上から y 段目のマスで、'0' なら空です。 パネルは上から詰まっているので、上から順に見ていって、空になる手前がその列のいちばん下 (取れるパネル) になります。

      // 列ごとに、いちばん下 (手前) のパネルの色を調べる
      // board[y * 7 + x]、 y = 0 がいちばん上。 パネルは上から詰まっている
      const color = [];
      for (let i = 0; i < 7; i++) {
        let y = 0;
        while (y < 14 && m.me.board[y * 7 + i] !== '0') y++;
        color.push(y ? m.me.board[(y - 1) * 7 + i] : '');
      }
      let x;
      if (m.me.held.n) {
        // 持っている: 同じ色がいちばん下にある列へ投げる
        x = color.indexOf(String(m.me.held.c));
      } else {
        // 手が空: いちばん下の色が、ほかの列と同じになっている列から取る
        x = color.findIndex((c) => c && color.indexOf(c) !== color.lastIndexOf(c));
      }
      if (x < 0) x = Math.floor(Math.random() * 7);    // 当てが無ければ、でたらめな列

「同じ色の列から取って、同じ色の列へ投げる」を繰り返すだけです。投げた先で同じ色が縦に3つ並べば消えます。 列は7つ、色は星パネルを入れても5種類なので、いちばん下の色が同じ列はたいてい見つかります。

これだけで見違えるように強くなります。手元では CPU HARD を相手に 3分半ねばり、7連鎖まで出ました (最後は押し切られました)。

ここから先は、たとえば次のように育てていけます。

  • 消えたあとに上へ詰まるパネルを先読みして、連鎖になる所から消す
  • 消えている間 (0.75秒) も動けるので、その間に次を消して連鎖をつなぐ
  • hold で1色預けておいて、色を選べるようにする
  • m.me.garbage (下りてくるのを待っている段) を見て、危ない時は消すのを急ぐ

届いているデータを覗く

どういうデータが届いているのか見たい時は、state を受け取った所に1段足すのがいちばん手軽です。

    console.log(m);        // state をそのまま開発者ツールへ出す

ブラウザで F12 を押して Console タブを開くと、盤面・手持ち・下りてくる段の数などが毎フレーム流れていきます。


もう少し賢いサンプル

AI DEVELOP の画面には、AI を選ぶ前だけ サンプルの AI をダウンロード というリンクが出ています。 sample-magi.html がそれで、そのまま「ファイルをえらぶ」で動かせます。

やっていることは「取って投げたら消える所をさがして、そこへ運ぶ」です。2列ぶんで足りなければ3列ぶんを集め、 やることが無い時は自分で段を足して材料を増やします。 200段ほどで、ファイルの先頭に受け取る情報と操作の説明を書いてあります。 送った操作が効いたかを確かめてから次を送る書き方も入っているので、自分の AI を育てる土台に使ってください。


ここまでのまとめ

  • AI は HTML ファイル1つ。AI DEVELOP で MAGI BLOCKS を選び、ファイルをえらぶ で指定するだけ
  • hello と pong に game: 'magi' を添える … 忘れると「読み込み失敗」
  • 盤面は毎フレーム届く。落ちてくる組が無いので、時間で区切って考える
  • 返す中身は { from: 'ai', act: [['left'], ['grab']] } の形。操作は grab と throw
  • 盤面は board[y * 7 + x]、y = 0 がいちばん上

届く情報のすべてと、得点・攻撃の数え方は ② API一覧 にあります。

ほかのゲームの AI は、PAYA BLOCKS で自作AIを動かす ① と trisblocks で自作AIを動かす ① をどうぞ。