AI (o3-mini-high) が一発ノーミスで実現！ブロック崩しゲーム自動生成の実例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82835/2e1b5ac4-a4d7-7fd2-72fc-2d8031369e42.png)


今回は、ChatGPT o3-mini-high を使用して、**p5.js** によるブロック崩しゲームを一発ノーミスで生成できた事例をご紹介します。  
生成されたコードは、[p5.js エディタ](https://editor.p5js.org/) にそのまま貼り付けるだけで実行できる点も大きな魅力です。

## 目次
- [はじめに](#はじめに)
- [使用環境](#使用環境)
- [使用したプロンプト](#使用したプロンプト)
- [生成されたコード](#生成されたコード)
- [動作確認](#動作確認)
- [まとめ](#まとめ)

## はじめに
今回紹介するブロック崩しゲームは、以下の要件を含む詳細なプロンプトを元に、ChatGPT o3-mini-high によって自動生成されました。

- **キャンバスと初期設定**  
  `createCanvas(windowWidth, windowHeight)` を用いて全画面表示、背景やテキストの初期化を実施。

- **ブロック配置**  
  画面上部に 5 段×10 列のブロックを配置。耐久度や色の変化を設定し、破壊時にエフェクトも追加可能。

- **パドルとボール**  
  画面下部に配置されるパドルは、マウスまたはキーボードで操作可能。  
  ボールはパドルの上部近くに初期配置し、ランダムな角度で発射されます。

- **衝突判定**  
  円と矩形の衝突判定ロジックを実装し、ブロック、パドル、壁との衝突処理を行います。

- **スコアリングとゲーム進行**  
  ブロック破壊時のスコア加算、全ブロック破壊でゲームクリア、ボールが画面下部に落ちた際のライフ減少などを実装。

## 使用環境
- **p5.js エディタ**  
  [https://editor.p5js.org/](https://editor.p5js.org/) にて、生成されたコードをそのまま実行できます。  
- **ChatGPT o3-mini-high**  
  今回のコード生成には、ChatGPT o3-mini-high を採用。  
  高精度なコード生成能力により、一発ノーミスで要件を満たすコードが作成されました。

## 使用したプロンプト
以下は、今回のコード生成に使用した詳細なプロンプトです。

```markdown
# 改善後のプロンプト例（ブロック崩し編）

以下の要件を含め、**p5.js** を使用してブロック崩しゲームを作成してください。

## 1. キャンバスと初期設定
- `createCanvas(windowWidth, windowHeight)` を使用し、ブラウザ全画面で表示できるようにしてください。
- 背景色やテキスト描画を含め、必要に応じて初期化処理を行ってください。
- フォントや色など、デザイン面でも可読性を意識してください。

## 2. ブロック配置
- 画面上部にブロックを複数段（例: 5 段×10 列）配置してください。
- ブロックごとに異なる色や耐久度（壊れにくさ）を設定しても構いません。
  - 例: 耐久度が 2 以上あるブロックは 1 回の衝突で色が薄くなり、2 回目で壊れる など。
- ブロックが壊されたときのエフェクト（パーティクルや簡易アニメーション）があれば、演出として追加しても構いません。

## 3. パドル（プレイヤーの操作領域）
- パドルを画面下部に配置し、マウスまたはキーボード操作によって横方向へ移動できるようにしてください。
- パドルの幅や色を適宜設定し、見やすくかつ操作しやすい大きさにしてください。
- ゲーム性を高めるために、パドルの両端部分に当たるとボールの反射角度が変化するようにするなど、調整を行ってください。

## 4. ボール
- 初期位置はパドルの上部近くに設定し、スタート時にランダムまたは固定角度で発射されるようにしてください。
- ボールの移動はフレームごとに座標を更新し、**壁・ブロック・パドル**への衝突判定を行ってください。
  - 壁やパドルに衝突した場合は、**反射**を行う。
  - ブロックに衝突した場合は、ブロックの耐久度を減らし、必要に応じて破壊（削除）します。

## 5. 衝突判定の実装
- ボールとブロックとの衝突判定は、**四角形と円の当たり判定**を検出するしくみを導入してください。
- パドルとの衝突判定も同様に行い、適切に反射角を計算してください。
  - 衝突判定処理のロジックがわかりやすいよう、コメントを入れてください。

## 6. スコアリングとゲーム進行
- ブロックを破壊するたびに**スコア**が加算されるようにしてください。
- **全ブロックを破壊**したらゲームクリアとし、クリア画面や再スタートの案内を表示してください。
- ボールが画面下部から外れてしまったら、ライフを減らすようにしてください。
  - ライフをすべて失ったらゲームオーバーにし、リスタートできるような処理を追加してください。

## 7. ユーザーインターフェース
- ゲーム中にスコアやライフなどの情報を表示してください。
- スタート画面やゲームオーバー画面など、**ゲームの状態**に応じた画面表示を行うとわかりやすいです。
- 音声を追加して、ブロックが壊れた音やボールが跳ね返る音を再生しても構いません。

## 8. デバッグと最適化
- 1 フレームあたりの描画がスムーズに行われるよう、コードを**最適化**してください。
  - 不必要な計算やオブジェクト生成を極力避けるなど。
- 当たり判定が間違っていないか、ビジュアルデバッグ（衝突判定の可視化）などを利用して検証してください。

## 9. 拡張機能（任意）
- アイテム（パワーアップ）をブロック破壊時に出現させ、パドルが拡大したり、ボールが増えたりする要素を追加しても構いません。
- レベル設計や難易度調整、あるいはスコアランキング機能など、よりゲーム性を高める工夫を加えてください。

## 10. コードの可読性
- 変数名や関数名は意味を持つわかりやすい名前を使用し、主要な処理にはコメントを入れてください。
- 今後の機能拡張やバグ修正がしやすいように、ファイル分割などの工夫も検討してください。

```

## 生成されたコード

以下に、実際に生成されたコードを掲載します。
このコードは、p5.js のキャンバス上でブロック崩しゲームとして動作します。

```js
// グローバル変数
let paddle;
let ball;
let blocks = [];
const rows = 5;
const cols = 10;
let blockWidth;
const blockHeight = 30;
const blockPadding = 5;
let score = 0;
let lives = 3;
let gameState = "start"; // "start", "playing", "gameover", "gameclear"

// 初期設定
function setup() {
  createCanvas(windowWidth, windowHeight);
  textAlign(CENTER, CENTER);
  rectMode(CORNER);
  // パドルの初期化
  paddle = new Paddle();
  // ボールの初期化（パドルの上部近くに配置）
  ball = new Ball();
  // ブロック配置用の幅計算（左右の余白を含める）
  blockWidth = (width - (cols + 1) * blockPadding) / cols;
  createBlocks();
}

// ブロックの生成
function createBlocks() {
  blocks = [];
  const offsetY = 50; // 上部からのオフセット
  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      const x = blockPadding + col * (blockWidth + blockPadding);
      const y = offsetY + row * (blockHeight + blockPadding);
      // 例として、偶数段は耐久度1、奇数段は耐久度2に設定
      const durability = (row % 2 === 0) ? 1 : 2;
      blocks.push(new Block(x, y, blockWidth, blockHeight, durability));
    }
  }
}

// 毎フレームの描画処理
function draw() {
  background(0);

  // ゲーム状態に応じた画面表示
  if (gameState === "start") {
    drawStartScreen();
  } else if (gameState === "playing") {
    playGame();
  } else if (gameState === "gameover") {
    drawGameOverScreen();
  } else if (gameState === "gameclear") {
    drawGameClearScreen();
  }
  
  // スコアとライフの表示
  fill(255);
  textSize(20);
  text("Score: " + score, width - 80, 30);
  text("Lives: " + lives, 80, 30);
}

// ゲーム中の処理
function playGame() {
  // パドルの更新と表示
  paddle.update();
  paddle.show();
  
  // ボールの更新、各種判定と表示
  ball.update();
  ball.checkEdges();
  ball.checkPaddleCollision(paddle);
  ball.checkBlockCollision();
  ball.show();
  
  // 各ブロックの表示
  for (let i = blocks.length - 1; i >= 0; i--) {
    blocks[i].show();
  }
  
  // 全ブロックが破壊された場合
  if (blocks.length === 0) {
    gameState = "gameclear";
  }
}

// パドルクラス
class Paddle {
  constructor() {
    this.width = 150;
    this.height = 20;
    this.x = width / 2 - this.width / 2;
    this.y = height - this.height - 20;
    this.speed = 10;
  }
  
  update() {
    // マウス操作でパドルの位置を更新
    this.x = constrain(mouseX - this.width / 2, 0, width - this.width);
    
    // キーボード操作（左右矢印キー）も併用可能
    if (keyIsDown(LEFT_ARROW)) {
      this.x -= this.speed;
    }
    if (keyIsDown(RIGHT_ARROW)) {
      this.x += this.speed;
    }
    this.x = constrain(this.x, 0, width - this.width);
  }
  
  show() {
    fill(200);
    rect(this.x, this.y, this.width, this.height);
  }
}

// ボールクラス
class Ball {
  constructor() {
    this.radius = 12;
    this.reset();
  }
  
  // ボールの初期配置と速度設定
  reset() {
    // パドルの中央上部付近に配置
    this.x = paddle.x + paddle.width / 2;
    this.y = paddle.y - this.radius - 2;
    // 発射角度をランダムに（ただし上向き）
    let angle = random(-PI / 4, -3 * PI / 4);
    const speed = 7;
    this.dx = speed * cos(angle);
    this.dy = speed * sin(angle);
    // 念のため上向きになるよう補正
    if (this.dy > 0) this.dy *= -1;
  }
  
  update() {
    this.x += this.dx;
    this.y += this.dy;
    
    // 画面下部に落ちた場合：ライフ減少＆リセット、ライフが尽きればゲームオーバー
    if (this.y - this.radius > height) {
      lives--;
      if (lives > 0) {
        this.reset();
      } else {
        gameState = "gameover";
      }
    }
  }
  
  // 壁との衝突判定と反射
  checkEdges() {
    // 左右の壁
    if (this.x - this.radius < 0) {
      this.x = this.radius;
      this.dx *= -1;
    }
    if (this.x + this.radius > width) {
      this.x = width - this.radius;
      this.dx *= -1;
    }
    // 上部の壁
    if (this.y - this.radius < 0) {
      this.y = this.radius;
      this.dy *= -1;
    }
  }
  
  // パドルとの衝突判定と反射角の調整
  checkPaddleCollision(paddle) {
    if (
      this.x + this.radius > paddle.x &&
      this.x - this.radius < paddle.x + paddle.width &&
      this.y + this.radius > paddle.y &&
      this.y - this.radius < paddle.y + paddle.height
    ) {
      // ボールがパドルに衝突したら、上方向へ反射
      this.y = paddle.y - this.radius;
      this.dy *= -1;
      
      // 衝突位置に応じて反射角を調整（パドルの左側なら左寄りに、右側なら右寄りに）
      const hitPos = (this.x - paddle.x) / paddle.width;
      const angle = lerp(-PI / 4, -3 * PI / 4, hitPos);
      const speed = sqrt(this.dx * this.dx + this.dy * this.dy);
      this.dx = speed * cos(angle);
      this.dy = speed * sin(angle);
    }
  }
  
  // ブロックとの衝突判定
  checkBlockCollision() {
    // 各ブロックとの衝突判定を行う
    for (let i = blocks.length - 1; i >= 0; i--) {
      const block = blocks[i];
      if (circleRectCollision(this.x, this.y, this.radius, block.x, block.y, block.w, block.h)) {
        // 衝突時の処理
        block.durability--;
        score += 10;
        // 衝突判定のため、ボールの進行方向を単純に反転（ここでは垂直方向の反射）
        this.dy *= -1;
        
        // ブロックが壊れた場合は配列から削除、耐久度が残る場合は色変更（エフェクト）
        if (block.durability <= 0) {
          // ※ここでパーティクルなどのエフェクトを追加することも可能
          blocks.splice(i, 1);
        } else {
          block.updateColor();
        }
        // 1フレームで複数のブロックに同時に衝突しないよう、1件処理したらループを抜ける
        break;
      }
    }
  }
  
  show() {
    noStroke();
    fill(255, 255, 0);
    ellipse(this.x, this.y, this.radius * 2);
  }
}

// ブロッククラス
class Block {
  constructor(x, y, w, h, durability) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.durability = durability;
    this.setColor();
  }
  
  // 耐久度に応じた色を設定
  setColor() {
    if (this.durability >= 2) {
      this.color = color(255, 0, 0); // 耐久度2以上：赤
    } else {
      this.color = color(0, 255, 0); // 耐久度1：緑
    }
  }
  
  // 衝突を受けた際、色を薄くするなどのエフェクト（例）
  updateColor() {
    const r = red(this.color);
    const g = green(this.color);
    const b = blue(this.color);
    this.color = color(min(r + 50, 255), min(g + 50, 255), b);
  }
  
  show() {
    stroke(0);
    fill(this.color);
    rect(this.x, this.y, this.w, this.h);
  }
}

// 円と矩形の衝突判定
// ※この関数は、円の中心と矩形の各辺との距離を計算し、
// 円の半径内に矩形が含まれているかをチェックする
function circleRectCollision(cx, cy, radius, rx, ry, rw, rh) {
  const closestX = constrain(cx, rx, rx + rw);
  const closestY = constrain(cy, ry, ry + rh);
  const distanceX = cx - closestX;
  const distanceY = cy - closestY;
  const distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
  return distanceSquared < (radius * radius);
}

// キー入力によるゲーム開始／再スタートの処理
function keyPressed() {
  if (gameState === "start" && key === ' ') {
    gameState = "playing";
  } else if ((gameState === "gameover" || gameState === "gameclear") && key === ' ') {
    // ゲームリセット
    score = 0;
    lives = 3;
    createBlocks();
    ball.reset();
    gameState = "playing";
  }
}

// スタート画面の描画
function drawStartScreen() {
  fill(255);
  textSize(32);
  text("Block Breaker Game", width / 2, height / 2 - 40);
  textSize(20);
  text("Press SPACE to Start", width / 2, height / 2);
}

// ゲームオーバー画面の描画
function drawGameOverScreen() {
  fill(255, 0, 0);
  textSize(32);
  text("Game Over", width / 2, height / 2 - 40);
  textSize(20);
  text("Press SPACE to Restart", width / 2, height / 2);
}

// ゲームクリア画面の描画
function drawGameClearScreen() {
  fill(0, 255, 0);
  textSize(32);
  text("You Win!", width / 2, height / 2 - 40);
  textSize(20);
  text("Press SPACE to Restart", width / 2, height / 2);
}

// ウィンドウサイズ変更時の処理
function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // パドルの位置をウィンドウ下部に再配置
  paddle.y = height - paddle.height - 20;
  // ※必要に応じてブロックの再配置処理を追加することも可能
}
```

## 動作確認

- このコードは、[p5.js エディタ](https://editor.p5js.org/) にコピー＆ペーストするだけで動作確認ができます。
- エディタ上で実行すると、マウス操作によりパドルを移動でき、ボールとブロックとの衝突判定やスコア加算、ライフ管理などが正常に動作することを確認できます。

## まとめ

今回の実例では、ChatGPT o3-mini-high の高いコード生成能力により、詳細なプロンプトに基づいた **p5.js** のブロック崩しゲームのコードを、一発ノーミスで生成できたことを紹介しました。

- 複雑な衝突判定ロジック、パドル、ボール、ブロックといった各コンポーネントの実装が一貫しており、
- コードの可読性や拡張性にも十分配慮された高品質な実装となっています。

ぜひ、[p5.js エディタ](https://editor.p5js.org/) を活用して、オリジナルのゲーム作成やプロトタイピングに挑戦してみてください.