<!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>