ChatGPTでブロック崩しのゲームを実装する方法とプロンプトの工夫

注意事項

この記事は、ChatGPTに対して

  • 問題と解決策の概要
  • 例示用のコード

を渡して生成した実験的な記事になります。

概要

ChatGPTを用いてブロック崩しのゲームを実装する方法と、プロンプトの工夫によって生成されるコードの可能性について説明する記事です。

実際に生成されたブロック崩しスクリーンショットです。

記事に実際に生成されたコードがあるので、手元で動かしてみてください。
動きはしますが、課題が残るような挙動ではありそうです。

ChatGPTを使ってブロック崩しのゲームを生成する方法とプロンプトの形式

ChatGPTを使用してブロック崩しのゲームを生成する際には、特定のプロンプトを用意します。
この例では、プログラマーとしてロールプレイをすることを指示し、ブラウザ上で動作するブロック崩しゲームのプログラムを実装するよう要求されます。
その後、必要なファイルの一覧作成やコード実装を行う手順に従って進めます。

- あなたはプログラマーとしてロールプレイをしてください
- [Platform] ... ブラウザ
- [Goal] ... ブロック崩し
- あなたは、[Goal]のゲームを[Platform]で動かすために必要なプログラムを書く必要があります
- ゲームを書くためにあなたは以下の手順に従って行動してください
  - 必要なファイルの一覧を作成する
  - 各ファイルに必要なコードを実装する
    - 実装する場合、キー入力によって動きが与えられる場合には、キー入力を受け取り、それに応じて動きを変えるようにしてください

生成されたコードの構成と機能

ChatGPTによって生成されたコードは、index.html, styles.css, script.jsの3つのファイルで構成されています。
これらのファイルは、ブロック崩しのゲームをブラウザ上で実行するために必要な機能を提供しています。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ブロック崩し</title>
    <link rel="stylesheet" type="text/css" href="style.css">
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <script src="script.js"></script>
  </body>
</html>
#canvas {
  background-color: black;
  border: solid 1px white;
}
// キャンバス要素の取得
const canvas = document.getElementById("canvas");

// キャンバスのコンテキストの取得
const ctx = canvas.getContext("2d");

// ボールの初期位置・半径・速度の設定
let x = canvas.width / 2;
let y = canvas.height - 30;
let dx = 2;
let dy = -2;
const ballRadius = 10;

// パドルの初期位置・幅・高さの設定
const paddleHeight = 10;
const paddleWidth = 75;
let paddleX = (canvas.width - paddleWidth) / 2;

// パドルの移動用のフラグ
let rightPressed = false;
let leftPressed = false;

// ブロックの初期位置・幅・高さの設定
const brickRowCount = 3;
const brickColumnCount = 5;
const brickWidth = 75;
const brickHeight = 20;
const brickPadding = 10;
const brickOffsetTop = 30;
const brickOffsetLeft = 30;

// スコア・ライフの初期値の設定
let score = 0;
let lives = 3;

// ブロックの位置を表す2次元配列の初期化
const bricks = [];
for (let c = 0; c < brickColumnCount; c++) {
  bricks[c] = [];
  for (let r = 0; r < brickRowCount; r++) {
    bricks[c][r] = { x: 0, y: 0, status: 1 };
  }
}

// キーイベントのリスナーを追加
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);

// キーを押したときに呼ばれる関数
function keyDownHandler(e) {
  if (e.keyCode === 39) {
    rightPressed = true;
  } else if (e.keyCode === 37) {
    leftPressed = true;
  }
}

// キーを離したときに呼ばれる関数
function keyUpHandler(e) {
  if (e.keyCode === 39) {
    rightPressed = false;
  } else if (e.keyCode === 37) {
    leftPressed = false;
  }
}

// 衝突判定の関数
function collisionDetection() {
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      const b = bricks[c][r];
      if (b.status === 1) {
        if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
          dy = -dy;
          b.status = 0;
          score++;
          if (score === brickRowCount * brickColumnCount) {
            alert("You win!");
            document.location.reload();
          }
        }
      }
    }
  }
}

// 描画の関数
function draw() {
  // キャンバス全体をクリア
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // ブロックの描画
  for (let c = 0; c < brickColumnCount; c++) {
    for (let r = 0; r < brickRowCount; r++) {
      if (bricks[c][r].status === 1) {
        const brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
        const brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
        bricks[c][r].x = brickX;
        bricks[c][r].y = brickY;
        ctx.beginPath();
        ctx.rect(brickX, brickY, brickWidth, brickHeight);
        ctx.fillStyle = "#0095DD";
        ctx.fill();
        ctx.closePath();
      }
    }
  }

  // ボールの描画
  ctx.beginPath();
  ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();

  // パドルの描画
  ctx.beginPath();
  ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
  ctx.fillStyle = "#0095DD";
  ctx.fill();
  ctx.closePath();

  // スコアの描画
  ctx.font = "16px Arial";
  ctx.fillStyle = "#0095DD";
  ctx.fillText("Score: " + score, 8, 20);

  // ライフの描画
  ctx.font = "16px Arial";
  ctx.fillStyle = "#0095DD";
  ctx.fillText("Lives: " + lives, canvas.width - 65, 20);

  // ボールが画面外に出たときの処理
  if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
    dx = -dx;
  }
  if (y + dy < ballRadius) {
    dy = -dy;
  } else if (y + dy > canvas.height - ballRadius) {
    if (x > paddleX && x < paddleX + paddleWidth) {
      dy = -dy;
    } else {
      lives--;
      if (!lives) {
        alert("Game over");
        document.location.reload();
      } else {
        x = canvas.width / 2;
        y = canvas.height - 30;
        dx = 3;
        dy = -3;
        paddleX = (canvas.width - paddleWidth) / 2;
      }
    }
  }

  // パドルの移動
  if (rightPressed && paddleX < canvas.width - paddleWidth) {
    paddleX += 7;
  } else if (leftPressed && paddleX > 0) {
    paddleX -= 7;
  }

  // ボールの移動
  x += dx;
  y += dy;

  // 衝突判定の呼び出し
  collisionDetection();

  // 次のフレームの描画をリクエスト
  requestAnimationFrame(draw);
}

// 初期描画の呼び出し
draw();

コードの不完全な部分と改善方法

生成されたコードはほぼ動作するものの、一部のコードが未定義であることが課題です。
これは、GPT-3.5が学習データに基づいてコードを生成しているため、ブロック崩しに関連するデータが多く含まれている可能性があります。
不完全な部分を改善するためには、コードの修正や追加が必要です。

今回の場合は、以下の画像の部分のみが不足していたコードでした。

プロンプトの工夫がコード生成の精度に与える影響

プロンプトや質問の仕方を工夫することで、より正確なコードが生成される可能性があります。
ChatGPTは、与えられたプロンプトに応じて異なる結果を生成するため、プロンプトの最適化によってコード生成の精度が向上することが期待されます。

結論

ChatGPTを用いてブロック崩しのゲームを実装する方法と、プロンプトの工夫によって生成されるコードの可能性を学びました。この知識を活かして、さらに多様なプロジェクトに取り組むことができるでしょう。