JavaScript · 23661 bytes Raw Blame History
1 // Matter.js module aliases
2 const Body = Matter.Body;
3 const World = Matter.World;
4 const Engine = Matter.Engine;
5 const Bodies = Matter.Bodies;
6 const Render = Matter.Render;
7 const Constraint = Matter.Constraint;
8
9 // Game variables
10 let ball;
11 let world;
12 let engine;
13
14 // Spring paddle system components
15 let boundaries = [];
16 let leftSupport, leftPaddle, leftSpring;
17 let rightSupport, rightPaddle, rightSpring;
18
19 // Game state
20 let leftScore = 0;
21 let rightScore = 0;
22 let gameStarted = false;
23
24 // Player input
25 let keys = {};
26 let inputBuffer = { left: 0, right: 0 };
27
28 // Touch/mouse input
29 let mouseInput = {
30 active: false,
31 targetY: 0,
32 leftPaddleTarget: 0,
33 rightPaddleTarget: 0,
34 smoothing: 0.08, // Slower smoothing for deliberate lag
35 deadZone: 15 // Minimum distance before movement starts
36 };
37
38 // Particle systems
39 let particles = [];
40 let impactParticles = [];
41
42 // Canvas settings
43 const CANVAS_WIDTH = 800;
44 const CANVAS_HEIGHT = 400;
45
46 // Game constants
47 const BALL_SPEED = 6;
48 const BALL_RADIUS = 12;
49 const PADDLE_WIDTH = 20;
50 const PADDLE_HEIGHT = 80;
51
52 // Enhanced movement constants (tuned for faster response)
53 const SUPPORT_SPEED = 6.5; // Bumped up from 4.5
54 const SUPPORT_ACCEL = 1.2; // Increased acceleration
55 const INPUT_SMOOTHING = 0.25; // More responsive
56 const SUPPORT_MAX_SPEED = 8; // Higher max speed
57
58 // Touch/mouse control constants
59 const MOUSE_SPEED_LIMIT = 4; // Max speed for mouse movement
60 const MOUSE_LAG_FACTOR = 0.12; // How much lag in mouse following
61 const TOUCH_SENSITIVITY = 1.2; // Touch movement multiplier
62
63 // Spring physics constants (tuned for bounciness!)
64 const PADDLE_MASS = 0.6; // Lighter for more bounce
65 const SPRING_LENGTH = 40;
66 const SPRING_DAMPING = 0.4; // Much less damping = more bounce!
67 const SPRING_STIFFNESS = 0.035; // Higher stiffness = snappier
68
69 // Visual enhancement constants
70 const TRAIL_SEGMENTS = 8;
71 const PADDLE_GLOW_DISTANCE = 25;
72 const SPRING_GLOW_INTENSITY = 120; // More intense glow
73
74 // Particle system constants
75 const MAX_PARTICLES = 100;
76 const PARTICLE_LIFE = 60;
77 const IMPACT_PARTICLES = 8;
78 const SPRING_PARTICLE_RATE = 0.3;
79
80 function setup() {
81 // Create p5.js canvas
82 let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
83 canvas.parent('gameCanvas');
84
85 // Initialize Matter.js physics engine
86 engine = Engine.create();
87 world = engine.world;
88
89 // Disable gravity for classic Pong feel
90 engine.world.gravity.y = 0;
91 engine.world.gravity.x = 0;
92
93 // Create game boundaries (top and bottom walls)
94 let topWall = Bodies.rectangle(width/2, -10, width, 20, { isStatic: true });
95 let bottomWall = Bodies.rectangle(width/2, height + 10, width, 20, { isStatic: true });
96 boundaries.push(topWall, bottomWall);
97
98 // Create spring paddle systems
99 createSpringPaddleSystem('left');
100 createSpringPaddleSystem('right');
101
102 // Create ball
103 resetBall();
104
105 // Add everything to the world
106 World.add(world, [
107 ...boundaries,
108 ball,
109 leftSupport, leftPaddle, leftSpring,
110 rightSupport, rightPaddle, rightSpring
111 ]);
112
113 console.log("🎮 Sprong Phase 5 Complete!");
114 console.log("✓ Particle effects system");
115 console.log("✓ Tuned physics for maximum bounce");
116 console.log("✓ Faster, more responsive paddles");
117 }
118
119 function createSpringPaddleSystem(side) {
120 let supportX = side === 'left' ? 60 : width - 60;
121 let paddleX = side === 'left' ? 60 + SPRING_LENGTH : width - 60 - SPRING_LENGTH;
122 let startY = height / 2;
123
124 if (side === 'left') {
125 // Left support (invisible anchor point controlled by player)
126 leftSupport = Bodies.rectangle(supportX, startY, 10, 10, {
127 isStatic: true,
128 render: { visible: false }
129 });
130
131 // Left paddle (the actual hitting surface)
132 leftPaddle = Bodies.rectangle(paddleX, startY, PADDLE_WIDTH, PADDLE_HEIGHT, {
133 mass: PADDLE_MASS,
134 restitution: 1.3, // Even bouncier!
135 friction: 0,
136 frictionAir: 0.005 // Less air resistance
137 });
138
139 // Spring constraint connecting support to paddle
140 leftSpring = Constraint.create({
141 bodyA: leftSupport,
142 bodyB: leftPaddle,
143 length: SPRING_LENGTH,
144 stiffness: SPRING_STIFFNESS,
145 damping: SPRING_DAMPING
146 });
147 } else {
148 // Right support (invisible anchor point controlled by player/AI)
149 rightSupport = Bodies.rectangle(supportX, startY, 10, 10, {
150 isStatic: true,
151 render: { visible: false }
152 });
153
154 // Right paddle (the actual hitting surface)
155 rightPaddle = Bodies.rectangle(paddleX, startY, PADDLE_WIDTH, PADDLE_HEIGHT, {
156 mass: PADDLE_MASS,
157 restitution: 1.3,
158 friction: 0,
159 frictionAir: 0.005
160 });
161
162 // Spring constraint connecting support to paddle
163 rightSpring = Constraint.create({
164 bodyA: rightSupport,
165 bodyB: rightPaddle,
166 length: SPRING_LENGTH,
167 stiffness: SPRING_STIFFNESS,
168 damping: SPRING_DAMPING
169 });
170 }
171 }
172
173 function draw() {
174 // Update physics
175 Engine.update(engine);
176
177 // Handle enhanced player input
178 handleEnhancedInput();
179
180 // Update particle systems
181 updateParticles();
182 checkCollisions();
183
184 // Check for scoring
185 checkBallPosition();
186
187 // Clear canvas
188 background(10, 10, 10);
189
190 // Draw particles behind everything
191 drawParticles();
192
193 // Draw game objects with enhanced visuals
194 drawSpringPaddleSystemsEnhanced();
195 drawBallEnhanced();
196 drawBoundaries();
197 drawCenterLine();
198
199 // Draw debug info
200 drawDebugInfo();
201
202 // Start message
203 if (!gameStarted) {
204 drawStartMessage();
205 }
206 }
207
208 function handleEnhancedInput() {
209 // Handle both keyboard and mouse/touch input
210 handleKeyboardInput();
211 handleMouseTouchInput();
212 }
213
214 function handleKeyboardInput() {
215 // Smooth input accumulation with acceleration
216 let leftInput = 0;
217 let rightInput = 0;
218
219 // Left paddle input (W/S keys)
220 if (keys['w'] || keys['W']) leftInput -= 1;
221 if (keys['s'] || keys['S']) leftInput += 1;
222
223 // Right paddle input (Arrow keys)
224 if (keys['ArrowUp']) rightInput -= 1;
225 if (keys['ArrowDown']) rightInput += 1;
226
227 // Apply acceleration and smoothing for keyboard
228 inputBuffer.left = lerp(inputBuffer.left, leftInput, INPUT_SMOOTHING);
229 inputBuffer.right = lerp(inputBuffer.right, rightInput, INPUT_SMOOTHING);
230
231 // Move supports with enhanced physics (only if not using mouse)
232 if (!mouseInput.active) {
233 if (Math.abs(inputBuffer.left) > 0.01) {
234 moveSupportEnhanced(leftSupport, inputBuffer.left * SUPPORT_SPEED);
235 }
236 if (Math.abs(inputBuffer.right) > 0.01) {
237 moveSupportEnhanced(rightSupport, inputBuffer.right * SUPPORT_SPEED);
238 }
239 }
240 }
241
242 function handleMouseTouchInput() {
243 if (!mouseInput.active) return;
244
245 // Determine which paddle to control based on mouse X position
246 let controllingLeft = mouseX < width / 2;
247 let targetSupport = controllingLeft ? leftSupport : rightSupport;
248
249 // Calculate target Y with dead zone
250 let currentY = targetSupport.position.y;
251 let targetY = mouseY;
252 let deltaY = targetY - currentY;
253
254 // Apply dead zone - don't move unless mouse is far enough
255 if (Math.abs(deltaY) < mouseInput.deadZone) {
256 return;
257 }
258
259 // Calculate movement with lag and speed limiting
260 let movement = deltaY * MOUSE_LAG_FACTOR * TOUCH_SENSITIVITY;
261
262 // Limit maximum speed to prevent snappy movement
263 movement = constrain(movement, -MOUSE_SPEED_LIMIT, MOUSE_SPEED_LIMIT);
264
265 // Apply the lagged movement
266 moveSupportEnhanced(targetSupport, movement);
267
268 // Visual feedback - update input buffer for particle effects
269 if (controllingLeft) {
270 inputBuffer.left = constrain(movement / MOUSE_SPEED_LIMIT, -1, 1);
271 } else {
272 inputBuffer.right = constrain(movement / MOUSE_SPEED_LIMIT, -1, 1);
273 }
274 }
275
276 function moveSupportEnhanced(support, deltaY) {
277 let newY = support.position.y + deltaY;
278
279 // Keep support within reasonable bounds with smooth clamping
280 let minY = 50;
281 let maxY = height - 50;
282
283 if (newY < minY) {
284 newY = minY + (newY - minY) * 0.1; // Soft boundary
285 } else if (newY > maxY) {
286 newY = maxY + (newY - maxY) * 0.1; // Soft boundary
287 }
288
289 Body.setPosition(support, { x: support.position.x, y: newY });
290 }
291
292 function checkCollisions() {
293 let ballPos = ball.position;
294 let ballVel = ball.velocity;
295
296 // Check paddle collisions for particle effects
297 let leftDist = dist(ballPos.x, ballPos.y, leftPaddle.position.x, leftPaddle.position.y);
298 let rightDist = dist(ballPos.x, ballPos.y, rightPaddle.position.x, rightPaddle.position.y);
299
300 // Collision threshold
301 let collisionDist = BALL_RADIUS + PADDLE_WIDTH/2 + 5;
302
303 // Left paddle collision
304 if (leftDist < collisionDist && ballVel.x < 0) {
305 createImpactParticles(ballPos.x, ballPos.y, ballVel.x, ballVel.y);
306 }
307
308 // Right paddle collision
309 if (rightDist < collisionDist && ballVel.x > 0) {
310 createImpactParticles(ballPos.x, ballPos.y, ballVel.x, ballVel.y);
311 }
312 }
313
314 function createImpactParticles(x, y, velX, velY) {
315 for (let i = 0; i < IMPACT_PARTICLES; i++) {
316 let angle = random(TWO_PI);
317 let speed = random(2, 8);
318 let size = random(2, 6);
319
320 particles.push({
321 x: x + random(-5, 5),
322 y: y + random(-5, 5),
323 vx: cos(angle) * speed - velX * 0.2,
324 vy: sin(angle) * speed - velY * 0.2,
325 size: size,
326 life: PARTICLE_LIFE,
327 maxLife: PARTICLE_LIFE,
328 color: { r: 255, g: random(100, 255), b: random(100, 150) },
329 type: 'impact'
330 });
331 }
332 }
333
334 function createSpringParticles(springPos, compression) {
335 if (random() < SPRING_PARTICLE_RATE * compression) {
336 let angle = random(TWO_PI);
337 let speed = random(1, 3) * compression;
338
339 particles.push({
340 x: springPos.x + random(-10, 10),
341 y: springPos.y + random(-10, 10),
342 vx: cos(angle) * speed,
343 vy: sin(angle) * speed,
344 size: random(1, 3),
345 life: PARTICLE_LIFE * 0.5,
346 maxLife: PARTICLE_LIFE * 0.5,
347 color: { r: 0, g: 255, b: 136 },
348 type: 'spring'
349 });
350 }
351 }
352
353 function updateParticles() {
354 // Update and remove dead particles
355 for (let i = particles.length - 1; i >= 0; i--) {
356 let p = particles[i];
357
358 // Update position
359 p.x += p.vx;
360 p.y += p.vy;
361
362 // Apply drag
363 p.vx *= 0.98;
364 p.vy *= 0.98;
365
366 // Update life
367 p.life--;
368
369 // Remove dead particles
370 if (p.life <= 0) {
371 particles.splice(i, 1);
372 }
373 }
374
375 // Limit particle count
376 if (particles.length > MAX_PARTICLES) {
377 particles.splice(0, particles.length - MAX_PARTICLES);
378 }
379 }
380
381 function drawParticles() {
382 for (let p of particles) {
383 let alpha = map(p.life, 0, p.maxLife, 0, 255);
384
385 push();
386 translate(p.x, p.y);
387
388 if (p.type === 'impact') {
389 // Impact particles: bright sparks
390 fill(p.color.r, p.color.g, p.color.b, alpha);
391 noStroke();
392 ellipse(0, 0, p.size, p.size);
393
394 // Add glow
395 fill(p.color.r, p.color.g, p.color.b, alpha * 0.3);
396 ellipse(0, 0, p.size * 2, p.size * 2);
397
398 } else if (p.type === 'spring') {
399 // Spring particles: green energy
400 fill(p.color.r, p.color.g, p.color.b, alpha);
401 noStroke();
402 ellipse(0, 0, p.size, p.size);
403 }
404
405 pop();
406 }
407 }
408
409 function drawSpringPaddleSystemsEnhanced() {
410 // Draw springs with enhanced visuals and particles
411 drawSpringsEnhanced();
412
413 // Draw paddles with glow effects
414 drawPaddlesWithGlow();
415
416 // Draw support points with input feedback
417 drawSupportPointsEnhanced();
418 }
419
420 function drawSpringsEnhanced() {
421 // Left spring
422 let leftSupportPos = leftSupport.position;
423 let leftPaddlePos = leftPaddle.position;
424 let leftCompression = drawSpringLineEnhanced(leftSupportPos, leftPaddlePos);
425 createSpringParticles(leftPaddlePos, leftCompression);
426
427 // Right spring
428 let rightSupportPos = rightSupport.position;
429 let rightPaddlePos = rightPaddle.position;
430 let rightCompression = drawSpringLineEnhanced(rightSupportPos, rightPaddlePos);
431 createSpringParticles(rightPaddlePos, rightCompression);
432 }
433
434 function drawSpringLineEnhanced(startPos, endPos) {
435 let segments = 12; // More segments for smoother springs
436 let amplitude = 10; // Bigger amplitude for more dramatic effect
437
438 // Calculate spring compression for visual effects
439 let currentLength = dist(startPos.x, startPos.y, endPos.x, endPos.y);
440 let compression = SPRING_LENGTH / currentLength;
441 amplitude *= compression;
442
443 // Enhanced spring glow based on compression
444 let glowIntensity = 150 + compression * SPRING_GLOW_INTENSITY;
445 stroke(0, 255, 136, glowIntensity);
446 strokeWeight(3 + compression * 2); // Thicker when compressed
447
448 // Draw spring coil with smooth curves
449 beginShape();
450 noFill();
451
452 for (let i = 0; i <= segments; i++) {
453 let t = i / segments;
454 let x = lerp(startPos.x, endPos.x, t);
455 let y = lerp(startPos.y, endPos.y, t);
456
457 // Enhanced zigzag with smoother curves
458 if (i > 0 && i < segments) {
459 let perpX = -(endPos.y - startPos.y) / currentLength;
460 let perpY = (endPos.x - startPos.x) / currentLength;
461 let offset = sin(i * PI * 1.5) * amplitude; // More dramatic oscillation
462 x += perpX * offset;
463 y += perpY * offset;
464 }
465
466 vertex(x, y);
467 }
468
469 endShape();
470
471 // Add spring glow effect with pulsing
472 let pulse = sin(frameCount * 0.1) * 0.2 + 1;
473 stroke(0, 255, 136, glowIntensity * 0.4 * pulse);
474 strokeWeight(8 + compression * 3);
475 beginShape();
476 noFill();
477
478 for (let i = 0; i <= segments; i++) {
479 let t = i / segments;
480 let x = lerp(startPos.x, endPos.x, t);
481 let y = lerp(startPos.y, endPos.y, t);
482 vertex(x, y);
483 }
484
485 endShape();
486
487 return compression; // Return compression for particle effects
488 }
489
490 function drawPaddlesWithGlow() {
491 // Calculate ball distance for glow effects
492 let ballPos = ball.position;
493 let leftDist = dist(ballPos.x, ballPos.y, leftPaddle.position.x, leftPaddle.position.y);
494 let rightDist = dist(ballPos.x, ballPos.y, rightPaddle.position.x, rightPaddle.position.y);
495
496 // Enhanced paddle drawing
497 drawSinglePaddleEnhanced(leftPaddle, leftDist);
498 drawSinglePaddleEnhanced(rightPaddle, rightDist);
499 }
500
501 function drawSinglePaddleEnhanced(paddle, ballDistance) {
502 let pos = paddle.position;
503 let angle = paddle.angle;
504
505 // Calculate glow intensity based on ball proximity
506 let glowIntensity = map(ballDistance, 0, PADDLE_GLOW_DISTANCE, 150, 0);
507 glowIntensity = constrain(glowIntensity, 0, 150);
508
509 push();
510 translate(pos.x, pos.y);
511 rotate(angle);
512
513 // Draw enhanced glow effect first
514 if (glowIntensity > 0) {
515 fill(0, 255, 136, glowIntensity * 0.6);
516 noStroke();
517 rectMode(CENTER);
518 rect(0, 0, PADDLE_WIDTH + 12, PADDLE_HEIGHT + 12);
519
520 // Add outer glow
521 fill(0, 255, 136, glowIntensity * 0.3);
522 rect(0, 0, PADDLE_WIDTH + 20, PADDLE_HEIGHT + 20);
523 }
524
525 // Draw main paddle with enhanced visual
526 fill(0, 255, 136);
527 stroke(0, 255, 136, 220 + glowIntensity * 0.5);
528 strokeWeight(3);
529 rectMode(CENTER);
530 rect(0, 0, PADDLE_WIDTH, PADDLE_HEIGHT);
531
532 // Add core highlight
533 fill(150, 255, 200, 100);
534 noStroke();
535 rect(0, 0, PADDLE_WIDTH - 4, PADDLE_HEIGHT - 4);
536
537 pop();
538 }
539
540 function drawSupportPointsEnhanced() {
541 // Enhanced support indicators with input feedback
542 let leftActivity = Math.abs(inputBuffer.left) * 255;
543 let rightActivity = Math.abs(inputBuffer.right) * 255;
544
545 // Left support with pulsing effect
546 let leftPulse = sin(frameCount * 0.2) * 0.3 + 1;
547 fill(0, 255, 136, 100 + leftActivity * 0.6);
548 noStroke();
549 ellipse(leftSupport.position.x, leftSupport.position.y,
550 (8 + leftActivity * 0.15) * leftPulse,
551 (8 + leftActivity * 0.15) * leftPulse);
552
553 // Right support with pulsing effect
554 let rightPulse = sin(frameCount * 0.2 + PI) * 0.3 + 1;
555 fill(0, 255, 136, 100 + rightActivity * 0.6);
556 ellipse(rightSupport.position.x, rightSupport.position.y,
557 (8 + rightActivity * 0.15) * rightPulse,
558 (8 + rightActivity * 0.15) * rightPulse);
559 }
560
561 function drawBallEnhanced() {
562 let ballPos = ball.position;
563 let ballVel = ball.velocity;
564 let speed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
565
566 // Enhanced ball with speed-based effects
567 let speedIntensity = map(speed, 0, 15, 50, 255);
568
569 // Multi-layered trail effect
570 for (let i = 0; i < 3; i++) {
571 let offset = i * 3;
572 fill(255, 100, 100, 40 - i * 10);
573 noStroke();
574 ellipse(ballPos.x - ballVel.x * offset * 0.1,
575 ballPos.y - ballVel.y * offset * 0.1,
576 BALL_RADIUS * (4 - i), BALL_RADIUS * (4 - i));
577 }
578
579 // Main ball with enhanced glow
580 fill(255, 100, 100);
581 stroke(255, 200, 200, speedIntensity);
582 strokeWeight(3 + speed * 0.15);
583 ellipse(ballPos.x, ballPos.y, BALL_RADIUS * 2, BALL_RADIUS * 2);
584
585 // Speed indicator core
586 if (speed > 8) {
587 fill(255, 255, 255, speedIntensity * 0.8);
588 noStroke();
589 ellipse(ballPos.x, ballPos.y, BALL_RADIUS * 0.8, BALL_RADIUS * 0.8);
590 }
591
592 // Outer energy ring for high speeds
593 if (speed > 12) {
594 noFill();
595 stroke(255, 255, 255, speedIntensity * 0.5);
596 strokeWeight(2);
597 ellipse(ballPos.x, ballPos.y, BALL_RADIUS * 3, BALL_RADIUS * 3);
598 }
599 }
600
601 function drawBoundaries() {
602 stroke(0, 255, 136, 30);
603 strokeWeight(1);
604 noFill();
605 line(0, 0, width, 0);
606 line(0, height, width, height);
607 }
608
609 function drawCenterLine() {
610 stroke(0, 255, 136, 50);
611 strokeWeight(2);
612
613 for (let y = 0; y < height; y += 20) {
614 line(width/2, y, width/2, y + 10);
615 }
616 }
617
618 function drawDebugInfo() {
619 fill(255, 100);
620 textAlign(LEFT);
621 textSize(12);
622 text(`FPS: ${Math.round(frameRate())}`, 10, 20);
623 text(`Ball Speed: ${Math.round(getBallSpeed())}`, 10, 35);
624 text(`Particles: ${particles.length}`, 10, 50);
625
626 // Enhanced spring info
627 let leftSpringLength = dist(leftSupport.position.x, leftSupport.position.y,
628 leftPaddle.position.x, leftPaddle.position.y);
629 let rightSpringLength = dist(rightSupport.position.x, rightSupport.position.y,
630 rightPaddle.position.x, rightPaddle.position.y);
631
632 text(`L Spring: ${Math.round(leftSpringLength)}px (${((SPRING_LENGTH/leftSpringLength - 1) * 100).toFixed(0)}%)`, 10, 65);
633 text(`R Spring: ${Math.round(rightSpringLength)}px (${((SPRING_LENGTH/rightSpringLength - 1) * 100).toFixed(0)}%)`, 10, 80);
634 text(`Input: L=${inputBuffer.left.toFixed(2)} R=${inputBuffer.right.toFixed(2)}`, 10, 95);
635
636 // Mouse/touch input debug
637 if (mouseInput.active) {
638 text(`Mouse: ${mouseInput.active ? 'Active' : 'Inactive'} | Side: ${mouseX < width/2 ? 'Left' : 'Right'}`, 10, 110);
639 text(`Mouse Y: ${mouseY} | Dead Zone: ${mouseInput.deadZone}px`, 10, 125);
640 }
641 }
642
643 function drawStartMessage() {
644 fill(0, 255, 136, 200);
645 textAlign(CENTER);
646 textSize(20);
647 text("Press any key to start!", width/2, height/2 + 100);
648 textSize(14);
649 text("Keyboard: W/S + ↑/↓ | Mouse/Touch: Drag paddles", width/2, height/2 + 125);
650 textSize(12);
651 text("(Mouse movement has deliberate lag to preserve challenge!)", width/2, height/2 + 145);
652 }
653
654 function resetBall() {
655 if (ball) {
656 World.remove(world, ball);
657 }
658
659 // Create new ball at center
660 ball = Bodies.circle(width/2, height/2, BALL_RADIUS, {
661 restitution: 1,
662 friction: 0,
663 frictionAir: 0
664 });
665
666 if (world) {
667 World.add(world, ball);
668 }
669
670 // Start ball moving after a short delay
671 setTimeout(() => {
672 let direction = random() > 0.5 ? 1 : -1;
673 let angle = random(-PI/6, PI/6);
674
675 Body.setVelocity(ball, {
676 x: direction * BALL_SPEED * cos(angle),
677 y: BALL_SPEED * sin(angle)
678 });
679
680 gameStarted = true;
681 }, 1000);
682 }
683
684 function checkBallPosition() {
685 let ballX = ball.position.x;
686
687 if (ballX < -BALL_RADIUS) {
688 rightScore++;
689 updateScore();
690 resetBall();
691 gameStarted = false;
692 }
693
694 if (ballX > width + BALL_RADIUS) {
695 leftScore++;
696 updateScore();
697 resetBall();
698 gameStarted = false;
699 }
700 }
701
702 function updateScore() {
703 document.getElementById('leftScore').textContent = leftScore;
704 document.getElementById('rightScore').textContent = rightScore;
705 }
706
707 function getBallSpeed() {
708 let velocity = ball.velocity;
709 return Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
710 }
711
712 // Input handling
713 function keyPressed() {
714 keys[key] = true;
715 keys[keyCode] = true;
716
717 if (!gameStarted && key !== ' ') {
718 gameStarted = true;
719 }
720
721 // Reset game with spacebar
722 if (key === ' ') {
723 leftScore = 0;
724 rightScore = 0;
725 updateScore();
726 resetBall();
727 gameStarted = false;
728
729 // Reset input buffers
730 inputBuffer.left = 0;
731 inputBuffer.right = 0;
732
733 // Reset mouse input
734 mouseInput.active = false;
735
736 // Clear particles
737 particles = [];
738
739 console.log("🔄 Game reset!");
740 }
741 }
742
743 function keyReleased() {
744 keys[key] = false;
745 keys[keyCode] = false;
746 }
747
748 // Mouse/touch input handlers
749 function mousePressed() {
750 // Start mouse/touch input when clicking in game area
751 if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
752 mouseInput.active = true;
753
754 // Start game if not started
755 if (!gameStarted) {
756 gameStarted = true;
757 }
758
759 return false; // Prevent default behavior
760 }
761 }
762
763 function mouseDragged() {
764 // Continue mouse/touch input while dragging
765 if (mouseInput.active) {
766 return false; // Prevent default behavior
767 }
768 }
769
770 function mouseReleased() {
771 // Stop mouse/touch input when releasing
772 mouseInput.active = false;
773
774 // Gradually reduce input buffer when mouse is released
775 inputBuffer.left *= 0.8;
776 inputBuffer.right *= 0.8;
777 }
778
779 function touchStarted() {
780 // Handle touch events same as mouse
781 return mousePressed();
782 }
783
784 function touchMoved() {
785 // Handle touch drag same as mouse
786 return mouseDragged();
787 }
788
789 function touchEnded() {
790 // Handle touch end same as mouse
791 mouseReleased();
792 return false;
793 }