github copilot chatが使えるようになったのでコード比較してみた

# はじめに
github copilot chatが使えるようになったのでこれを記念して
chatGPT、Bardと同じお題でゲーム作らせて仕上がり具合を比較したのでそのときの様子をメモ。

# お題

```
webブラウザで遊べるインベーダーゲームプログラムを書いてください。
```
でお願いしました。

# Bard
## コード
キャラはどこかしらから画像URLを参照しようとしてたけど、存在しないURLだったため
急遽ここだけ手作業で画像を用意。

```javascript:game_bard.html
<!DOCTYPE html>
<html>
<head>
<title>インベーダーゲーム</title>
<style>
body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

canvas {
  width: 100%;
  height: 100%;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>

<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

// インベーダーの画像
var invaderImage = new Image();
invaderImage.src = "enemy.png";

// 自機の画像
var playerImage = new Image();
playerImage.src = "player.png";

// インベーダーの初期位置
var invaderPositions = [
  [100, 100],
  [200, 100],
  [300, 100],
  [400, 100],
  [500, 100],
];

// 自機の初期位置
var playerPosition = [
  400,
  400,
];

// インベーダーの移動速度
var invaderSpeed = 5;

// 自機の移動速度
var playerSpeed = 10;

// ゲームの状態
var gameState = "playing";

// インベーダーを描画する関数
function drawInvaders() {
  for (var i = 0; i < invaderPositions.length; i++) {
    ctx.drawImage(invaderImage, invaderPositions[i][0], invaderPositions[i][1]);
  }
}

// 自機を描画する関数
function drawPlayer() {
  ctx.drawImage(playerImage, playerPosition[0], playerPosition[1]);
}

// キーボードイベントを処理する関数
function handleKeyDown(event) {
  switch (event.keyCode) {
    case 37: // 左矢印キー
      playerPosition[0] -= playerSpeed;
      break;
    case 38: // 上矢印キー
      playerPosition[1] -= playerSpeed;
      break;
    case 39: // 右矢印キー
      playerPosition[0] += playerSpeed;
      break;
    case 40: // 下矢印キー
      playerPosition[1] += playerSpeed;
      break;
  }
}

// タイマーイベントを処理する関数
function handleTimer() {
  // インベーダーを移動させる
  for (var i = 0; i < invaderPositions.length; i++) {
    invaderPositions[i][0] += invaderSpeed;
  }

  // インベーダーが画面の端に到達したら、反転させる
  for (var i = 0; i < invaderPositions.length; i++) {
    if (invaderPositions[i][0] > canvas.width) {
      invaderPositions[i][0] = -invaderImage.width;
      invaderPositions[i][1] += 100;
    }
  }

  // インベーダーと自機が衝突したら、ゲームオーバーにする
  for (var i = 0; i < invaderPositions.length; i++) {
    if (invaderPositions[i][0] < playerPosition[0] + playerImage.width &&
        invaderPositions[i][0] > playerPosition[0] - playerImage.width &&
        invaderPositions[i][1] < playerPosition[1] + playerImage.height &&
        invaderPositions[i][1] > playerPosition[1] - playerImage.height) {
      gameState = "gameover";
    }
 
}

// 自機からビームを発射する
if (event.keyCode === 32) {
  var beam = {
    x: playerPosition[0] + playerImage.width / 2,
    y: playerPosition[1] - playerImage.height,
  };
  beams.push(beam);
}

// ビームを移動させる
for (var i = 0; i < beams.length; i++) {
  beams[i].y -= 10;
}

// ビームが画面外に出たら、削除する
for (var i = 0; i < beams.length; i++) {
  if (beams[i].y < 0) {
    beams.splice(i, 1);
  }
}

// ビームとインベーダーが衝突したら、インベーダーを削除する
for (var i = 0; i < beams.length; i++) {
  for (var j = 0; j < invaderPositions.length; j++) {
    if (beams[i].x < invaderPositions[j][0] + invaderImage.width &&
        beams[i].x > invaderPositions[j][0] - invaderImage.width &&
        beams[i].y < invaderPositions[j][1] + invaderImage.height &&
        beams[i].y > invaderPositions[j][1] - invaderImage.height) {
      beams.splice(i, 1);
      invaderPositions.splice(j, 1);
      break;
    }
  }
}

// インベーダーがすべて消えたら、ゲームクリアにする
if (invaderPositions.length === 0) {
  gameState = "clear";
}

// 画面をクリアする
ctx.clearRect(0, 0, canvas.width, canvas.height);

// インベーダーを描画する
drawInvaders();

// 自機を描画する
drawPlayer();

// ビームを描画する
for (var i = 0; i < beams.length; i++) {
  ctx.drawImage(beamImage, beams[i].x, beams[i].y);
}
}

// タイマーを開始する
var timer = setInterval(handleTimer, 1000);

// キーボードイベントを登録する
document.addEventListener("keydown", handleKeyDown);

// ゲームを開始する
gameState = "playing";
</script>
</body>
</html>
```
## 結果
真っ白な画面、、、開発ツール起動させてみるとめっちゃエラー出てた。残念。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416263/3dc6da16-9f3d-383d-fcee-df598b5bbbaf.png)




# chatGPT
こちらはスタイルシートを使ってキャラを作ってくれてるようなんだけど
長文過ぎたので２回に分けて出力するという事態に。大変だった…。

```javascript:game_gpt.html
<!DOCTYPE html>
<html>
<head>
  <title>スペースインベーダーゲーム</title>
  <style>
    #game-container {
      position: relative;
      width: 400px;
      height: 400px;
      margin: 0 auto;
      border: 1px solid #000;
    }
    .player {
      position: absolute;
      bottom: 0;
      left: 50%;
      transform: translateX(-50%);
      width: 40px;
      height: 40px;
      background-color: #000;
    }
    .bullet {
      position: absolute;
      width: 2px;
      height: 10px;
      background-color: #f00;
    }
    .invader {
      position: absolute;
      width: 40px;
      height: 40px;
      background-color: #00f;
    }
  </style>
</head>
<body>
  <h1>スペースインベーダーゲーム</h1>
  <div id="game-container">
    <div class="player"></div>
  </div>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const player = document.querySelector('.player');
      const gameContainer = document.querySelector('#game-container');
      let invaders = [];

      function movePlayer(event) {
        const key = event.key;
        if (key === 'ArrowLeft' && parseInt(player.style.left) > 0) {
          player.style.left = parseInt(player.style.left) - 10 + 'px';
        } else if (key === 'ArrowRight' && parseInt(player.style.left) < gameContainer.offsetWidth - player.offsetWidth) {
          player.style.left = parseInt(player.style.left) + 10 + 'px';
        } else if (key === ' ') {
          fireBullet();
        }
      }

      function fireBullet() {
        const bullet = document.createElement('div');
        bullet.classList.add('bullet');
        bullet.style.left = parseInt(player.style.left) + player.offsetWidth / 2 - bullet.offsetWidth / 2 + 'px';
        bullet.style.bottom = player.offsetHeight + 'px';
        gameContainer.appendChild(bullet);

        const bulletMoveInterval = setInterval(() => {
          const bulletTop = parseInt(bullet.style.bottom);
          if (bulletTop > gameContainer.offsetHeight) {
            clearInterval(bulletMoveInterval);
            bullet.remove();
          } else {
            bullet.style.bottom = bulletTop + 10 + 'px';
            checkCollision(bullet);
          }
        }, 16);
      }

      function createInvader() {
        const invader = document.createElement('div');
        invader.classList.add('invader');
        invader.style.left = Math.random() * (gameContainer.offsetWidth - invader.offsetWidth) + 'px';
        invader.style.top = '0px';
        gameContainer.appendChild(invader);
        invaders.push(invader);
      }

      function moveInvaders() {
        invaders.forEach((invader) => {
          const invaderTop = parseInt(invader.style.top);
          if (invaderTop > gameContainer.offsetHeight - invader.offsetHeight) {
            gameOver();
          } else {
            invader.style.top = invaderTop + 10 + 'px';
            checkCollision(invader);
          }
        });
      }

      function checkCollision(element) {
        const elementRect = element.getBoundingClientRect();
        invaders.forEach((invader) => {
          const invaderRect = invader.getBoundingClientRect();

          if (elementRect.top <= invaderRect.bottom &&
              elementRect.bottom >= invaderRect.top &&
              elementRect.left <= invaderRect.right &&
              elementRect.right >= invaderRect.left) {
            // Collision detected
            element.remove();
            invader.remove();
            invaders = invaders.filter((item) => item !== invader);
          }
        });
      }

      function gameOver() {
        clearInterval(invaderMoveInterval);
        alert('ゲームオーバー');
        location.reload();
      }

      document.addEventListener('keydown', movePlayer);

      const invaderMoveInterval = setInterval(() => {
        createInvader();
        moveInvaders();
      }, 1000);
    });
  </script>
</body>
</html>
```
## 結果
エラーなく、画面が表示されたので成功した？！って思ったけど
実際は画面表示されただけで何も反応しない、悲しい。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416263/dff314c2-d91d-d7a2-1965-1ea76b0e9d56.png)



# Github Copilot Chat
本命のcopilot chat。
こちらはコード出力で途中やめってことはなく一撃で出力してくれた。
キャラはchatGPT同じくcssで代用。ありがたい。
```javascript:game_copilot.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Invaders</title>
    <style>
      canvas {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="400" height="400"></canvas>
    <script>
      const canvas = document.getElementById('canvas');
      const ctx = canvas.getContext('2d');

      // Player
      const playerWidth = 20;
      const playerHeight = 20;
      let playerX = canvas.width / 2 - playerWidth / 2;
      const playerY = canvas.height - playerHeight;
      const playerSpeed = 5;

      // Bullets
      const bulletWidth = 5;
      const bulletHeight = 10;
      const bulletSpeed = 10;
      let bullets = [];

      // Enemies
      const enemyRows = 5;
      const enemyCols = 10;
      const enemyWidth = 20;
      const enemyHeight = 20;
      const enemyPadding = 10;
      const enemyOffsetTop = 30;
      const enemyOffsetLeft = 30;
      const enemySpeed = 2;
      let enemies = [];
      for (let c = 0; c < enemyCols; c++) {
        enemies[c] = [];
        for (let r = 0; r < enemyRows; r++) {
          enemies[c][r] = { x: 0, y: 0, alive: true };
        }
      }

      // Game loop
      function gameLoop() {
        // Clear canvas
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // Draw player
        ctx.fillStyle = 'blue';
        ctx.fillRect(playerX, playerY, playerWidth, playerHeight);

        // Move bullets
        bullets.forEach((bullet) => {
          bullet.y -= bulletSpeed;
          ctx.fillStyle = 'red';
          ctx.fillRect(bullet.x, bullet.y, bulletWidth, bulletHeight);
        });

        // Move enemies
        let enemyHitWall = false;
        enemies.forEach((col) => {
          col.forEach((enemy) => {
            if (enemy.alive) {
              enemy.x += enemySpeed;
              if (enemy.x + enemyWidth > canvas.width || enemy.x < 0) {
                enemyHitWall = true;
              }
              ctx.fillStyle = 'green';
              ctx.fillRect(enemy.x, enemy.y, enemyWidth, enemyHeight);
            }
          });
        });

        // Reverse enemy direction if hit wall
        if (enemyHitWall) {
          enemySpeed *= -1;
          enemies.forEach((col) => {
            col.forEach((enemy) => {
              enemy.y += enemyHeight + enemyPadding;
            });
          });
        }

        // Check for bullet-enemy collisions
        bullets.forEach((bullet) => {
          enemies.forEach((col) => {
            col.forEach((enemy) => {
              if (enemy.alive && bullet.x < enemy.x + enemyWidth && bullet.x + bulletWidth > enemy.x && bullet.y < enemy.y + enemyHeight && bullet.y + bulletHeight > enemy.y) {
                enemy.alive = false;
                bullet.hit = true;
              }
            });
          });
        });

        // Remove dead enemies
        enemies.forEach((col) => {
          col.forEach((enemy) => {
            if (!enemy.alive) {
              col.splice(col.indexOf(enemy), 1);
            }
          });
        });

        // Remove hit bullets
        bullets = bullets.filter((bullet) => !bullet.hit);

        // Move player
        if (keys.left && playerX > 0) {
          playerX -= playerSpeed;
        }
        if (keys.right && playerX + playerWidth < canvas.width) {
          playerX += playerSpeed;
        }

        // Fire bullet
        if (keys.space) {
          bullets.push({ x: playerX + playerWidth / 2 - bulletWidth / 2, y: playerY - bulletHeight, hit: false });
          keys.space = false;
        }

        // Request next frame
        requestAnimationFrame(gameLoop);
      }

      // Keyboard input
      const keys = {};
      document.addEventListener('keydown', (event) => {
        if (event.code === 'ArrowLeft') {
          keys.left = true;
        }
        if (event.code === 'ArrowRight') {
          keys.right = true;
        }
        if (event.code === 'Space') {
          keys.space = true;
        }
      });
      document.addEventListener('keyup', (event) => {
        if (event.code === 'ArrowLeft') {
          keys.left = false;
        }
        if (event.code === 'ArrowRight') {
          keys.right = false;
        }
        if (event.code === 'Space') {
          keys.space = false;
        }
      });

      // Start game loop
      gameLoop();
    </script>
  </body>
</html>
```

## 結果
敵（緑）が動いてミサイル打てた！キャラ（青）も動く！
ただ、敵が右端に行ったらおしまい。あと、敵をミサイルで撃破しても終了。
でもゲームとして成立してるので十分OK！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416263/034c907e-6234-1de0-dc59-9b2efc612200.png)

# おわりに
そんなわけでゲームプログラムの仕上がり具合を比較。
copilot chatはプログラムに特化してるだけあってちゃんとしてる！
さすが、有料な分いい仕事をしてくれたって思いました。