🦖

PlaywrightでDino Gameを操作してみた

に公開

はじめに

画面系のテストに携わる機会があり、初めて Playwright を使うことになりました。
しかし、最初はわからないことだらけで、情報収集や使い方の理解に苦戦しました。
そこで、学習のモチベーションを高めるため、苦戦しているものと好きなジャンルを組み合わせて学んでみようと考えました。

本記事では、「Playwright × ゲーム」 というテーマで、Google Chromeブラウザ(以降、Chrome)の隠しゲーム「Dino Game」をPlaywrightを用いて自動操作を実装しました。
そこから学んだ内容をご紹介します。

Dino Gameは、恐竜が飛んだりしゃがんだりして障害物を避けハイスコアを目指すゲームです。


出典: Google Chromeオフライン恐竜ゲーム (Chrome://dino)
※ Google Chrome ブラウザのアドレスバーに「chrome://dino」と入力することでアクセスできます。

実行環境は以下の通りです。また、本記事では自己啓発範囲内での調査として、GitHub CodespacesやPlaywrightを使用しています

  • GitHub Codespaces
  • Node.js v22.17.0
  • Playwright 1.54.2

Playwrightとは

Playwrightは、Microsoftが開発したブラウザ自動操作ライブラリです。

主な特徴は以下の通りです。

  1. クロスブラウザ対応
    Playwrightは、Chrome、Firefox、Safariなど複数のブラウザをサポートしています。
    そのため、1つのコードで異なるブラウザの同じ操作を自動化でき、クロスブラウザテストでのテストが容易になります。

  2. 豊富な操作が可能
    ページ遷移やクリック操作、キーボード入力、ページ内要素の取得など複数の操作をプログラム上で実施することが可能です。

  3. テスト自動化とCI/CD
    GUI操作なしでもテスト実行可能なためGitHub ActionsやJenkinsといったCI/CDツールに取り込みやすいです。
    また、テスト中にスクリーンショットや録画ができるためテスト結果確認が簡単に行えます。

Playwrightはテスト自動化に使用されることが主ですが、今回のようにゲーム操作やブラウザ操作の自動化にも活用できます。

GitHub Codespacesでの環境構築

GitHub Codespacesは、GitHub が提供するコードエディターです。
クラウド上に開発環境を構築でき、Web ブラウザからコードエディターを直接利用できます。

1. Dino Gameをフォークする

GitHub Codespacesで扱うため、Dino Gameをフォークします。

本記事では、Githubで公開されているwayouさん(ライセンス上では牛さん)のリポジトリを使用させていただきました。
BSD 3-Clause License全文はこちらを参照してください。

2. GitHub Codespacesでフォークしたリポジトリを開く

フォークが完了したら、画面の中ほどにあるcodeボタンを押下し、CodespacesからCodespacesを作成します。
詳しくは、GitHubのドキュメントを参照ください。
https://docs.github.com/ja/codespaces/developing-in-a-codespace/creating-a-codespace-for-a-repository

3. Playwrightのインストール

Github Codespaces上にプリインストールされている、Node環境のnpmを使ってPlaywrightをインストールします。

npm install playwright

動作させるブラウザもインストールしておきます。

npx playwright install

準備が整ったら、いよいよ自動操作の実装に入ります。

Playwrightで自動操作

1. ゲーム起動

まずは、ゲーム開始画面を表示するところまで実装してみます。
以下のコードは、Playwrightを使用してブラウザを操作し、Dino Gameの開始画面を表示するためのテストコードです。

js
import { chromium } from 'playwright';

(async () => {
  // ブラウザ起動
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    recordVideo: {
      dir: 'videos/',
      size: { width: 800, height: 600 }
    }
  });
  const page = await context.newPage();

  // Dino Gameページを開く
  await page.goto('file:///workspaces/dino-game-playwright/index.html', { waitUntil: 'load' });

  // canvas が出るまで待機
  await page.waitForSelector('canvas.runner-canvas');

  // 開始画面のスクリーンショット
  await page.screenshot({ path: 'debug_loaded.png' });

  await context.close();
  await browser.close();
})();

ブラウザを起動し、ゲーム画面を開いてスクリーンショットを撮っています。
ポイントは以下になります。

  1. コンテナ環境で実行するため、ヘッドレスモードでブラウザを起動
  2. 録画を保存したいためrecordVideoオプションで設定
  3. タブを作成してローカルファイルを開く
  4. canvasが描画されるまで待ち、スタート画面のスクリーンショットを撮影

次にゲームを開始してみるところまで確認してみます。
開始画面にアクセス後、スペースを押すことでゲームを開始することができるためスペースを押す処理を追加すれば完了です。

js
// スタート画面でスペースを押す
  await page.keyboard.press('Space');

2. 障害物の認識と操作

ゲームでは下記2つのキー操作があります。

  • スペースキー: ジャンプ
  • 下キー: しゃがむ

障害物を認識して、適切なタイミングでキー操作を行うことで自動操作を実現できそうです。
障害物には以下があります。

  • CACTUS_SMALL / CACTUS_LARGE : サボテン
  • PTERODACTYL : 高さが異なる鳥型の障害物 (プテラノドンだと思っていました...)

プテラノドンの飛ぶ高さは3種類存在します。

  • ジャンプで避ける高さ
  • しゃがんで避ける高さ
  • 頭上を通り過ぎる高さ

障害物によって必要な操作を実装します。

js
      const obstacles = runner.horizon.obstacles;
      for (const obs of obstacles) {
        const type = obs.typeConfig.type;

        // サボテンはジャンプ
        if ((type === 'CACTUS_SMALL' || type === 'CACTUS_LARGE') && obs.xPos <= 100) {
          needJump = true;
        }

        // 地面に近いプテラノドンはジャンプ
        if (type === 'PTERODACTYL' && obs.yPos === 100 && obs.xPos <= 100) {
          needJump = true;
        }

        // 目の前を飛ぶプテラノドンはしゃがむ
        if (type === 'PTERODACTYL' && obs.yPos === 75 && obs.xPos <= 120) {
          needDuck = true;
        }
      }

描画されている障害物を取得して、それぞれの障害物に対して必要なアクションを行います。

サボテンはジャンプ処理、プテラノドンは高さによって処理を変えます。
ジャンプもしゃがみもキーを押下する長さによって挙動が違うため、障害物が通り過ぎるまで押し続けるように実装します。

js
    // ジャンプ処理
    if (needJump) {
      await page.keyboard.down('Space');   // 押しっぱなし開始
      jumping = true;
    } else if (!needJump) {
      await page.keyboard.up('Space');     // 離す
      jumping = false;
    }

    // しゃがみ処理
    if (needDuck) {
      await page.keyboard.down('ArrowDown');
      ducking = true;
    } else if (!needDuck) {
      await page.keyboard.up('ArrowDown');
      ducking = false;
    }

必要なアクションに応じてキー操作を行っています。
これで、描画された障害物に応じた操作が実装できました。

描画されるたびに必要なアクションを行うことで、自動的に障害物を避けることができるようになります。

js
  await page.evaluate(() => {
    function loop() {
      // 描画された障害物に応じた操作
      requestAnimationFrame(loop);
    }
    requestAnimationFrame(loop);
  });

ゲーム画面の起動からゲームを開始し、障害物をよけるところまで自動化できました。

ここでは、説明を割愛していますが、実際にはGame Over判定の処理も追加して挙動を確認しています。
実際に自動操作を動かして、ゲームをプレイさせた様子は以下の動画になります。
背景スクロール速度の変化を考慮していないため、スピードが速くなるとゲームオーバーになります。

より精度を高めるには、背景スクロール速度を取得してジャンプ位置を調整するなどの工夫も必要になりそうです。

おわりに

Playwrightをゲームと組み合わせながら学んでみました。
苦手な勉強や自己研鑽でも、好きなジャンルと組み合わせ楽しみながら学ぶことができました。

基本的な機能を理解し、実際に動作させる中で以下の点が特に有益でした。

  • スクリーンショットの取得:打鍵結果を視覚的に確認できるため、問題の特定が容易になる。
  • 要素の待機:ページのロードや特定の要素が表示されるまで待機することで、安定した自動打鍵ができる。

みなさんもぜひ、自分なりの工夫を取り入れて、学びを遊びのように楽しんでみてください。
小さな発見や達成感が積み重なることで、学習のハードルがぐっと下がり、新しい世界が開けた気がします。

なお、掲載したソースコードはサンプルになります。本ソースコードを使用することで発生するいかなる損害や不利益について、当社は一切の責任を負いませんので自己の責任においてご利用ください。

Discussion