pong.js
1 window.addEventListener('DOMContentLoaded', () => { 2 (() => { 3 const overlay = document.getElementById('pongOverlay'); 4 const canvas = document.getElementById('pongCanvas'); 5 const ctx = canvas.getContext('2d'); 6 7 const paddleWidth = 10, paddleHeight = 100, ballSize = 10; 8 const initialBallSpeed = 300, paddleSpeed = 400, fireballThreshold = 700; 9 const particleCount = 20, fireParticleCount = 30, fireballParticleInterval = 0.05; 10 11 let ball = { x: 0, y: 0, vx: 1, vy: 0, speed: initialBallSpeed }; 12 let player = { x: 20, y: 0, score: 0 }; 13 let bot = { x: 0, y: 0, score: 0 }; 14 let keys = {}, particles = []; 15 16 let lastTime = 0, fireballParticleTimer = 0; 17 let gameOver = false, pongActive = false, sequence = ''; 18 const secretCode = 'pong'; 19 20 document.addEventListener('keydown', (e) => { 21 const key = e.key.toLowerCase(); 22 if (!pongActive) { 23 sequence = (sequence + key).slice(-secretCode.length); 24 if (sequence === secretCode) { 25 pongActive = true; 26 overlay.style.display = 'block'; 27 startPong(); 28 } 29 } else { 30 keys[key] = true; 31 32 if (gameOver && key === ' ') { 33 startPong(); 34 } else if (key === 'escape') { 35 pongActive = false; 36 overlay.style.display = 'none'; 37 gameOver = true; 38 keys = {}; 39 } 40 } 41 }); 42 43 document.addEventListener('keyup', (e) => { 44 keys[e.key.toLowerCase()] = false; 45 }); 46 47 function startPong() { 48 canvas.width = canvas.clientWidth; 49 canvas.height = canvas.clientHeight; 50 if (player.y === 0 && bot.y === 0) { 51 player.y = bot.y = (canvas.height - paddleHeight) / 2; 52 } 53 bot.x = canvas.width - 30; 54 ball.speed = initialBallSpeed; 55 gameOver = false; 56 lastTime = 0; 57 resetBall(); 58 requestAnimationFrame(gameLoop); 59 } 60 61 function resetBall() { 62 ball.x = canvas.width / 2; 63 ball.y = canvas.height / 2; 64 const angle = (Math.random() - 0.5) * (Math.PI / 4); 65 const direction = Math.random() > 0.5 ? 1 : -1; 66 ball.vx = Math.cos(angle) * direction; 67 ball.vy = Math.sin(angle); 68 } 69 70 const drawRect = (x, y, w, h, color) => { 71 ctx.fillStyle = color; 72 ctx.fillRect(x, y, w, h); 73 }; 74 75 const drawCircle = (x, y, r, color) => { 76 ctx.fillStyle = color; 77 ctx.beginPath(); 78 ctx.arc(x, y, r, 0, Math.PI * 2); 79 ctx.fill(); 80 }; 81 82 const drawText = (text, x, y, size, color) => { 83 ctx.fillStyle = color; 84 ctx.font = `${size}px Arial`; 85 ctx.fillText(text, x, y); 86 }; 87 88 function emitParticles(x, y, isFire = false, normalX = 0, normalY = 0) { 89 const count = isFire ? fireParticleCount : particleCount; 90 const spread = isFire ? 300 : 100; 91 const baseColor = isFire ? `hsl(${Math.random() * 30}, 100%, 50%)` : '#f60'; 92 const angleBase = Math.atan2(normalY, normalX); 93 const angleSpread = isFire ? Math.PI : Math.PI / 2; 94 95 for (let i = 0; i < count; i++) { 96 const angle = angleBase + (Math.random() - 0.5) * angleSpread; 97 const speed = Math.random() * spread; 98 particles.push({ 99 x, y, 100 vx: Math.cos(angle) * speed, 101 vy: Math.sin(angle) * speed, 102 life: isFire ? 0.5 : 0.6, 103 initialLife: isFire ? 0.5 : 0.6, 104 color: baseColor, 105 spin: (Math.random() - 0.5) * 4, 106 angle: Math.random() * Math.PI * 2, 107 size: 2 + Math.random() * 2, 108 }); 109 } 110 } 111 112 function updateParticles(dt) { 113 particles = particles.filter(p => (p.life -= dt) > 0); 114 for (let p of particles) { 115 p.x += p.vx * dt; 116 p.y += p.vy * dt; 117 p.angle += p.spin * dt; 118 } 119 } 120 121 function drawParticles() { 122 for (let p of particles) { 123 ctx.save(); 124 ctx.translate(p.x, p.y); 125 ctx.rotate(p.angle); 126 const alpha = Math.max(0, p.life / (p.initialLife || p.life)); 127 ctx.fillStyle = p.color; 128 if (p.color.startsWith('hsl')) { 129 ctx.fillStyle = p.color.replace('hsl', 'hsla').replace(')', `, ${alpha})`); 130 } else if (p.color.startsWith('#')) { 131 ctx.globalAlpha = alpha; 132 } 133 ctx.beginPath(); 134 ctx.arc(0, 0, p.size, 0, Math.PI * 2); 135 ctx.fill(); 136 ctx.restore(); 137 ctx.globalAlpha = 1.0; 138 } 139 } 140 141 function gameLoop(timestamp) { 142 if (!lastTime) lastTime = timestamp; 143 const dt = (timestamp - lastTime) / 1000; 144 lastTime = timestamp; 145 update(dt); 146 render(); 147 if (!gameOver) requestAnimationFrame(gameLoop); 148 } 149 150 function update(dt) { 151 if (keys['arrowup']) player.y -= paddleSpeed * dt; 152 if (keys['arrowdown']) player.y += paddleSpeed * dt; 153 player.y = Math.max(0, Math.min(canvas.height - paddleHeight, player.y)); 154 155 const targetY = ball.y - paddleHeight / 2; 156 bot.y += Math.sign(targetY - bot.y) * Math.min(Math.abs(targetY - bot.y), paddleSpeed * 0.8 * dt); 157 bot.y = Math.max(0, Math.min(canvas.height - paddleHeight, bot.y)); 158 159 ball.x += ball.vx * ball.speed * dt; 160 ball.y += ball.vy * ball.speed * dt; 161 162 if (ball.y < 0 || ball.y > canvas.height - ballSize) { 163 ball.vy *= -1; 164 emitParticles(ball.x, ball.y, false, 0, ball.vy > 0 ? 1 : -1); 165 } 166 167 checkPaddleCollision(player, 1); 168 checkPaddleCollision(bot, -1); 169 170 if (ball.speed > fireballThreshold) { 171 fireballParticleTimer -= dt; 172 if (fireballParticleTimer <= 0) { 173 emitParticles(ball.x, ball.y, true); 174 fireballParticleTimer = fireballParticleInterval; 175 } 176 } 177 178 if (ball.x < 0) { 179 bot.score++; 180 endGame('Bot Wins!'); 181 } else if (ball.x > canvas.width) { 182 player.score++; 183 endGame('You Win!'); 184 } 185 186 updateParticles(dt); 187 } 188 189 function checkPaddleCollision(paddle, direction) { 190 const ballLeft = ball.x; 191 const ballRight = ball.x + ballSize; 192 const paddleLeft = paddle.x; 193 const paddleRight = paddle.x + paddleWidth; 194 195 const withinX = (direction > 0) 196 ? (ballRight > paddleLeft && ballLeft < paddleRight) 197 : (ballLeft < paddleRight && ballRight > paddleLeft); 198 199 if (withinX && ball.y + ballSize > paddle.y && ball.y < paddle.y + paddleHeight) { 200 ball.vx = Math.abs(ball.vx) * direction; 201 const relativeY = (paddle.y + paddleHeight / 2) - (ball.y + ballSize / 2); 202 ball.vy = -relativeY / (paddleHeight / 2); 203 ball.speed += 30; 204 emitParticles(ball.x, ball.y, false, direction, 0); 205 } 206 } 207 208 function render() { 209 ctx.clearRect(0, 0, canvas.width, canvas.height); 210 211 // Middle line 212 for (let i = 0; i < canvas.height; i += 30) { 213 drawRect(canvas.width / 2 - 1, i, 2, 20, '#444'); 214 } 215 216 drawRect(player.x, player.y, paddleWidth, paddleHeight, '#0f0'); 217 drawRect(bot.x, bot.y, paddleWidth, paddleHeight, '#f00'); 218 drawCircle(ball.x, ball.y, ballSize, '#f60'); 219 drawParticles(); 220 221 const scoreY = 50; 222 const scoreSize = 40; 223 224 const playerScoreText = player.score.toString(); 225 const playerScoreWidth = ctx.measureText(playerScoreText).width; 226 drawText(playerScoreText, canvas.width / 4 - playerScoreWidth / 2, scoreY, scoreSize, '#0f0'); 227 228 const botScoreText = bot.score.toString(); 229 const botScoreWidth = ctx.measureText(botScoreText).width; 230 drawText(botScoreText, canvas.width * 3/4 - botScoreWidth / 2, scoreY, scoreSize, '#f00'); 231 232 // Game Over Screen 233 if (gameOver) { 234 ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; 235 ctx.fillRect(0, 0, canvas.width, canvas.height); 236 237 ctx.fillStyle = '#fff'; 238 ctx.font = '40px Arial'; 239 const msg = 'Game Over!'; 240 const msgWidth = ctx.measureText(msg).width; 241 ctx.fillText(msg, (canvas.width - msgWidth) / 2, canvas.height / 2 - 30); 242 243 ctx.font = '20px Arial'; 244 const info = 'Press [SPACE] to play again!'; 245 const infoWidth = ctx.measureText(info).width; 246 ctx.fillText(info, (canvas.width - infoWidth) / 2, canvas.height / 2 + 10); 247 248 const escNote = 'Press [ESC] to quit Pong.'; 249 const escWidth = ctx.measureText(escNote).width; 250 ctx.fillText(escNote, (canvas.width - escWidth) / 2, canvas.height / 2 + 40); 251 } 252 } 253 254 function endGame(message) { 255 gameOver = true; 256 } 257 })(); 258 });