Zadanie JS Pong

Gra Pong
class Ball {
  constructor(x, y, radius, speedX, speedY, color) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.speedX = speedX;
    this.speedY = speedY;
    this.color = color;
  }

  draw(ctx) {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();
  }

  move() {
    this.x += this.speedX;
    this.y += this.speedY;
  }

  reset(canvas) {
    this.x = canvas.width / 2 + (Math.random() - 0.5) * (canvas.width / 4);
    this.y = canvas.height / 3;
    const baseSpeed = canvas.width / 200;
    this.speedX = baseSpeed * (Math.random() > 0.5 ? 1 : -1);
    this.speedY = baseSpeed * (Math.random() > 0.5 ? 1 : -1);
  }
}

class Paddle {
  constructor(width, height, x, y, color) {
    this.width = width;
    this.height = height;
    this.x = x;
    this.y = y;
    this.color = color;
  }

  draw(ctx) {
    ctx.beginPath();
    ctx.rect(this.x, this.y, this.width, this.height);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.closePath();
  }
}

class Game {
  constructor(canvasId) {
    this.canvas = document.getElementById(canvasId);
    this.ctx = this.canvas.getContext("2d");
    this.scoreEl = document.getElementById("score");
    this.livesEl = document.getElementById("lives");

    this.score = 0;
    this.lives = 5;
    this.gameOver = false;

    this.canvas.width = 400;
    this.canvas.height = 600;

    this.balls = [
      new Ball(this.canvas.width / 2, this.canvas.height / 2, 10, 2, -2, "#ff4757"),
      new Ball(this.canvas.width / 3, this.canvas.height / 3, 10, -2, 2, "#feca57"),
    ];

    this.paddle = new Paddle(90, 14, (this.canvas.width - 90) / 2, this.canvas.height - 20, "#000000");

    this.canvas.addEventListener("mousemove", this.movePaddle.bind(this));
    this.canvas.addEventListener("touchmove", this.movePaddle.bind(this));
    window.addEventListener("resize", this.resize.bind(this));
    this.resize();
  }

  resize() {
    const oldWidth = this.canvas.width;
    const oldHeight = this.canvas.height;

    this.canvas.width = this.canvas.clientWidth;
    this.canvas.height = this.canvas.width * 1.5;

    if (oldWidth === 0) return;

    const scaleX = this.canvas.width / oldWidth;
    const scaleY = this.canvas.height / oldHeight;

    this.paddle.x *= scaleX;
    this.paddle.width *= scaleX;
    this.paddle.y *= scaleY;
    this.paddle.height *= scaleY;

    this.balls.forEach((ball) => {
      ball.x *= scaleX;
      ball.y *= scaleY;
      ball.radius *= scaleX;
      ball.speedX *= scaleX;
      ball.speedY *= scaleY;
    });
  }

  movePaddle(e) {
    const rect = this.canvas.getBoundingClientRect();
    let root = document.documentElement;
    let newX = e.clientX - rect.left - root.scrollLeft;

    if (e.type === "touchmove") {
      newX = e.touches[0].clientX - rect.left - root.scrollLeft;
    }

    this.paddle.x = newX - this.paddle.width / 2;

    if (this.paddle.x < 0) {
      this.paddle.x = 0;
    }
    if (this.paddle.x + this.paddle.width > this.canvas.width) {
      this.paddle.x = this.canvas.width - this.paddle.width;
    }
  }

  checkCollisions() {
    this.balls.forEach((ball) => {
      // Ściany boczne
      if (ball.x + ball.radius > this.canvas.width || ball.x - ball.radius < 0) {
        ball.speedX = -ball.speedX;
      }

      // Sufit
      if (ball.y - ball.radius < 0) {
        ball.speedY = -ball.speedY;
      }

      // Odbicie od paletki
      if (ball.y + ball.radius > this.paddle.y && ball.x > this.paddle.x && ball.x < this.paddle.x + this.paddle.width) {
        let intersect = (ball.x - (this.paddle.x + this.paddle.width / 2)) / (this.paddle.width / 2);
        let bounceAngle = intersect * (Math.PI / 3);
        let speed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY) + this.canvas.width / 8000 + this.score / 500;

        ball.speedX = speed * Math.sin(bounceAngle);
        ball.speedY = -speed * Math.cos(bounceAngle);

        this.score++;
        this.scoreEl.textContent = this.score;
      }

      // Spadnięcie piłki
      if (ball.y + ball.radius > this.canvas.height) {
        this.lives--;
        this.livesEl.textContent = this.lives;
        ball.reset(this.canvas);
        if (this.lives === 0) {
          this.gameOver = true;
          setTimeout(() => {
            this.resetGame();
          }, 3000);
        }
      }
    });
  }

  resetGame() {
    this.score = 0;
    this.lives = 5;
    this.scoreEl.textContent = this.score;
    this.livesEl.textContent = this.lives;
    this.balls.forEach((ball) => ball.reset(this.canvas));
    this.gameOver = false;
  }

  update() {
    if (this.gameOver) return;
    this.balls.forEach((ball) => ball.move());
    this.checkCollisions();
  }

  draw() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    if (this.gameOver) {
      this.ctx.fillStyle = "black";
      this.ctx.font = "bold 40px Arial";
      this.ctx.textAlign = "center";
      this.ctx.fillText("Koniec gry!", this.canvas.width / 2, this.canvas.height / 2 - 40);
      this.ctx.font = "24px Arial";
      this.ctx.fillText("Wynik: " + this.score, this.canvas.width / 2, this.canvas.height / 2);
    } else {
      this.balls.forEach((ball) => ball.draw(this.ctx));
      this.paddle.draw(this.ctx);
    }
  }

  gameLoop() {
    this.update();
    this.draw();
    requestAnimationFrame(this.gameLoop.bind(this));
  }

  start() {
    this.gameLoop();
  }
}

const game = new Game("gameCanvas");
game.start();
* {
    box-sizing: border-box;
}

body {
    font-family: 'Arial', sans-serif;
    background-color: #ffffff;
    color: #000000;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background: #12c2e9;
    background: -webkit-linear-gradient(to right, #12c2e9, #c471ed, #f64f59);
    background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59);
}

.game-container {
    background-color: #ffffff;
    border: none;
    border-radius: 10px;
    padding: 20px;
    text-align: center;
    width: 90%;
    max-width: 400px;
}

.game-title {
    font-size: 3.5rem;
    font-weight: bold;
    color: #000000;
    margin-bottom: 10px;
}

.game-ui {
    display: flex;
    justify-content: space-between;
    font-size: 1.2rem;
    margin-bottom: 10px;
}

#gameCanvas {
    background-color: #f0f0f0;
    border-radius: 6px;
    border: 6px solid #000000;
    width: 100%;
}

.game-instructions {
    margin-top: 10px;
    font-size: 0.9rem;
    color: #333333;
}
<!DOCTYPE html>
<html lang="pl">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Pong</title>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <div class="game-container">
      <div class="game-title">Pong</div>
      <div class="game-ui">
        <div class="score">Wynik: <span id="score">0</span></div>
        <div class="lives">Życia: <span id="lives">5</span></div>
      </div>
      <canvas id="gameCanvas"></canvas>
      <div class="game-instructions">Kieruj rakietką za pomocą myszki lub dotyku ekranu</div>
    </div>
    <script src="js/script.js"></script>
  </body>
</html>