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