/ html / assets / js / pong.js
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    });