ブロック崩し

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Breakout Game</title>
  <style>
    /* 画面中央にキャンバスを表示 */
    canvas {
      background: #eee;
      display: block;
      margin: 30px auto;
      border: 1px solid #333;
    }
  </style>
</head>
<body>
  <canvas id="myCanvas" width="480" height="320"></canvas>
  <script>
    // キャンバスとコンテキストを取得
    const canvas = document.getElementById("myCanvas");
    const ctx = canvas.getContext("2d");

    // ボールの設定
    let x = canvas.width / 2;     // ボールの初期位置 X
    let y = canvas.height - 30;   // ボールの初期位置 Y
    const ballRadius = 8;        // ボールの半径
    let dx = 2;                  // ボールの移動速度 X方向
    let dy = -2;                 // ボールの移動速度 Y方向

    // パドルの設定
    const paddleHeight = 10;
    const paddleWidth = 75;
    let paddleX = (canvas.width - paddleWidth) / 2; // パドルの初期位置
    let rightPressed = false;    // 右キーが押されているか
    let leftPressed = false;     // 左キーが押されているか

    // ブロック(レンガ)の設定
    const brickRowCount = 5;     // 行数
    const brickColumnCount = 7;  // 列数
    const brickWidth = 50;       // 幅
    const brickHeight = 20;      // 高さ
    const brickPadding = 10;     // ブロック間の余白
    const brickOffsetTop = 30;   // 画面上端からのオフセット
    const brickOffsetLeft = 30;  // 画面左端からのオフセット

    // スコアやライフ
    let score = 0;
    let lives = 3; // 残機

    // ブロックを格納する2次元配列
    let bricks = [];
    for(let c = 0; c < brickColumnCount; c++) {
      bricks[c] = [];
      for(let r = 0; r < brickRowCount; r++) {
        // x, yは後で計算するので、とりあえずステータスだけ持たせておく
        bricks[c][r] = { x: 0, y: 0, status: 1 }; 
      }
    }

    // キーボードイベントのリスナー登録
    document.addEventListener("keydown", keyDownHandler, false);
    document.addEventListener("keyup", keyUpHandler, false);

    function keyDownHandler(e) {
      if(e.key === "Right" || e.key === "ArrowRight") {
        rightPressed = true;
      }
      else if(e.key === "Left" || e.key === "ArrowLeft") {
        leftPressed = true;
      }
    }

    function keyUpHandler(e) {
      if(e.key === "Right" || e.key === "ArrowRight") {
        rightPressed = false;
      }
      else if(e.key === "Left" || e.key === "ArrowLeft") {
        leftPressed = false;
      }
    }

    // ブロックとボールの当たり判定
    function collisionDetection() {
      for(let c = 0; c < brickColumnCount; c++) {
        for(let r = 0; r < brickRowCount; r++) {
          let b = bricks[c][r];
          // status=1 のブロックだけ当たり判定をする
          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, CONGRATS!");
                document.location.reload(); // ページをリロードして再開
              }
            }
          }
        }
      }
    }

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

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

    // ブロックを描画
    function drawBricks() {
      for(let c = 0; c < brickColumnCount; c++) {
        for(let r = 0; r < brickRowCount; r++) {
          if(bricks[c][r].status === 1) {
            let brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
            let 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 = "#6CBE47";
            ctx.fill();
            ctx.closePath();
          }
        }
      }
    }

    // スコアを描画
    function drawScore() {
      ctx.font = "16px Arial";
      ctx.fillStyle = "#333";
      ctx.fillText("Score: " + score, 8, 20);
    }

    // ライフを描画
    function drawLives() {
      ctx.font = "16px Arial";
      ctx.fillStyle = "#333";
      ctx.fillText("Lives: " + lives, canvas.width - 65, 20);
    }

    // 毎フレーム呼び出して描画&更新するメイン関数
    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // 各パーツの描画
      drawBricks();
      drawBall();
      drawPaddle();
      drawScore();
      drawLives();
      collisionDetection();

      // ボールを左右の壁で反射
      if(x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
        dx = -dx;
      }
      // 上壁で反射
      if(y + dy < ballRadius) {
        dy = -dy;
      }
      // 下に落ちたらライフを1減らしてリセット
      else if(y + dy > canvas.height - ballRadius) {
        // パドルの範囲内かどうか
        if(x > paddleX && x < paddleX + paddleWidth) {
          // パドルに当たったらはね返す
          dy = -dy;
        } else {
          // ミス → ライフ減少
          lives--;
          if(!lives) {
            // ライフ0 → ゲームオーバー
            alert("GAME OVER");
            document.location.reload();
          } else {
            // ボールとパドルを初期位置へ
            x = canvas.width / 2;
            y = canvas.height - 30;
            dx = 2;
            dy = -2;
            paddleX = (canvas.width - paddleWidth) / 2;
          }
        }
      }

      // ボール位置の更新
      x += dx;
      y += dy;

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

      requestAnimationFrame(draw);
    }

    // ゲーム開始
    draw();
  </script>
</body>
</html>