Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Advanced Ping Pong</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); | |
| body { | |
| font-family: 'Press Start 2P', cursive; | |
| overflow: hidden; | |
| background: linear-gradient(135deg, #1a1a2e, #16213e); | |
| color: white; | |
| } | |
| #game-container { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| #game-board { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| border: 8px solid #4cc9f0; | |
| border-radius: 10px; | |
| box-shadow: 0 0 30px rgba(76, 201, 240, 0.5); | |
| background-color: #0f0f1a; | |
| } | |
| .paddle { | |
| position: absolute; | |
| background: linear-gradient(to right, #f72585, #b5179e); | |
| border-radius: 10px; | |
| box-shadow: 0 0 15px rgba(247, 37, 133, 0.7); | |
| } | |
| #ball { | |
| position: absolute; | |
| background: radial-gradient(circle at 30% 30%, #fff, #4cc9f0); | |
| border-radius: 50%; | |
| box-shadow: 0 0 20px rgba(76, 201, 240, 0.8); | |
| } | |
| .power-up { | |
| position: absolute; | |
| border-radius: 50%; | |
| animation: pulse 1.5s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.2); } | |
| 100% { transform: scale(1); } | |
| } | |
| .score { | |
| position: absolute; | |
| top: 20px; | |
| font-size: 2rem; | |
| text-shadow: 0 0 10px rgba(76, 201, 240, 0.8); | |
| } | |
| #player-score { | |
| left: 30%; | |
| } | |
| #computer-score { | |
| right: 30%; | |
| } | |
| #game-over { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| text-align: center; | |
| display: none; | |
| z-index: 10; | |
| } | |
| #start-screen { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| text-align: center; | |
| z-index: 10; | |
| } | |
| button { | |
| background: linear-gradient(to right, #f72585, #b5179e); | |
| border: none; | |
| color: white; | |
| padding: 15px 30px; | |
| margin: 10px; | |
| border-radius: 5px; | |
| font-family: 'Press Start 2P', cursive; | |
| cursor: pointer; | |
| box-shadow: 0 0 15px rgba(247, 37, 133, 0.5); | |
| transition: all 0.3s; | |
| } | |
| button:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 0 20px rgba(247, 37, 133, 0.8); | |
| } | |
| .effect { | |
| position: absolute; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| opacity: 0; | |
| } | |
| #instructions { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| font-size: 0.8rem; | |
| text-align: center; | |
| color: rgba(255, 255, 255, 0.7); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <div id="start-screen"> | |
| <h1 class="text-4xl mb-8 text-transparent bg-clip-text bg-gradient-to-r from-pink-500 to-purple-600">ADVANCED PING PONG</h1> | |
| <button id="start-btn">START GAME</button> | |
| <button id="how-to-play-btn">HOW TO PLAY</button> | |
| <div id="how-to-play" class="mt-6 hidden"> | |
| <p class="mb-2">Use MOUSE or TOUCH to move your paddle</p> | |
| <p class="mb-2">First to score 7 points wins!</p> | |
| <p>Collect power-ups for special abilities</p> | |
| </div> | |
| </div> | |
| <canvas id="game-board"></canvas> | |
| <div id="player-score" class="score">0</div> | |
| <div id="computer-score" class="score">0</div> | |
| <div id="game-over"> | |
| <h1 id="game-over-text" class="text-4xl mb-8"></h1> | |
| <button id="restart-btn">PLAY AGAIN</button> | |
| </div> | |
| <div id="instructions"> | |
| <p>Move your mouse or finger to control the paddle</p> | |
| </div> | |
| </div> | |
| <script> | |
| // Game variables | |
| const canvas = document.getElementById('game-board'); | |
| const ctx = canvas.getContext('2d'); | |
| const playerScoreDisplay = document.getElementById('player-score'); | |
| const computerScoreDisplay = document.getElementById('computer-score'); | |
| const gameOverScreen = document.getElementById('game-over'); | |
| const gameOverText = document.getElementById('game-over-text'); | |
| const startScreen = document.getElementById('start-screen'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const restartBtn = document.getElementById('restart-btn'); | |
| const howToPlayBtn = document.getElementById('how-to-play-btn'); | |
| const howToPlay = document.getElementById('how-to-play'); | |
| // Game settings | |
| const WINNING_SCORE = 7; | |
| const POWER_UP_CHANCE = 0.02; // 2% chance per frame | |
| const POWER_UP_DURATION = 300; // frames (5 seconds at 60fps) | |
| // Game state | |
| let gameRunning = false; | |
| let playerScore = 0; | |
| let computerScore = 0; | |
| let lastTime = 0; | |
| let powerUps = []; | |
| let activeEffects = { | |
| player: null, | |
| computer: null | |
| }; | |
| let effects = []; | |
| // Game objects | |
| const ball = { | |
| x: 0, | |
| y: 0, | |
| radius: 10, | |
| speedX: 5, | |
| speedY: 5, | |
| color: '#4cc9f0' | |
| }; | |
| const playerPaddle = { | |
| x: 0, | |
| y: 0, | |
| width: 15, | |
| height: 100, | |
| speed: 8, | |
| color: '#f72585' | |
| }; | |
| const computerPaddle = { | |
| x: 0, | |
| y: 0, | |
| width: 15, | |
| height: 100, | |
| speed: 5, | |
| color: '#7209b7' | |
| }; | |
| // Power-up types | |
| const POWER_UP_TYPES = { | |
| EXTRA_LIFE: { color: '#4cc9f0', radius: 12, effect: 'extraLife' }, | |
| SPEED_BOOST: { color: '#f72585', radius: 12, effect: 'speedBoost' }, | |
| PADDLE_GROW: { color: '#b5179e', radius: 12, effect: 'paddleGrow' }, | |
| PADDLE_SHRINK: { color: '#7209b7', radius: 12, effect: 'paddleShrink' }, | |
| BALL_SLOW: { color: '#3a0ca3', radius: 12, effect: 'ballSlow' }, | |
| BALL_FAST: { color: '#4361ee', radius: 12, effect: 'ballFast' } | |
| }; | |
| // Initialize game | |
| function init() { | |
| resizeCanvas(); | |
| resetGame(); | |
| // Event listeners | |
| window.addEventListener('resize', resizeCanvas); | |
| canvas.addEventListener('mousemove', movePaddle); | |
| canvas.addEventListener('touchmove', (e) => { | |
| e.preventDefault(); | |
| const touch = e.touches[0]; | |
| const rect = canvas.getBoundingClientRect(); | |
| const mouseY = touch.clientY - rect.top; | |
| movePaddle({ clientY: mouseY }); | |
| }); | |
| startBtn.addEventListener('click', startGame); | |
| restartBtn.addEventListener('click', startGame); | |
| howToPlayBtn.addEventListener('click', () => { | |
| howToPlay.classList.toggle('hidden'); | |
| }); | |
| } | |
| // Resize canvas to fit window | |
| function resizeCanvas() { | |
| canvas.width = Math.min(window.innerWidth - 40, 800); | |
| canvas.height = Math.min(window.innerHeight - 40, 500); | |
| resetPositions(); | |
| } | |
| // Reset game objects to starting positions | |
| function resetPositions() { | |
| ball.x = canvas.width / 2; | |
| ball.y = canvas.height / 2; | |
| // Randomize ball direction | |
| ball.speedX = 5 * (Math.random() > 0.5 ? 1 : -1); | |
| ball.speedY = 5 * (Math.random() * 2 - 1); | |
| playerPaddle.x = 20; | |
| playerPaddle.y = (canvas.height - playerPaddle.height) / 2; | |
| computerPaddle.x = canvas.width - 20 - computerPaddle.width; | |
| computerPaddle.y = (canvas.height - computerPaddle.height) / 2; | |
| // Reset power-ups and effects | |
| powerUps = []; | |
| effects = []; | |
| activeEffects.player = null; | |
| activeEffects.computer = null; | |
| } | |
| // Reset game state | |
| function resetGame() { | |
| playerScore = 0; | |
| computerScore = 0; | |
| playerScoreDisplay.textContent = '0'; | |
| computerScoreDisplay.textContent = '0'; | |
| resetPositions(); | |
| } | |
| // Start the game | |
| function startGame() { | |
| resetGame(); | |
| gameRunning = true; | |
| startScreen.style.display = 'none'; | |
| gameOverScreen.style.display = 'none'; | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // End the game | |
| function endGame(winner) { | |
| gameRunning = false; | |
| gameOverScreen.style.display = 'block'; | |
| gameOverText.textContent = `${winner} WINS!`; | |
| } | |
| // Move player paddle | |
| function movePaddle(e) { | |
| if (!gameRunning) return; | |
| const rect = canvas.getBoundingClientRect(); | |
| const mouseY = e.clientY - rect.top; | |
| // Keep paddle within canvas | |
| playerPaddle.y = Math.max( | |
| 0, | |
| Math.min( | |
| mouseY - playerPaddle.height / 2, | |
| canvas.height - playerPaddle.height | |
| ) | |
| ); | |
| } | |
| // AI for computer paddle | |
| function moveComputerPaddle() { | |
| // Simple AI - follow the ball with some delay | |
| const computerPaddleCenter = computerPaddle.y + computerPaddle.height / 2; | |
| const ballCenter = ball.y + ball.radius; | |
| // Add some imperfection to the AI | |
| const reactionThreshold = 0.3; | |
| const reactionSpeed = computerPaddle.speed * (0.5 + Math.random() * 0.5); | |
| if (computerPaddleCenter < ballCenter - reactionThreshold * computerPaddle.height) { | |
| computerPaddle.y += reactionSpeed; | |
| } else if (computerPaddleCenter > ballCenter + reactionThreshold * computerPaddle.height) { | |
| computerPaddle.y -= reactionSpeed; | |
| } | |
| // Keep paddle within canvas | |
| computerPaddle.y = Math.max(0, Math.min(computerPaddle.y, canvas.height - computerPaddle.height)); | |
| } | |
| // Check collision between ball and paddle | |
| function collision(ball, paddle) { | |
| return ( | |
| ball.x + ball.radius > paddle.x && | |
| ball.x - ball.radius < paddle.x + paddle.width && | |
| ball.y + ball.radius > paddle.y && | |
| ball.y - ball.radius < paddle.y + paddle.height | |
| ); | |
| } | |
| // Spawn random power-up | |
| function spawnPowerUp() { | |
| if (Math.random() < POWER_UP_CHANCE && powerUps.length < 2) { | |
| const types = Object.keys(POWER_UP_TYPES); | |
| const randomType = types[Math.floor(Math.random() * types.length)]; | |
| const powerUp = { | |
| ...POWER_UP_TYPES[randomType], | |
| x: Math.random() * (canvas.width - 40) + 20, | |
| y: Math.random() * (canvas.height - 40) + 20, | |
| type: randomType, | |
| lifetime: 300 // 5 seconds at 60fps | |
| }; | |
| powerUps.push(powerUp); | |
| } | |
| } | |
| // Apply power-up effect | |
| function applyPowerUp(powerUp, target) { | |
| // Remove any existing effect | |
| if (activeEffects[target]) { | |
| removeEffect(activeEffects[target], target); | |
| } | |
| // Apply new effect | |
| switch (powerUp.effect) { | |
| case 'extraLife': | |
| if (target === 'player') playerScore++; | |
| else computerScore++; | |
| updateScore(); | |
| createEffect(powerUp.x, powerUp.y, powerUp.color, '+1'); | |
| break; | |
| case 'speedBoost': | |
| if (target === 'player') { | |
| playerPaddle.speed *= 1.5; | |
| } else { | |
| computerPaddle.speed *= 1.5; | |
| } | |
| activeEffects[target] = { type: 'speedBoost', duration: POWER_UP_DURATION }; | |
| createEffect(powerUp.x, powerUp.y, powerUp.color, 'SPEED!'); | |
| break; | |
| case 'paddleGrow': | |
| if (target === 'player') { | |
| playerPaddle.height *= 1.5; | |
| } else { | |
| computerPaddle.height *= 1.5; | |
| } | |
| activeEffects[target] = { type: 'paddleGrow', duration: POWER_UP_DURATION }; | |
| createEffect(powerUp.x, powerUp.y, powerUp.color, 'GROW!'); | |
| break; | |
| case 'paddleShrink': | |
| if (target === 'opponent') { | |
| playerPaddle.height *= 0.7; | |
| activeEffects.player = { type: 'paddleShrink', duration: POWER_UP_DURATION }; | |
| } else { | |
| computerPaddle.height *= 0.7; | |
| activeEffects.computer = { type: 'paddleShrink', duration: POWER_UP_DURATION }; | |
| } | |
| createEffect(powerUp.x, powerUp.y, powerUp.color, 'SHRINK!'); | |
| break; | |
| case 'ballSlow': | |
| ball.speedX *= 0.7; | |
| ball.speedY *= 0.7; | |
| activeEffects[target] = { type: 'ballSlow', duration: POWER_UP_DURATION }; | |
| createEffect(powerUp.x, powerUp.y, powerUp.color, 'SLOW!'); | |
| break; | |
| case 'ballFast': | |
| ball.speedX *= 1.3; | |
| ball.speedY *= 1.3; | |
| activeEffects[target] = { type: 'ballFast', duration: POWER_UP_DURATION }; | |
| createEffect(powerUp.x, powerUp.y, powerUp.color, 'FAST!'); | |
| break; | |
| } | |
| } | |
| // Remove effect after duration expires | |
| function removeEffect(effect, target) { | |
| switch (effect.type) { | |
| case 'speedBoost': | |
| if (target === 'player') { | |
| playerPaddle.speed /= 1.5; | |
| } else { | |
| computerPaddle.speed /= 1.5; | |
| } | |
| break; | |
| case 'paddleGrow': | |
| if (target === 'player') { | |
| playerPaddle.height /= 1.5; | |
| } else { | |
| computerPaddle.height /= 1.5; | |
| } | |
| break; | |
| case 'paddleShrink': | |
| if (target === 'player') { | |
| playerPaddle.height /= 0.7; | |
| } else { | |
| computerPaddle.height /= 0.7; | |
| } | |
| break; | |
| case 'ballSlow': | |
| ball.speedX /= 0.7; | |
| ball.speedY /= 0.7; | |
| break; | |
| case 'ballFast': | |
| ball.speedX /= 1.3; | |
| ball.speedY /= 1.3; | |
| break; | |
| } | |
| } | |
| // Create visual effect | |
| function createEffect(x, y, color, text) { | |
| effects.push({ | |
| x, | |
| y, | |
| color, | |
| text, | |
| alpha: 1, | |
| size: 20, | |
| lifetime: 60 | |
| }); | |
| } | |
| // Update score display | |
| function updateScore() { | |
| playerScoreDisplay.textContent = playerScore; | |
| computerScoreDisplay.textContent = computerScore; | |
| // Check for winner | |
| if (playerScore >= WINNING_SCORE) { | |
| endGame('PLAYER'); | |
| } else if (computerScore >= WINNING_SCORE) { | |
| endGame('COMPUTER'); | |
| } | |
| } | |
| // Main game loop | |
| function gameLoop(timestamp) { | |
| if (!gameRunning) return; | |
| // Calculate delta time for smooth animation | |
| const deltaTime = timestamp - lastTime; | |
| lastTime = timestamp; | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw center line | |
| ctx.beginPath(); | |
| ctx.setLineDash([10, 10]); | |
| ctx.moveTo(canvas.width / 2, 0); | |
| ctx.lineTo(canvas.width / 2, canvas.height); | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)'; | |
| ctx.lineWidth = 4; | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| // Draw paddles | |
| ctx.fillStyle = playerPaddle.color; | |
| ctx.fillRect(playerPaddle.x, playerPaddle.y, playerPaddle.width, playerPaddle.height); | |
| ctx.fillStyle = computerPaddle.color; | |
| ctx.fillRect(computerPaddle.x, computerPaddle.y, computerPaddle.width, computerPaddle.height); | |
| // Draw ball | |
| ctx.beginPath(); | |
| ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); | |
| ctx.fillStyle = ball.color; | |
| ctx.fill(); | |
| // Move ball | |
| ball.x += ball.speedX; | |
| ball.y += ball.speedY; | |
| // Ball collision with top and bottom walls | |
| if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) { | |
| ball.speedY = -ball.speedY; | |
| // Add some randomness to bounce | |
| ball.speedY *= (0.9 + Math.random() * 0.2); | |
| } | |
| // Ball collision with paddles | |
| if (collision(ball, playerPaddle)) { | |
| // Calculate angle based on where ball hits paddle | |
| const hitPosition = (ball.y - (playerPaddle.y + playerPaddle.height / 2)) / (playerPaddle.height / 2); | |
| const angle = hitPosition * Math.PI / 4; // Max 45 degree angle | |
| ball.speedX = Math.abs(ball.speedX) * 1.05; // Increase speed slightly | |
| ball.speedY = Math.sin(angle) * Math.abs(ball.speedX) * 1.2; | |
| // Add some randomness | |
| ball.speedX *= (0.95 + Math.random() * 0.1); | |
| ball.speedY *= (0.95 + Math.random() * 0.1); | |
| // Create hit effect | |
| createEffect(ball.x, ball.y, playerPaddle.color, ''); | |
| } | |
| if (collision(ball, computerPaddle)) { | |
| // Calculate angle based on where ball hits paddle | |
| const hitPosition = (ball.y - (computerPaddle.y + computerPaddle.height / 2)) / (computerPaddle.height / 2); | |
| const angle = hitPosition * Math.PI / 4; // Max 45 degree angle | |
| ball.speedX = -Math.abs(ball.speedX) * 1.05; // Increase speed slightly | |
| ball.speedY = Math.sin(angle) * Math.abs(ball.speedX) * 1.2; | |
| // Add some randomness | |
| ball.speedX *= (0.95 + Math.random() * 0.1); | |
| ball.speedY *= (0.95 + Math.random() * 0.1); | |
| // Create hit effect | |
| createEffect(ball.x, ball.y, computerPaddle.color, ''); | |
| } | |
| // Ball out of bounds | |
| if (ball.x - ball.radius < 0) { | |
| computerScore++; | |
| updateScore(); | |
| resetPositions(); | |
| } else if (ball.x + ball.radius > canvas.width) { | |
| playerScore++; | |
| updateScore(); | |
| resetPositions(); | |
| } | |
| // Move computer paddle | |
| moveComputerPaddle(); | |
| // Spawn and update power-ups | |
| spawnPowerUp(); | |
| for (let i = powerUps.length - 1; i >= 0; i--) { | |
| const powerUp = powerUps[i]; | |
| // Draw power-up | |
| ctx.beginPath(); | |
| ctx.arc(powerUp.x, powerUp.y, powerUp.radius, 0, Math.PI * 2); | |
| ctx.fillStyle = powerUp.color; | |
| ctx.fill(); | |
| // Draw outline | |
| ctx.beginPath(); | |
| ctx.arc(powerUp.x, powerUp.y, powerUp.radius + 2, 0, Math.PI * 2); | |
| ctx.strokeStyle = 'white'; | |
| ctx.lineWidth = 2; | |
| ctx.stroke(); | |
| // Check collision with paddles | |
| const playerDist = Math.sqrt( | |
| Math.pow(powerUp.x - (playerPaddle.x + playerPaddle.width / 2), 2) + | |
| Math.pow(powerUp.y - (playerPaddle.y + playerPaddle.height / 2), 2) | |
| ); | |
| const computerDist = Math.sqrt( | |
| Math.pow(powerUp.x - (computerPaddle.x + computerPaddle.width / 2), 2) + | |
| Math.pow(powerUp.y - (computerPaddle.y + computerPaddle.height / 2), 2) | |
| ); | |
| const paddleRadius = Math.max(playerPaddle.width, playerPaddle.height) / 2; | |
| if (playerDist < powerUp.radius + paddleRadius) { | |
| applyPowerUp(powerUp, 'player'); | |
| powerUps.splice(i, 1); | |
| } else if (computerDist < powerUp.radius + paddleRadius) { | |
| applyPowerUp(powerUp, 'computer'); | |
| powerUps.splice(i, 1); | |
| } else { | |
| // Decrease lifetime | |
| powerUp.lifetime--; | |
| // Remove if expired | |
| if (powerUp.lifetime <= 0) { | |
| powerUps.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Update active effects | |
| for (const target in activeEffects) { | |
| if (activeEffects[target]) { | |
| activeEffects[target].duration--; | |
| if (activeEffects[target].duration <= 0) { | |
| removeEffect(activeEffects[target], target); | |
| activeEffects[target] = null; | |
| } | |
| } | |
| } | |
| // Update and draw effects | |
| for (let i = effects.length - 1; i >= 0; i--) { | |
| const effect = effects[i]; | |
| ctx.globalAlpha = effect.alpha; | |
| ctx.fillStyle = effect.color; | |
| ctx.font = `${effect.size}px 'Press Start 2P'`; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(effect.text, effect.x, effect.y); | |
| ctx.globalAlpha = 1; | |
| effect.alpha -= 0.02; | |
| effect.size += 0.5; | |
| effect.lifetime--; | |
| if (effect.lifetime <= 0) { | |
| effects.splice(i, 1); | |
| } | |
| } | |
| // Continue game loop | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Initialize the game when the page loads | |
| window.onload = init; | |
| </script> | |
| </body> | |
| </html> |