このメモでやること

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

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

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


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

  • 2個1組のブロックが、上から落ちてきます。左右へ動かし、回して、置きます
  • 同じ絵が4つつながると消えます。消えて落ちた先でまた4つつながれば連鎖です
  • 連鎖すると、相手の盤面におじゃまが降ります
  • 左から3列目のいちばん上が埋まると負けです

AI にやらせるのは「組をどの列へ、どの向きで置くか」を決めることです。


用意するもの

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

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

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


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

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

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

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


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

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

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

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

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

    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;

      // 盤面は毎フレーム届く。 組が変わった時だけ考える
      if (m.me.curId === lastId) return;
      lastId = m.me.curId;

      // ここが AI の中身。 いまは「でたらめな列へ寄せて、落とす」だけ
      const x = Math.floor(Math.random() * 6);         // 0〜5 のどれかの列
      const act = [];
      for (let i = m.me.cur.x; i > x; i--) act.push(['left']);
      for (let i = m.me.cur.x; i < x; i++) act.push(['right']);
      act.push(['hard']);
      CH.postMessage({ from: 'ai', act: act });

      document.getElementById('log').textContent =
        m.me.placed + ' 組目 / ' + m.me.cleared + ' 個消した';
    };

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

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

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

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

見ているもの意味
m.me.canActいま操作が効くか。連鎖の最中や、次の組を待っている間は false
m.me.curId組が変わるたびに変わる番号。これが変わったら新しい組
m.me.cur.x操作している組の、いまの列 (左から 0〜5)。出てくる列は 2

操作は1回の返事にまとめて入れられます。上の例なら「左へ2回、そのあとハードドロップ」のように、並べた順に効きます。


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

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

① trisblocks を開く

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

② DEVELOP → AI DEVELOP → PAYA BLOCKS

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

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

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

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

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

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

組が勝手に左右へ散らばって落ちていけば成功です。画面の左上には、選んだファイルの名前と、効いた操作の数 (入力) が出ます。

ただし、この AI は盤面を一切見ていません。でたらめに寄せて落とすだけなので、手元で試した時は 47組を置いたところ (17秒ほど) で負けました。たまたま4つつながって消えることもありますが、それで正常です。


うまくいかないとき

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

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

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


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

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

盤面は m.me.board という文字列で届きます。board[y * 6 + x] が、左から x 列目・下から y 段目のマスで、'0' なら空です。 下から順に見ていって、はじめて空になる所がその列の高さになります。

      // 列ごとの高さを数える。 board[y * 6 + x]、 y = 0 がいちばん下
      const high = [0, 0, 0, 0, 0, 0];
      for (let i = 0; i < 6; i++) {
        while (high[i] < 13 && m.me.board[high[i] * 6 + i] !== '0') high[i]++;
      }
      const x = high.indexOf(Math.min(...high));       // いちばん低い列

いちばん低い列へ置くだけですが、平らに積むので長持ちします。手元で2回試したところ 66組 (34秒) と 73組 (36秒) まで伸び、置いているうちに 2〜3連鎖も起きました。

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

  • 置いたあとの盤面を自分で作って、4つつながる所を優先する
  • cw / ccw で回して、横向きや上下逆さまにも置く
  • m.me.next (次に来る3組) を見て、色をそろえて積む
  • m.me.garbReady (いま降ってこられるおじゃまの数) を見て、危ない時は消すのを急ぐ

届いているデータを覗く

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

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

ブラウザで F12 を押して Console タブを開くと、盤面・NEXT・HOLD・おじゃまの数などが毎フレーム流れていきます。


もう少し賢いサンプル

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

やっていることは「置ける所をすべて試して、いちばん点の高い所へ置く」です。連鎖が起きる所、同じ色がとなり合う所ほど点が高く、高く積むほど低くなります。 250段ほどで、ファイルの先頭に受け取る情報と操作の説明を書いてあります。 送った操作が効いたかを確かめてから次を送る書き方も入っているので、自分の AI を育てる土台に使ってください。


ここまでのまとめ

  • AI は HTML ファイル1つ。AI DEVELOP で PAYA BLOCKS を選び、ファイルをえらぶ で指定するだけ
  • hello と pong に game: 'paya' を添える … 忘れると「読み込み失敗」
  • 盤面は毎フレーム届く。me.canAct が true で、me.curId が変わった時だけ考えれば十分です
  • 返す中身は { from: 'ai', act: [['left'], ['hard']] } の形
  • 盤面は board[y * 6 + x]、y = 0 がいちばん下

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

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