JavaScript · 14132 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 // Canvas settings
29 const CANVAS_WIDTH = 800;
30 const CANVAS_HEIGHT = 400;
31
32 // Game constants
33 const BALL_SPEED = 8;
34 const BALL_RADIUS = 12;
35 const PADDLE_WIDTH = 20;
36 const PADDLE_HEIGHT = 80;
37
38 // Enhanced movement constants
39 const SUPPORT_SPEED = 4.5;
40 const SUPPORT_ACCEL = 0.8;
41 const INPUT_SMOOTHING = 0.15;
42 const SUPPORT_MAX_SPEED = 6;
43
44 // Spring physics constants
45 const PADDLE_MASS = 0.8;
46 const SPRING_LENGTH = 40;
47 const SPRING_DAMPING = 0.8;
48 const SPRING_STIFFNESS = 0.02;
49
50 // Visual enhancement constants
51 const TRAIL_SEGMENTS = 8;
52 const PADDLE_GLOW_DISTANCE = 25;
53 const SPRING_GLOW_INTENSITY = 80;
54
55 function setup() {
56 // Create p5.js canvas
57 let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
58 canvas.parent('gameCanvas');
59
60 // Initialize Matter.js physics engine
61 engine = Engine.create();
62 world = engine.world;
63
64 // Disable gravity for classic Pong feel
65 engine.world.gravity.y = 0;
66 engine.world.gravity.x = 0;
67
68 // Create game boundaries (top and bottom walls)
69 let topWall = Bodies.rectangle(width/2, -10, width, 20, { isStatic: true });
70 let bottomWall = Bodies.rectangle(width/2, height + 10, width, 20, { isStatic: true });
71 boundaries.push(topWall, bottomWall);
72
73 // Create spring paddle systems
74 createSpringPaddleSystem('left');
75 createSpringPaddleSystem('right');
76
77 // Create ball
78 resetBall();
79
80 // Add everything to the world
81 World.add(world, [
82 ...boundaries,
83 ball,
84 leftSupport, leftPaddle, leftSpring,
85 rightSupport, rightPaddle, rightSpring
86 ]);
87 }
88
89 function createSpringPaddleSystem(side) {
90 let supportX = side === 'left' ? 60 : width - 60;
91 let paddleX = side === 'left' ? 60 + SPRING_LENGTH : width - 60 - SPRING_LENGTH;
92 let startY = height / 2;
93
94 if (side === 'left') {
95 // Left support (invisible anchor point controlled by player)
96 leftSupport = Bodies.rectangle(supportX, startY, 10, 10, {
97 isStatic: true,
98 render: { visible: false }
99 });
100
101 // Left paddle (the actual hitting surface)
102 leftPaddle = Bodies.rectangle(paddleX, startY, PADDLE_WIDTH, PADDLE_HEIGHT, {
103 mass: PADDLE_MASS,
104 restitution: 1.2,
105 friction: 0,
106 frictionAir: 0.01
107 });
108
109 // Spring constraint connecting support to paddle
110 leftSpring = Constraint.create({
111 bodyA: leftSupport,
112 bodyB: leftPaddle,
113 length: SPRING_LENGTH,
114 stiffness: SPRING_STIFFNESS,
115 damping: SPRING_DAMPING
116 });
117 } else {
118 // Right support (invisible anchor point controlled by player/AI)
119 rightSupport = Bodies.rectangle(supportX, startY, 10, 10, {
120 isStatic: true,
121 render: { visible: false }
122 });
123
124 // Right paddle (the actual hitting surface)
125 rightPaddle = Bodies.rectangle(paddleX, startY, PADDLE_WIDTH, PADDLE_HEIGHT, {
126 mass: PADDLE_MASS,
127 restitution: 1.2,
128 friction: 0,
129 frictionAir: 0.01
130 });
131
132 // Spring constraint connecting support to paddle
133 rightSpring = Constraint.create({
134 bodyA: rightSupport,
135 bodyB: rightPaddle,
136 length: SPRING_LENGTH,
137 stiffness: SPRING_STIFFNESS,
138 damping: SPRING_DAMPING
139 });
140 }
141 }
142
143 function draw() {
144 // Update physics
145 Engine.update(engine);
146
147 // Handle enhanced player input
148 handleEnhancedInput();
149
150 // Check for scoring
151 checkBallPosition();
152
153 // Clear canvas
154 background(10, 10, 10);
155
156 // Draw game objects with enhanced visuals
157 drawSpringPaddleSystemsEnhanced();
158 drawBallEnhanced();
159 drawBoundaries();
160 drawCenterLine();
161
162 // Draw debug info
163 drawDebugInfo();
164
165 // Start message
166 if (!gameStarted) {
167 drawStartMessage();
168 }
169 }
170
171 function handleEnhancedInput() {
172 // Smooth input accumulation with acceleration
173 let leftInput = 0;
174 let rightInput = 0;
175
176 // Left paddle input (W/S keys)
177 if (keys['w'] || keys['W']) leftInput -= 1;
178 if (keys['s'] || keys['S']) leftInput += 1;
179
180 // Right paddle input (Arrow keys)
181 if (keys['ArrowUp']) rightInput -= 1;
182 if (keys['ArrowDown']) rightInput += 1;
183
184 // Apply acceleration and smoothing
185 inputBuffer.left = lerp(inputBuffer.left, leftInput, INPUT_SMOOTHING);
186 inputBuffer.right = lerp(inputBuffer.right, rightInput, INPUT_SMOOTHING);
187
188 // Move supports with enhanced physics
189 if (Math.abs(inputBuffer.left) > 0.01) {
190 moveSupportEnhanced(leftSupport, inputBuffer.left * SUPPORT_SPEED);
191 }
192 if (Math.abs(inputBuffer.right) > 0.01) {
193 moveSupportEnhanced(rightSupport, inputBuffer.right * SUPPORT_SPEED);
194 }
195 }
196
197 function moveSupportEnhanced(support, deltaY) {
198 let newY = support.position.y + deltaY;
199
200 // Keep support within reasonable bounds with smooth clamping
201 let minY = 50;
202 let maxY = height - 50;
203
204 if (newY < minY) {
205 newY = minY + (newY - minY) * 0.1; // Soft boundary
206 } else if (newY > maxY) {
207 newY = maxY + (newY - maxY) * 0.1; // Soft boundary
208 }
209
210 Body.setPosition(support, { x: support.position.x, y: newY });
211 }
212
213 function drawSpringPaddleSystemsEnhanced() {
214 // Draw springs with enhanced visuals
215 drawSpringsEnhanced();
216
217 // Draw paddles with glow effects
218 drawPaddlesWithGlow();
219
220 // Draw support points with input feedback
221 drawSupportPointsEnhanced();
222 }
223
224 function drawSpringsEnhanced() {
225 // Left spring
226 let leftSupportPos = leftSupport.position;
227 let leftPaddlePos = leftPaddle.position;
228 drawSpringLineEnhanced(leftSupportPos, leftPaddlePos);
229
230 // Right spring
231 let rightSupportPos = rightSupport.position;
232 let rightPaddlePos = rightPaddle.position;
233 drawSpringLineEnhanced(rightSupportPos, rightPaddlePos);
234 }
235
236 function drawSpringLineEnhanced(startPos, endPos) {
237 let segments = 10;
238 let amplitude = 8;
239
240 // Calculate spring compression for visual effects
241 let currentLength = dist(startPos.x, startPos.y, endPos.x, endPos.y);
242 let compression = SPRING_LENGTH / currentLength;
243 amplitude *= compression;
244
245 // Enhanced spring glow based on compression
246 let glowIntensity = 150 + compression * SPRING_GLOW_INTENSITY;
247 stroke(0, 255, 136, glowIntensity);
248 strokeWeight(2 + compression * 1.5);
249
250 // Draw spring coil with smooth curves
251 beginShape();
252 noFill();
253
254 for (let i = 0; i <= segments; i++) {
255 let t = i / segments;
256 let x = lerp(startPos.x, endPos.x, t);
257 let y = lerp(startPos.y, endPos.y, t);
258
259 // Enhanced zigzag with smoother curves
260 if (i > 0 && i < segments) {
261 let perpX = -(endPos.y - startPos.y) / currentLength;
262 let perpY = (endPos.x - startPos.x) / currentLength;
263 let offset = sin(i * PI * 1.2) * amplitude;
264 x += perpX * offset;
265 y += perpY * offset;
266 }
267
268 vertex(x, y);
269 }
270
271 endShape();
272
273 // Add spring glow effect
274 stroke(0, 255, 136, glowIntensity * 0.3);
275 strokeWeight(6 + compression * 2);
276 beginShape();
277 noFill();
278
279 for (let i = 0; i <= segments; i++) {
280 let t = i / segments;
281 let x = lerp(startPos.x, endPos.x, t);
282 let y = lerp(startPos.y, endPos.y, t);
283 vertex(x, y);
284 }
285
286 endShape();
287 }
288
289 function drawPaddlesWithGlow() {
290 // Calculate ball distance for glow effects
291 let ballPos = ball.position;
292 let leftDist = dist(ballPos.x, ballPos.y, leftPaddle.position.x, leftPaddle.position.y);
293 let rightDist = dist(ballPos.x, ballPos.y, rightPaddle.position.x, rightPaddle.position.y);
294
295 // Enhanced paddle drawing
296 drawSinglePaddleEnhanced(leftPaddle, leftDist);
297 drawSinglePaddleEnhanced(rightPaddle, rightDist);
298 }
299
300 function drawSinglePaddleEnhanced(paddle, ballDistance) {
301 let pos = paddle.position;
302 let angle = paddle.angle;
303
304 // Calculate glow intensity based on ball proximity
305 let glowIntensity = map(ballDistance, 0, PADDLE_GLOW_DISTANCE, 100, 0);
306 glowIntensity = constrain(glowIntensity, 0, 100);
307
308 push();
309 translate(pos.x, pos.y);
310 rotate(angle);
311
312 // Draw glow effect first
313 if (glowIntensity > 0) {
314 fill(0, 255, 136, glowIntensity * 0.5);
315 noStroke();
316 rectMode(CENTER);
317 rect(0, 0, PADDLE_WIDTH + 8, PADDLE_HEIGHT + 8);
318 }
319
320 // Draw main paddle
321 fill(0, 255, 136);
322 stroke(0, 255, 136, 200 + glowIntensity);
323 strokeWeight(2);
324 rectMode(CENTER);
325 rect(0, 0, PADDLE_WIDTH, PADDLE_HEIGHT);
326
327 pop();
328 }
329
330 function drawSupportPointsEnhanced() {
331 // Enhanced support indicators with input feedback
332 let leftActivity = Math.abs(inputBuffer.left) * 255;
333 let rightActivity = Math.abs(inputBuffer.right) * 255;
334
335 // Left support
336 fill(0, 255, 136, 100 + leftActivity * 0.5);
337 noStroke();
338 ellipse(leftSupport.position.x, leftSupport.position.y, 8 + leftActivity * 0.1, 8 + leftActivity * 0.1);
339
340 // Right support
341 fill(0, 255, 136, 100 + rightActivity * 0.5);
342 ellipse(rightSupport.position.x, rightSupport.position.y, 8 + rightActivity * 0.1, 8 + rightActivity * 0.1);
343 }
344
345 function drawBallEnhanced() {
346 let ballPos = ball.position;
347 let ballVel = ball.velocity;
348 let speed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
349
350 // Enhanced ball with speed-based effects
351 let speedIntensity = map(speed, 0, 15, 50, 255);
352
353 // Ball trail effect
354 fill(255, 100, 100, 30);
355 noStroke();
356 ellipse(ballPos.x, ballPos.y, BALL_RADIUS * 4, BALL_RADIUS * 4);
357
358 // Main ball
359 fill(255, 100, 100);
360 stroke(255, 150, 150, speedIntensity);
361 strokeWeight(2 + speed * 0.1);
362 ellipse(ballPos.x, ballPos.y, BALL_RADIUS * 2, BALL_RADIUS * 2);
363
364 // Speed indicator
365 if (speed > 10) {
366 fill(255, 255, 255, speedIntensity * 0.5);
367 noStroke();
368 ellipse(ballPos.x, ballPos.y, BALL_RADIUS, BALL_RADIUS);
369 }
370 }
371
372 function drawBoundaries() {
373 stroke(0, 255, 136, 30);
374 strokeWeight(1);
375 noFill();
376 line(0, 0, width, 0);
377 line(0, height, width, height);
378 }
379
380 function drawCenterLine() {
381 stroke(0, 255, 136, 50);
382 strokeWeight(2);
383
384 for (let y = 0; y < height; y += 20) {
385 line(width/2, y, width/2, y + 10);
386 }
387 }
388
389 function drawDebugInfo() {
390 fill(255, 100);
391 textAlign(LEFT);
392 textSize(12);
393 text(`FPS: ${Math.round(frameRate())}`, 10, 20);
394 text(`Ball Speed: ${Math.round(getBallSpeed())}`, 10, 35);
395
396 // Enhanced spring info
397 let leftSpringLength = dist(leftSupport.position.x, leftSupport.position.y,
398 leftPaddle.position.x, leftPaddle.position.y);
399 let rightSpringLength = dist(rightSupport.position.x, rightSupport.position.y,
400 rightPaddle.position.x, rightPaddle.position.y);
401
402 text(`Left Spring: ${Math.round(leftSpringLength)}px`, 10, 50);
403 text(`Right Spring: ${Math.round(rightSpringLength)}px`, 10, 65);
404 text(`Input Buffer: L=${inputBuffer.left.toFixed(2)} R=${inputBuffer.right.toFixed(2)}`, 10, 80);
405 }
406
407 function drawStartMessage() {
408 fill(0, 255, 136, 200);
409 textAlign(CENTER);
410 textSize(20);
411 text("Press any key to start!", width/2, height/2 + 100);
412 textSize(14);
413 text("Enhanced controls with smooth acceleration!", width/2, height/2 + 125);
414 }
415
416 function resetBall() {
417 if (ball) {
418 World.remove(world, ball);
419 }
420
421 // Create new ball at center
422 ball = Bodies.circle(width/2, height/2, BALL_RADIUS, {
423 restitution: 1,
424 friction: 0,
425 frictionAir: 0
426 });
427
428 if (world) {
429 World.add(world, ball);
430 }
431
432 // Start ball moving after a short delay
433 setTimeout(() => {
434 let direction = random() > 0.5 ? 1 : -1;
435 let angle = random(-PI/6, PI/6);
436
437 Body.setVelocity(ball, {
438 x: direction * BALL_SPEED * cos(angle),
439 y: BALL_SPEED * sin(angle)
440 });
441
442 gameStarted = true;
443 }, 1000);
444 }
445
446 function checkBallPosition() {
447 let ballX = ball.position.x;
448
449 if (ballX < -BALL_RADIUS) {
450 rightScore++;
451 updateScore();
452 resetBall();
453 gameStarted = false;
454 }
455
456 if (ballX > width + BALL_RADIUS) {
457 leftScore++;
458 updateScore();
459 resetBall();
460 gameStarted = false;
461 }
462 }
463
464 function updateScore() {
465 document.getElementById('leftScore').textContent = leftScore;
466 document.getElementById('rightScore').textContent = rightScore;
467 }
468
469 function getBallSpeed() {
470 let velocity = ball.velocity;
471 return Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
472 }
473
474 // Input handling
475 function keyPressed() {
476 keys[key] = true;
477 keys[keyCode] = true;
478
479 if (!gameStarted && key !== ' ') {
480 gameStarted = true;
481 }
482
483 // Reset game with spacebar
484 if (key === ' ') {
485 leftScore = 0;
486 rightScore = 0;
487 updateScore();
488 resetBall();
489 gameStarted = false;
490
491 // Reset input buffers
492 inputBuffer.left = 0;
493 inputBuffer.right = 0;
494
495 console.log("🔄 Game reset!");
496 }
497 }
498
499 function keyReleased() {
500 keys[key] = false;
501 keys[keyCode] = false;
502 }