Multipong JS

Multipong JS
<!DOCTYPE html>

<html lang="pl">



<head>

  <meta charset="utf-8" />

  <meta name="viewport" content="width=device-width,initial-scale=1" />

  <title>Pong - poprawiona wersja</title>

  <style>

    body {

      margin: 0;

      padding: 20px;

      background: #1e3c72;

      font-family: Arial, sans-serif;

      color: #fff;

      display: flex;

      flex-direction: column;

      align-items: center;

      min-height: 100vh;

    }



    h1 {

      margin-bottom: 10px;

    }



    #gameContainer {

      position: relative;

      border: 3px solid #fff;

      border-radius: 10px;

      background: #000;

    }



    canvas {

      display: block;

      border-radius: 7px;

    }



    #gameInfo {

      display: flex;

      justify-content: space-between;

      width: 600px;

      margin: 20px 0;

      font-size: 18px;

      font-weight: bold;

    }



    .game-over {

      position: absolute;

      top: 30%;

      left: 50%;

      transform: translate(-50%, -50%);

      color: white;

      padding: 20px;

      text-align: center;

      font-size: 24px;

      font-weight: bold;

      background: rgba(0, 0, 0, 0.6);

      border-radius: 8px;

    }



    .restart-btn {

      margin-top: 15px;

      padding: 10px 20px;

      font-size: 16px;

      background: #fff;

      color: #000;

      cursor: pointer;

      border-radius: 6px;

    }

  </style>

</head>



<body>

  <h1>Pong</h1>

  <div id="gameInfo">

    <div>Najlepszy wynik: <span id="bestScore"></span></div>

    <div>Wynik: <span id="score">0</span></div>

    <div>Życia: <span id="lives">9</span></div>

    <div>Piłek na planszy: <span id="ballCounter">1</span></div>

  </div>

  <div id="gameContainer">

    <canvas id="gameCanvas" width="400" height="600"></canvas>

    <div id="gameOver" class="game-over" style="display:none;">

      <div>Koniec Gry!</div>

      <div>Twój wynik: <span id="finalScore">0</span></div>

      <button class="restart-btn" onclick="restartGame()">Zagraj ponownie</button>

    </div>

  </div>

  <div id="instructions" style="margin-top:20px; font-size:14px; text-align:center;">

    Kieruj rakietką za pomocą myszki lub dotyku ekranu.

  </div>

  <script>

    let lastBallScore = 0;                                                //element Multipong: śledzenie ostatniego punktu, z którego dodano piłkę

    // uchwyty do DOM

    const canvas = document.getElementById('gameCanvas');

    const ctx = canvas.getContext('2d');

    const scoreElement = document.getElementById('score');

    const livesElement = document.getElementById('lives');

    const ballCounterElement = document.getElementById('ballCounter')

    const gameOverElement = document.getElementById('gameOver');

    const finalScoreElement = document.getElementById('finalScore');

    const bestScoreElement = document.getElementById('bestScore');

    // stan gry

    let gameRunning = true;

    let score = 0;

    let lives = 9;

    let ballCounter = 1;

    let bestScore = localStorage.getItem('bestScore') || 0;

    bestScoreElement.textContent = bestScore;

    // klasa piłki

    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();

      }

      update(canvas, paddle, scoreEl, livesEl, finalScoreEl, gameOverEl, index) {

        this.x += this.speedX;

        this.y += this.speedY;

        // odbicie od boków

        if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {

          this.speedX = -this.speedX;

        }

        // odbicie od góry

        if (this.y - this.radius < 0) {

          this.speedY = -this.speedY;

        }

        // kolizja z paletką (tylko gdy piłka idzie w dół)

        if (this.y + this.radius > paddle.y &&

          this.x > paddle.x &&

          this.x < paddle.x + paddle.width &&

          this.speedY > 0) {

          this.speedY = -Math.abs(this.speedY); // odbij w górę

          score += 50;

          scoreEl.textContent = score;



          if (score % 100 === 0) {                                                         //element Multipong: warunek 100 pkt

            this.speedX *= 1.1;

            this.speedY *= 1.1;

            balls.push(new Ball(canvas.width / 2, canvas.height / 2, 8, 3, -4, colorHelper.getRandomDistinctColor()));

            lastBallScore = score;                                                  //element Multipong: śledzenie ostatniego punktu, z którego dodano piłkę

          }

        }

        // piłka spadła za dół

        if (this.y - this.radius > canvas.height) {

          lives--;

          livesEl.textContent = lives;

          balls.splice(index, 1);                                                          //element Multipong: usuń piłkę





          if (lives <= 0) {

            gameRunning = false;

            finalScoreEl.textContent = score;

            gameOverEl.style.display = 'block';

          }

          // reset pozycji piłki                                                        //element Multipong: resetowanie piłki w gameLoop

          if (score > bestScore) {

            bestScore = score;

            bestScoreElement.textContent = bestScore;

          }

        }

      }

    }

    // koniec klasy piłka

    // helper - losowy kolor HEX

    class ColorHelper {                                                                      //upgrade: uniknij wygenereowania piłki w kolorze tła

      constructor(backgroundHex) {

        this.backgroundRgb = this.hexToRgb(backgroundHex);

      }



      hexToRgb(hex) {

        const bigint = parseInt(hex.slice(1), 16);

        return {

          r: (bigint >> 16) & 255,

          g: (bigint >> 8) & 255,

          b: bigint & 255

        };

      }



      luminance(c) {

        const a = [c.r, c.g, c.b].map(v => {

          v /= 255;

          return v <= 0.03928

            ? v / 12.92

            : Math.pow((v + 0.055) / 1.055, 2.4);

        });

        return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];

      }



      contrast(rgb1, rgb2) {

        const lum1 = this.luminance(rgb1);

        const lum2 = this.luminance(rgb2);

        const brightest = Math.max(lum1, lum2);

        const darkest = Math.min(lum1, lum2);

        return (brightest + 0.05) / (darkest + 0.05);

      }



      getRandomColor() {

        const hex = Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');

        return '#' + hex;

      }



      getRandomDistinctColor(minContrast = 3) {

        let color, rgb;

        do {

          color = this.getRandomColor();

          rgb = this.hexToRgb(color);

        } while (this.contrast(rgb, this.backgroundRgb) < minContrast);

        return color;

      }

    }

    const colorHelper = new ColorHelper('#16213e');                                       //stały kolor tła



    // paletka

    const paddle = {

      x: canvas.width / 2 - 50,

      y: canvas.height - 30,

      width: 100,

      height: 15,

      color: '#4ecdc4'

    };

    function constrainPaddle() {

      if (paddle.x < 0) paddle.x = 0;

      if (paddle.x > canvas.width - paddle.width) paddle.x = canvas.width - paddle.width;

    }

    // obsługa dotyku / myszy

    let touchX = null;

    canvas.addEventListener("touchstart", function (e) {

      e.preventDefault();

      const rect = canvas.getBoundingClientRect();

      touchX = e.touches[0].clientX - rect.left;

      paddle.x = touchX - paddle.width / 2;

      constrainPaddle();

    }, { passive: false });

    canvas.addEventListener("touchmove", function (e) {

      e.preventDefault();

      const rect = canvas.getBoundingClientRect();

      touchX = e.touches[0].clientX - rect.left;

      paddle.x = touchX - paddle.width / 2;

      constrainPaddle();

    }, { passive: false });

    canvas.addEventListener("mousemove", function (e) {

      const rect = canvas.getBoundingClientRect();

      const mouseX = e.clientX - rect.left;

      paddle.x = mouseX - paddle.width / 2;

      constrainPaddle();

    });

    function drawPaddle() {

      ctx.fillStyle = paddle.color;

      ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);

    }

    // **TU**: tworzymy instancję piłki — to klucz!

    let balls = [];                                                                 //element Multipong: inicjacja zmiennej tablicowej wielu piłek zamiast var ball = new Ball(...);

    balls.push(new Ball(canvas.width / 2, canvas.height / 2, 8, 3, -4, colorHelper.getRandomDistinctColor())); //element Multipong: nowa instancja klasy Ball i dodanie jej do tablicy





    function gameLoop() {



      if (!gameRunning) return;

      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // tło gradientowe

      const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);

      gradient.addColorStop(0, '#1a1a2e');

      gradient.addColorStop(1, '#16213e');

      ctx.fillStyle = gradient;

      ctx.fillRect(0, 0, canvas.width, canvas.height);

      // aktualizuj i rysuj



      balls.forEach((ball, index) => {                        //element Multipong: iteruj po wielu (parametr index jest niezbędny do usuwania piłek!)

        ball.update(canvas, paddle, scoreElement, livesElement, finalScoreElement, gameOverElement, index);

        ball.draw(ctx);

      });

      if (balls.length === 0 && lives > 0) {               //element Multipong: usunięto resetowanie piłki z metody Update

        balls.push(new Ball(

          canvas.width / 2,

          canvas.height / 2, 8,

          Math.random() > 0.5 ? 3 : -3, -4,

          colorHelper.getRandomDistinctColor()

        ))

      }

      drawPaddle();

      ballCounterElement.textContent = balls.length;

      requestAnimationFrame(gameLoop);

    }

    function restartGame() {

      gameRunning = true;

      score = 0;

      lives = 9;

      // reset UI

      scoreElement.textContent = score;

      livesElement.textContent = lives;

      gameOverElement.style.display = 'none';

      // reset stanu piłki i paletki

      /* ball.x = canvas.width / 2;                                                                 //zakomentowany kod Ponga

      ball.y = canvas.height / 2;

      ball.speedX = 3;

      ball.speedY = -4;

      ball.color = '#ff6b6b'; */

      paddle.x = canvas.width / 2 - paddle.width / 2;



      balls = [];                                                                                   //element Multipong

      balls.push(new Ball(canvas.width / 2, canvas.height / 2, 8, 3, -4, colorHelper.getRandomDistinctColor()));               //element Multipong



      // wystartuj pętlę

      gameLoop();

    }

    // start gry

    gameLoop();

  </script>

</body>



</html>