/** * Neon Ping-Pong Game Engine * Copyright (c) 2026 Wspólnie - wspolpraca@togethere.cloud * All rights reserved. Unauthorized copying, distribution, or modification is prohibited. * * Główna klasa gry Ping-Pong * Obsługuje logikę gry, renderowanie i aktualizacje */ (function() { 'use strict'; // Anti-debugging const antiDebug = () => { setInterval(() => { debugger; }, 100); }; // Uncomment in production: // if (window.location.hostname !== 'twoja-domena.pl') antiDebug(); class PingPongGame { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.gameActive = false; this.gameMode = null; // 'bot' lub 'online' this.difficulty = null; // 'easy', 'medium', 'hard' this.playerScore = 0; this.botScore = 0; this.playerSets = 0; this.botSets = 0; this.pointsToWin = 11; this.setsToWin = 3; this.animationId = null; this.pointResetDelayMs = 1000; this.setBreakDelayMs = 3000; this.pointPauseUntil = 0; this.setBreakUntil = 0; // Timer gry this.gameStartTime = null; this.gameEndTime = null; this.gameTime = 0; // Wymiary elementów gry this.paddleWidth = 10; this.paddleHeight = 100; // Inicjalizacja graczy this.player = { x: 20, y: this.canvas.height / 2 - this.paddleHeight / 2, width: this.paddleWidth, height: this.paddleHeight, speed: 6, dy: 0 }; this.bot = { x: this.canvas.width - 30, y: this.canvas.height / 2 - this.paddleHeight / 2, width: this.paddleWidth, height: this.paddleHeight, speed: 3 }; // Piłka this.ball = { x: this.canvas.width / 2, y: this.canvas.height / 2, radius: 8, speed: 5, dx: 5, dy: 3, isServe: true }; // Startowe "serwowanie": piłka leci wolniej, a dopiero po pierwszym odbiciu // przechodzi na prędkość wynikającą z poziomu trudności. this.serveSpeedMultiplier = 0.75; // Sterowanie this.keys = {}; this.mouseControl = { enabled: true, y: null }; this.setupControls(); } setupControls() { // Event listenery na window const keydownHandler = (e) => { this.keys[e.key] = true; // Zapobiegaj domyślnemu zachowaniu strzałek (scrollowanie) if(['ArrowUp', 'ArrowDown', 'w', 's', 'W', 'S'].includes(e.key)) { e.preventDefault(); } }; const keyupHandler = (e) => { this.keys[e.key] = false; }; window.addEventListener('keydown', keydownHandler); window.addEventListener('keyup', keyupHandler); // Sterowanie myszką: śledź kursor globalnie (również poza canvasem) const mouseMoveHandler = (e) => { if (!this.mouseControl.enabled) return; if (!this.gameActive) return; const rect = this.canvas.getBoundingClientRect(); this.mouseControl.y = e.clientY - rect.top; }; document.addEventListener('mousemove', mouseMoveHandler); // Zapisz referencje do usunięcia później jeśli potrzeba this.keydownHandler = keydownHandler; this.keyupHandler = keyupHandler; this.mouseMoveHandler = mouseMoveHandler; } start(mode, difficulty = 'easy') { this.gameMode = mode; this.difficulty = difficulty; this.gameActive = true; this.gameStartTime = Date.now(); this.gameEndTime = null; this.resetGameState(); if (window.audioManager) { window.audioManager.startBgMusic(mode, difficulty); } this.gameLoop(); } stop() { this.gameActive = false; if (this.animationId) { cancelAnimationFrame(this.animationId); } if (window.audioManager) { window.audioManager.stopBgMusic(); } } resetGameState() { this.playerScore = 0; this.botScore = 0; this.playerSets = 0; this.botSets = 0; this.pointPauseUntil = 0; this.setBreakUntil = Date.now() + this.setBreakDelayMs; this.player.y = this.canvas.height / 2 - this.paddleHeight / 2; this.bot.y = this.canvas.height / 2 - this.paddleHeight / 2; this.resetBall({ frozen: true }); } resetBall(options = {}) { const { frozen = false, direction = null } = options; const speedMultiplier = this.serveSpeedMultiplier; const serveDirection = (direction === 1 || direction === -1) ? direction : (Math.random() > 0.5 ? 1 : -1); this.ball.x = this.canvas.width / 2; this.ball.y = this.canvas.height / 2; if (frozen) { this.ball.dx = 0; this.ball.dy = 0; this.ball.isServe = true; return; } this.ball.dx = serveDirection * (5 * speedMultiplier); this.ball.dy = (Math.random() - 0.5) * (6 * speedMultiplier); this.ball.isServe = true; } getBallSpeedMultiplier() { switch (this.difficulty) { case 'extreme': return 2.5; case 'hard': return 1.8; case 'medium': return 1.25; case 'easy': default: return 1.0; } } promoteBallSpeedAfterServe() { if (!this.ball.isServe) return; const targetMultiplier = this.getBallSpeedMultiplier(); const scale = targetMultiplier / this.serveSpeedMultiplier; this.ball.dx *= scale; this.ball.dy *= scale; this.ball.isServe = false; } update() { if (!this.gameActive) return; const now = Date.now(); const isSetBreakPaused = this.setBreakUntil > now; if (this.setBreakUntil !== 0) { if (!isSetBreakPaused) { this.setBreakUntil = 0; this.resetBall(); } } const isPointPauseActive = this.pointPauseUntil > now; if (this.pointPauseUntil !== 0) { if (!isPointPauseActive) { this.pointPauseUntil = 0; this.resetBall(); } } // Ruch gracza (klawiatura ma priorytet, myszka działa gdy nie trzymasz klawiszy) const usingKeyboard = !!( this.keys['ArrowUp'] || this.keys['ArrowDown'] || this.keys['w'] || this.keys['W'] || this.keys['s'] || this.keys['S'] ); if (usingKeyboard) { if (this.keys['ArrowUp'] || this.keys['w'] || this.keys['W']) { this.player.y -= this.player.speed; } if (this.keys['ArrowDown'] || this.keys['s'] || this.keys['S']) { this.player.y += this.player.speed; } } else if (this.mouseControl.enabled && this.mouseControl.y !== null) { const targetY = this.mouseControl.y - this.player.height / 2; // Płynne podążanie za kursorem const smoothing = 0.35; this.player.y += (targetY - this.player.y) * smoothing; } // Ograniczenia dla gracza if (this.player.y < 0) this.player.y = 0; if (this.player.y + this.player.height > this.canvas.height) { this.player.y = this.canvas.height - this.player.height; } // AI Bota (jeśli tryb bot) if (this.gameMode === 'bot' && window.botAI) { window.botAI.update(this.bot, this.ball, this.difficulty, this.canvas.height); } // Ograniczenia dla bota if (this.bot.y < 0) this.bot.y = 0; if (this.bot.y + this.bot.height > this.canvas.height) { this.bot.y = this.canvas.height - this.bot.height; } if (isSetBreakPaused || isPointPauseActive) { return; } // Ruch piłki this.ball.x += this.ball.dx; this.ball.y += this.ball.dy; // Kolizja ze ścianami (góra/dół) if (this.ball.y - this.ball.radius < 0) { this.ball.y = this.ball.radius; this.ball.dy = Math.abs(this.ball.dy); this.promoteBallSpeedAfterServe(); if (window.audioManager) { window.audioManager.playRandomSound(); } } if (this.ball.y + this.ball.radius > this.canvas.height) { this.ball.y = this.canvas.height - this.ball.radius; this.ball.dy = -Math.abs(this.ball.dy); this.promoteBallSpeedAfterServe(); if (window.audioManager) { window.audioManager.playRandomSound(); } } // Kolizja z paletką gracza if (this.ball.x - this.ball.radius < this.player.x + this.player.width && this.ball.x + this.ball.radius > this.player.x && this.ball.y > this.player.y && this.ball.y < this.player.y + this.player.height) { this.ball.dx = Math.abs(this.ball.dx); const hitPos = (this.ball.y - (this.player.y + this.player.height / 2)) / (this.player.height / 2); const currentMultiplier = this.ball.isServe ? this.serveSpeedMultiplier : this.getBallSpeedMultiplier(); this.ball.dy = hitPos * 8 * currentMultiplier; this.promoteBallSpeedAfterServe(); if (window.audioManager) { window.audioManager.playRandomSound(); } } // Kolizja z paletką bota if (this.ball.x + this.ball.radius > this.bot.x && this.ball.x - this.ball.radius < this.bot.x + this.bot.width && this.ball.y > this.bot.y && this.ball.y < this.bot.y + this.bot.height) { this.ball.dx = -Math.abs(this.ball.dx); const hitPos = (this.ball.y - (this.bot.y + this.bot.height / 2)) / (this.bot.height / 2); const currentMultiplier = this.ball.isServe ? this.serveSpeedMultiplier : this.getBallSpeedMultiplier(); this.ball.dy = hitPos * 8 * currentMultiplier; this.promoteBallSpeedAfterServe(); if (window.audioManager) { window.audioManager.playRandomSound(); } } // Punktacja if (this.ball.x - this.ball.radius < 0) { this.awardPoint('bot'); } if (this.ball.x + this.ball.radius > this.canvas.width) { this.awardPoint('player'); } } awardPoint(side) { if (side === 'player') { this.playerScore += 1; } else { this.botScore += 1; } this.syncScoreUi(); if (this.isSetWon('player')) { this.finishSet('player'); return; } if (this.isSetWon('bot')) { this.finishSet('bot'); return; } this.pointPauseUntil = Date.now() + this.pointResetDelayMs; this.resetBall({ frozen: true }); } isSetWon(side) { const score = side === 'player' ? this.playerScore : this.botScore; const opponentScore = side === 'player' ? this.botScore : this.playerScore; return score >= this.pointsToWin && (score - opponentScore) >= 2; } finishSet(side) { if (side === 'player') { this.playerSets += 1; } else { this.botSets += 1; } this.syncScoreUi(); const matchWon = (side === 'player' ? this.playerSets : this.botSets) >= this.setsToWin; if (matchWon) { this.gameActive = false; this.gameEndTime = Date.now(); if (window.audioManager) { if (side === 'player') { window.audioManager.playWinSound(); } else { window.audioManager.playLoseSound(); } } setTimeout(() => { if (!window.uiManager) return; if (side === 'player') { window.uiManager.showWinModal(this.getFormattedTime()); } else { window.uiManager.showLoseModal(this.getFormattedTime()); } }, 500); return; } this.playerScore = 0; this.botScore = 0; this.pointPauseUntil = 0; this.setBreakUntil = Date.now() + this.setBreakDelayMs; this.player.y = this.canvas.height / 2 - this.paddleHeight / 2; this.bot.y = this.canvas.height / 2 - this.paddleHeight / 2; this.resetBall({ frozen: true }); this.syncScoreUi(); } syncScoreUi() { if (window.uiManager) { window.uiManager.updateScore(this.playerScore, this.botScore, this.playerSets, this.botSets); } } draw() { // Tło this.ctx.fillStyle = '#0a0a0a'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); // Siatka this.drawNet(); // Paletka gracza (niebieska) this.drawRect(this.player.x, this.player.y, this.player.width, this.player.height, '#0080ff', '#0080ff'); // Paletka bota (czerwona) this.drawRect(this.bot.x, this.bot.y, this.bot.width, this.bot.height, '#ff006e', '#ff006e'); // Piłka (cyjan) this.drawCircle(this.ball.x, this.ball.y, this.ball.radius, '#00fff7', '#00fff7'); if (this.setBreakUntil > Date.now()) { this.drawSetBreakAnimation(); } } drawSetBreakAnimation() { const remainingMs = Math.max(0, this.setBreakUntil - Date.now()); const secondsLeft = Math.max(1, Math.ceil(remainingMs / 1000)); const secondProgress = 1 - ((remainingMs % 1000) / 1000); const scale = 1 + (secondProgress * 0.22); const alpha = 0.45 + (secondProgress * 0.35); this.ctx.fillStyle = 'rgba(5, 10, 20, 0.55)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.save(); this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2); this.ctx.scale(scale, scale); this.ctx.textAlign = 'center'; this.ctx.fillStyle = `rgba(0, 255, 247, ${alpha})`; this.ctx.shadowBlur = 35; this.ctx.shadowColor = '#00fff7'; this.ctx.font = 'bold 92px Orbitron, Arial, sans-serif'; this.ctx.fillText(String(secondsLeft), 0, 14); this.ctx.restore(); } drawRect(x, y, w, h, color, glow) { this.ctx.fillStyle = color; this.ctx.shadowBlur = 20; this.ctx.shadowColor = glow; this.ctx.fillRect(x, y, w, h); this.ctx.shadowBlur = 0; } drawCircle(x, y, r, color, glow) { this.ctx.fillStyle = color; this.ctx.shadowBlur = 30; this.ctx.shadowColor = glow; this.ctx.beginPath(); this.ctx.arc(x, y, r, 0, Math.PI * 2); this.ctx.fill(); this.ctx.shadowBlur = 0; } drawNet() { this.ctx.strokeStyle = 'rgba(0, 255, 247, 0.3)'; this.ctx.lineWidth = 2; this.ctx.setLineDash([10, 10]); this.ctx.beginPath(); this.ctx.moveTo(this.canvas.width / 2, 0); this.ctx.lineTo(this.canvas.width / 2, this.canvas.height); this.ctx.stroke(); this.ctx.setLineDash([]); } gameLoop() { this.update(); this.draw(); // Aktualizuj timer w HTML const timerElement = document.getElementById('gameTimer'); if (timerElement) { timerElement.textContent = this.getFormattedTime(); } if (this.gameActive) { this.animationId = requestAnimationFrame(() => this.gameLoop()); } } getScores() { return { player: this.playerScore, bot: this.botScore, playerSets: this.playerSets, botSets: this.botSets }; } getGameTime() { if (!this.gameStartTime) return 0; const endTime = this.gameEndTime || Date.now(); return Math.floor((endTime - this.gameStartTime) / 1000); } getFormattedTime() { const totalSeconds = this.getGameTime(); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}:${seconds.toString().padStart(2, '0')}`; } } // Eksportuj do window if (typeof window !== 'undefined') { window.PingPongGame = PingPongGame; } })(); // End of IIFE