zeroed-some/sprong / 7f416a3

Browse files

BOPPPP

Authored by espadonne
SHA
7f416a334ca10a80dce2e938b1d3722a6966f60a
Parents
420b0f7
Tree
005b51a

1 changed file

StatusFile+-
M sprong.js 436 75
sprong.jsmodified
@@ -6,23 +6,73 @@ const Bodies = Matter.Bodies;
6
 const Render = Matter.Render;
6
 const Render = Matter.Render;
7
 const Constraint = Matter.Constraint;
7
 const Constraint = Matter.Constraint;
8
 
8
 
9
+// Canvas settings
10
+const CANVAS_WIDTH  = 800;
11
+const CANVAS_HEIGHT = 400;
12
+
13
+// Game constants
14
+const BALL_SPEED    = 6;
15
+const BALL_RADIUS   = 12;
16
+const PADDLE_WIDTH  = 20;
17
+const PADDLE_HEIGHT = 80;
18
+
19
+// Enhanced movement constants (tuned for faster response)
20
+const SUPPORT_SPEED     = 6.5;  // Bumped up
21
+const SUPPORT_ACCEL     = 1.2;  // Increased acceleration
22
+const INPUT_SMOOTHING   = 0.25; // More responsive
23
+const SUPPORT_MAX_SPEED = 8;    // Higher max speed
24
+
25
+// Touch/mouse control constants
26
+const MOUSE_SPEED_LIMIT = 4;    // Max speed for mouse movement
27
+const MOUSE_LAG_FACTOR  = 0.12; // How much lag in mouse following
28
+const TOUCH_SENSITIVITY = 1.2;  // Touch movement multiplier
29
+
30
+// Spring physics constants (using your current settings)
31
+const PADDLE_MASS       = 0.8;  
32
+const SPRING_LENGTH     = 50;   
33
+const SPRING_DAMPING    = 0.6;  
34
+const SPRING_STIFFNESS  = 0.025;
35
+
36
+// Visual enhancement constants
37
+const TRAIL_SEGMENTS        = 8;
38
+const PADDLE_GLOW_DISTANCE  = 25;
39
+const SPRING_GLOW_INTENSITY = 120; // More intense glow
40
+
41
+// Particle system constants
42
+const MAX_PARTICLES         = 100;
43
+const PARTICLE_LIFE         = 60;
44
+const IMPACT_PARTICLES      = 8;
45
+const SPRING_PARTICLE_RATE  = 0.3;
46
+
47
+// Bop system constants
48
+const BOP_FORCE             = 1.0;
49
+const BOP_DURATION          = 300;
50
+const BOP_COOLDOWN          = 500;
51
+const ANCHOR_RECOIL         = 40;     // How far the anchor moves backward during bop
52
+const BOP_RANGE             = 600;    // How far the paddle can thrust forward
53
+const BOP_VELOCITY_BOOST    = 12;     // Initial velocity boost for paddle
54
+
9
 // Game variables
55
 // Game variables
10
 let ball;
56
 let ball;
11
 let world;
57
 let world;
12
 let engine;
58
 let engine;
13
 
59
 
60
+// Particle systems
61
+let particles = [];
62
+let impactParticles = [];
63
+
14
 // Spring paddle system components
64
 // Spring paddle system components
15
 let boundaries = [];
65
 let boundaries = [];
16
 let leftSupport, leftPaddle, leftSpring;
66
 let leftSupport, leftPaddle, leftSpring;
17
 let rightSupport, rightPaddle, rightSpring;
67
 let rightSupport, rightPaddle, rightSpring;
18
 
68
 
19
 // Game state
69
 // Game state
20
-let leftScore = 0;
70
+let leftScore  = 0;
21
 let rightScore = 0;
71
 let rightScore = 0;
22
-let gameStarted = false;
23
-let gameState = 'menu'; // 'menu', 'playing', 'paused'
24
-let gameMode = 'vs-cpu'; // 'vs-cpu' or 'vs-human'
25
 let aiEnabled = true;
72
 let aiEnabled = true;
73
+let gameState = 'menu';   // 'menu', 'playing', 'paused'
74
+let gameMode  = 'vs-cpu'; // 'vs-cpu' or 'vs-human'
75
+let gameStarted = false;
26
 
76
 
27
 // Menu state
77
 // Menu state
28
 let menuState = {
78
 let menuState = {
@@ -59,13 +109,229 @@ let aiState = {
59
     idleTarget: 200,      // Where AI "wants" to be when idle
109
     idleTarget: 200,      // Where AI "wants" to be when idle
60
     microAdjustment: 0,   // Small random movements
110
     microAdjustment: 0,   // Small random movements
61
     breathingOffset: 0,   // Subtle breathing-like motion
111
     breathingOffset: 0,   // Subtle breathing-like motion
62
-    lastMicroTime: 0      // For micro-movement timing
112
+    lastMicroTime: 0,     // For micro-movement timing
113
+    
114
+    // AI Bop system
115
+    consideringBop: false,  // Is AI thinking about bopping?
116
+    bopDecisionTime: 0,     // When AI decided to bop
117
+    bopTiming: 200          // How long before ball contact to bop (ms)
63
 };
118
 };
64
 
119
 
65
 // Player input
120
 // Player input
66
 let keys = {};
121
 let keys = {};
67
 let inputBuffer = { left: 0, right: 0 };
122
 let inputBuffer = { left: 0, right: 0 };
68
 
123
 
124
+// Bop system
125
+let bopState = {
126
+    left: {
127
+        active: false,
128
+        startTime: 0,
129
+        duration: BOP_DURATION,
130
+        power: BOP_FORCE,
131
+        cooldown: BOP_COOLDOWN,
132
+        lastBopTime: 0,
133
+        originalPos: null
134
+    },
135
+    right: {
136
+        active: false,
137
+        startTime: 0,
138
+        duration: BOP_DURATION,
139
+        power: BOP_FORCE,
140
+        cooldown: BOP_COOLDOWN,
141
+        lastBopTime: 0,
142
+        originalPos: null
143
+    }
144
+};
145
+
146
+function handleBopInput() {
147
+    let currentTime = millis();
148
+    
149
+    // Left player bop - use Left Shift for both modes
150
+    let leftBopPressed = keys['Shift'] && !keys['Control']; // Left shift (without Ctrl)
151
+    
152
+    if (leftBopPressed && !bopState.left.active && 
153
+        currentTime - bopState.left.lastBopTime > bopState.left.cooldown) {
154
+        activateBop('left', currentTime);
155
+    }
156
+    
157
+    // Right player bop (Enter - only in two player mode)
158
+    if (!aiEnabled) {
159
+        let rightBopPressed = keys['Enter'];
160
+        
161
+        if (rightBopPressed && !bopState.right.active && 
162
+            currentTime - bopState.right.lastBopTime > bopState.right.cooldown) {
163
+            activateBop('right', currentTime);
164
+        }
165
+    }
166
+    
167
+    // Update active bops
168
+    updateBopStates(currentTime);
169
+}
170
+
171
+function activateBop(side, currentTime) {
172
+    bopState[side].active = true;
173
+    bopState[side].startTime = currentTime;
174
+    bopState[side].lastBopTime = currentTime;
175
+    
176
+    // Get the relevant bodies
177
+    let paddle = side === 'left' ? leftPaddle : rightPaddle;
178
+    let support = side === 'left' ? leftSupport : rightSupport;
179
+    
180
+    // Calculate direction from support to paddle (this is the bop direction)
181
+    let dx = paddle.position.x - support.position.x;
182
+    let dy = paddle.position.y - support.position.y;
183
+    
184
+    // Normalize direction
185
+    let magnitude = Math.sqrt(dx * dx + dy * dy);
186
+    if (magnitude > 0) {
187
+        dx /= magnitude;
188
+        dy /= magnitude;
189
+        
190
+        // Calculate anchor recoil distance
191
+        let anchorRecoilDistance = ANCHOR_RECOIL * 0.4;
192
+        
193
+        // Move the support BACKWARD (recoil effect)
194
+        let newSupportX = support.position.x - dx * anchorRecoilDistance;
195
+        let newSupportY = support.position.y - dy * anchorRecoilDistance;
196
+        
197
+        // Apply the support movement
198
+        Body.setPosition(support, { x: newSupportX, y: newSupportY });
199
+        
200
+        // Store original support position for recovery
201
+        bopState[side].originalPos = { 
202
+            x: support.position.x + dx * anchorRecoilDistance, 
203
+            y: support.position.y + dy * anchorRecoilDistance 
204
+        };
205
+        
206
+        // IMPORTANT: Set paddle velocity directly for immediate forward thrust
207
+        // This creates the "shooting forward" effect based on BOP_RANGE
208
+        let forwardSpeed = (BOP_RANGE / SPRING_LENGTH) * BOP_VELOCITY_BOOST;
209
+        Body.setVelocity(paddle, {
210
+            x: paddle.velocity.x + dx * forwardSpeed,
211
+            y: paddle.velocity.y + dy * forwardSpeed
212
+        });
213
+        
214
+        // Also apply a strong forward force for continued acceleration
215
+        Body.applyForce(paddle, paddle.position, {
216
+            x: dx * bopState[side].power * BOP_RANGE * 0.1,
217
+            y: dy * bopState[side].power * BOP_RANGE * 0.1
218
+        });
219
+        
220
+        // Create particle burst for visual feedback
221
+        for (let i = 0; i < 5; i++) {
222
+            let angle = Math.atan2(dy, dx) + (Math.random() - 0.5) * 0.5;
223
+            let speed = Math.random() * 4 + 2;
224
+            particles.push({
225
+                x: support.position.x,
226
+                y: support.position.y,
227
+                vx: Math.cos(angle) * speed * -1, // Particles go backward
228
+                vy: Math.sin(angle) * speed * -1,
229
+                size: Math.random() * 3 + 2,
230
+                life: 30,
231
+                maxLife: 30,
232
+                color: { r: 255, g: 255, b: 100 },
233
+                type: 'impact'
234
+            });
235
+        }
236
+        
237
+        // Force collision detection update
238
+        Engine.update(engine, 0);
239
+    }
240
+    
241
+    console.log(side + " player BOP!");
242
+}
243
+
244
+function updateBopStates(currentTime) {
245
+    // Update left bop
246
+    if (bopState.left.active) {
247
+        let elapsed = currentTime - bopState.left.startTime;
248
+        let progress = elapsed / bopState.left.duration;
249
+        
250
+        if (progress >= 1.0) {
251
+            // Bop finished
252
+            bopState.left.active = false;
253
+            bopState.left.originalPos = null;
254
+        } else {
255
+            // Smoothly return support to original position
256
+            if (bopState.left.originalPos) {
257
+                let support = leftSupport;
258
+                let currentX = support.position.x;
259
+                let currentY = support.position.y;
260
+                
261
+                // Ease back to original position
262
+                let returnSpeed = 0.15 * (1 - Math.pow(1 - progress, 3)); // Ease out cubic
263
+                let newX = currentX + (bopState.left.originalPos.x - currentX) * returnSpeed;
264
+                let newY = currentY + (bopState.left.originalPos.y - currentY) * returnSpeed;
265
+                
266
+                Body.setPosition(support, { x: newX, y: newY });
267
+            }
268
+            
269
+            // Apply range limiting during active bop
270
+            limitBopRange(leftSupport, leftPaddle);
271
+        }
272
+    }
273
+    
274
+    // Update right bop
275
+    if (bopState.right.active) {
276
+        let elapsed = currentTime - bopState.right.startTime;
277
+        let progress = elapsed / bopState.right.duration;
278
+        
279
+        if (progress >= 1.0) {
280
+            // Bop finished
281
+            bopState.right.active = false;
282
+            bopState.right.originalPos = null;
283
+        } else {
284
+            // Smoothly return support to original position
285
+            if (bopState.right.originalPos) {
286
+                let support = rightSupport;
287
+                let currentX = support.position.x;
288
+                let currentY = support.position.y;
289
+                
290
+                // Ease back to original position
291
+                let returnSpeed = 0.15 * (1 - Math.pow(1 - progress, 3)); // Ease out cubic
292
+                let newX = currentX + (bopState.right.originalPos.x - currentX) * returnSpeed;
293
+                let newY = currentY + (bopState.right.originalPos.y - currentY) * returnSpeed;
294
+                
295
+                Body.setPosition(support, { x: newX, y: newY });
296
+            }
297
+            
298
+            // Apply range limiting during active bop
299
+            limitBopRange(rightSupport, rightPaddle);
300
+        }
301
+    }
302
+}
303
+
304
+function limitBopRange(support, paddle) {
305
+    // Calculate current distance
306
+    let currentDistance = dist(support.position.x, support.position.y,
307
+                              paddle.position.x, paddle.position.y);
308
+    
309
+    // If paddle is beyond max range (spring length + bop range), pull it back
310
+    let maxDistance = SPRING_LENGTH + BOP_RANGE;
311
+    if (currentDistance > maxDistance) {
312
+        // Calculate direction from support to paddle
313
+        let dx = paddle.position.x - support.position.x;
314
+        let dy = paddle.position.y - support.position.y;
315
+        
316
+        // Normalize
317
+        let magnitude = Math.sqrt(dx * dx + dy * dy);
318
+        dx /= magnitude;
319
+        dy /= magnitude;
320
+        
321
+        // Set paddle position at max distance
322
+        let newX = support.position.x + dx * maxDistance;
323
+        let newY = support.position.y + dy * maxDistance;
324
+        
325
+        // Preserve some velocity but dampen it
326
+        let currentVel = paddle.velocity;
327
+        Body.setPosition(paddle, { x: newX, y: newY });
328
+        Body.setVelocity(paddle, { 
329
+            x: currentVel.x * 0.7, 
330
+            y: currentVel.y * 0.7 
331
+        });
332
+    }
333
+}
334
+
69
 // Touch/mouse input
335
 // Touch/mouse input
70
 let mouseInput = {
336
 let mouseInput = {
71
     active: false,
337
     active: false,
@@ -76,43 +342,13 @@ let mouseInput = {
76
     deadZone: 15      // Minimum distance before movement starts
342
     deadZone: 15      // Minimum distance before movement starts
77
 };
343
 };
78
 
344
 
79
-// Particle systems
80
-let particles = [];
81
-let impactParticles = [];
82
-
83
-// Canvas settings
84
-const CANVAS_WIDTH  = 800;
85
-const CANVAS_HEIGHT = 400;
86
-
87
-// Game constants
88
-const BALL_SPEED    = 6;
89
-const BALL_RADIUS   = 12;
90
-const PADDLE_WIDTH  = 20;
91
-const PADDLE_HEIGHT = 80;
92
-
93
-// Enhanced movement constants (tuned for faster response)
94
-const SUPPORT_SPEED     = 6.5;    // Bumped up from 4.5
95
-const SUPPORT_ACCEL     = 1.2;    // Increased acceleration
96
-const INPUT_SMOOTHING   = 0.25;   // More responsive
97
-const SUPPORT_MAX_SPEED = 8;      // Higher max speed
98
-
99
-// Touch/mouse control constants
100
-const MOUSE_LAG_FACTOR  = 0.12;   // How much lag in mouse following
101
-const MOUSE_SPEED_LIMIT = 4;      // Max speed for mouse movement
102
-const TOUCH_SENSITIVITY = 1.2;    // Touch movement multiplier
103
-
104
-// Spring physics constants (adjusted for better oscillation)
105
-const PADDLE_MASS       = 0.8;    // Back to more stable value
106
-const SPRING_LENGTH     = 50;     // Increased from 40 for more room
107
-const SPRING_DAMPING    = 0.6;    // More damping = less erratic
108
-const SPRING_STIFFNESS  = 0.025;  // Slightly lower for smoother motion
109
 
345
 
110
 // AI difficulty settings
346
 // AI difficulty settings
111
 const AI_SETTINGS = {
347
 const AI_SETTINGS = {
112
     easy: {
348
     easy: {
113
         reactionTime: 400,    // ms delay
349
         reactionTime: 400,    // ms delay
114
         accuracy: 0.7,        // 70% accuracy
350
         accuracy: 0.7,        // 70% accuracy
115
-        speed: 0.6,           // 60% of normal speed
351
+        speed: 0.8,           // Increased from 0.6
116
         prediction: 0.3,      // 30% prediction vs reaction
352
         prediction: 0.3,      // 30% prediction vs reaction
117
         aggression: 0.2,      // Low aggression
353
         aggression: 0.2,      // Low aggression
118
         oscillation: 0.3      // Minimal oscillation
354
         oscillation: 0.3      // Minimal oscillation
@@ -120,7 +356,7 @@ const AI_SETTINGS = {
120
     medium: {
356
     medium: {
121
         reactionTime: 250,
357
         reactionTime: 250,
122
         accuracy: 0.85,
358
         accuracy: 0.85,
123
-        speed: 0.8,
359
+        speed: 1.0,           // Increased from 0.8
124
         prediction: 0.6,
360
         prediction: 0.6,
125
         aggression: 0.5,      // Moderate aggression
361
         aggression: 0.5,      // Moderate aggression
126
         oscillation: 0.7      // Good oscillation technique
362
         oscillation: 0.7      // Good oscillation technique
@@ -128,24 +364,13 @@ const AI_SETTINGS = {
128
     hard: {
364
     hard: {
129
         reactionTime: 150,
365
         reactionTime: 150,
130
         accuracy: 0.95,
366
         accuracy: 0.95,
131
-        speed: 1.0,
367
+        speed: 1.2,           // Increased from 1.0 
132
         prediction: 0.8,
368
         prediction: 0.8,
133
         aggression: 0.8,      // High aggression
369
         aggression: 0.8,      // High aggression
134
         oscillation: 1.0      // Master-level oscillation
370
         oscillation: 1.0      // Master-level oscillation
135
     }
371
     }
136
 };
372
 };
137
 
373
 
138
-// Visual enhancement constants
139
-const TRAIL_SEGMENTS        = 8;
140
-const PADDLE_GLOW_DISTANCE  = 25;
141
-const SPRING_GLOW_INTENSITY = 120; // More intense glow
142
-
143
-// Particle system constants
144
-const MAX_PARTICLES         = 100;
145
-const PARTICLE_LIFE         = 60;
146
-const IMPACT_PARTICLES      = 8;
147
-const SPRING_PARTICLE_RATE  = 0.3;
148
-
149
 function setup() {
374
 function setup() {
150
     // Create p5.js canvas
375
     // Create p5.js canvas
151
     let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
376
     let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
@@ -179,10 +404,42 @@ function setup() {
179
         rightSupport, rightPaddle, rightSpring
404
         rightSupport, rightPaddle, rightSpring
180
     ]);
405
     ]);
181
     
406
     
182
-    console.log("Sprong Phase 5 Complete!");
407
+    // Set up collision events
183
-    console.log("Particle effects system");
408
+    Matter.Events.on(engine, 'collisionStart', function(event) {
184
-    console.log("Tuned physics for maximum bounce");
409
+        let pairs = event.pairs;
185
-    console.log("Faster, more responsive paddles");
410
+        
411
+        for (let i = 0; i < pairs.length; i++) {
412
+            let pair = pairs[i];
413
+            
414
+            // Check if collision involves ball and paddle
415
+            if ((pair.bodyA === ball && (pair.bodyB === leftPaddle || pair.bodyB === rightPaddle)) ||
416
+                (pair.bodyB === ball && (pair.bodyA === leftPaddle || pair.bodyA === rightPaddle))) {
417
+                
418
+                let paddle = pair.bodyA === ball ? pair.bodyB : pair.bodyA;
419
+                let isLeftPaddle = paddle === leftPaddle;
420
+                
421
+                // Apply bop boost if paddle is currently bopping
422
+                if ((isLeftPaddle && bopState.left.active) || (!isLeftPaddle && bopState.right.active)) {
423
+                    // Get current velocities
424
+                    let ballVel = ball.velocity;
425
+                    let paddleVel = paddle.velocity;
426
+                    
427
+                    // Calculate boost based on paddle velocity
428
+                    let boostX = paddleVel.x * 0.5;
429
+                    let boostY = paddleVel.y * 0.5;
430
+                    
431
+                    // Apply extra velocity to ball
432
+                    Body.setVelocity(ball, {
433
+                        x: ballVel.x * 1.3 + boostX,
434
+                        y: ballVel.y * 1.3 + boostY
435
+                    });
436
+                    
437
+                    // Create extra impact particles
438
+                    createImpactParticles(ball.position.x, ball.position.y, ballVel.x, ballVel.y);
439
+                }
440
+            }
441
+        }
442
+    });
186
 }
443
 }
187
 
444
 
188
 function createSpringPaddleSystem(side) {
445
 function createSpringPaddleSystem(side) {
@@ -202,9 +459,20 @@ function createSpringPaddleSystem(side) {
202
             mass: PADDLE_MASS,
459
             mass: PADDLE_MASS,
203
             restitution: 1.3,  // Even bouncier!
460
             restitution: 1.3,  // Even bouncier!
204
             friction: 0,
461
             friction: 0,
205
-            frictionAir: 0.005 // Less air resistance
462
+            frictionAir: 0.005, // Less air resistance
463
+            isSensor: false,
464
+            slop: 0.01,  // Tighter collision detection
465
+            render: {
466
+                fillStyle: '#00ff88'
467
+            }
206
         });
468
         });
207
         
469
         
470
+        // Enable continuous collision detection for better bop collisions
471
+        leftPaddle.collisionFilter = {
472
+            category: 0x0002,
473
+            mask: 0xFFFF
474
+        };
475
+        
208
         // Spring constraint connecting support to paddle
476
         // Spring constraint connecting support to paddle
209
         leftSpring = Constraint.create({
477
         leftSpring = Constraint.create({
210
             bodyA: leftSupport,
478
             bodyA: leftSupport,
@@ -225,9 +493,20 @@ function createSpringPaddleSystem(side) {
225
             mass: PADDLE_MASS,
493
             mass: PADDLE_MASS,
226
             restitution: 1.2,  // Slightly toned down
494
             restitution: 1.2,  // Slightly toned down
227
             friction: 0,
495
             friction: 0,
228
-            frictionAir: 0.008 // Bit more air resistance for stability
496
+            frictionAir: 0.008, // Bit more air resistance for stability
497
+            isSensor: false,
498
+            slop: 0.01,  // Tighter collision detection
499
+            render: {
500
+                fillStyle: '#ff6464'
501
+            }
229
         });
502
         });
230
         
503
         
504
+        // Enable continuous collision detection for better bop collisions
505
+        rightPaddle.collisionFilter = {
506
+            category: 0x0004,
507
+            mask: 0xFFFF
508
+        };
509
+        
231
         // Spring constraint connecting support to paddle
510
         // Spring constraint connecting support to paddle
232
         rightSpring = Constraint.create({
511
         rightSpring = Constraint.create({
233
             bodyA: rightSupport,
512
             bodyA: rightSupport,
@@ -256,6 +535,13 @@ function draw() {
256
         updateParticles();
535
         updateParticles();
257
         checkCollisions();
536
         checkCollisions();
258
         
537
         
538
+        // Enhanced collision detection during bops - just more frequent updates
539
+        if (bopState.left.active || bopState.right.active) {
540
+            // Multiple smaller physics updates for better collision detection
541
+            Engine.update(engine, 8);
542
+            Engine.update(engine, 8);
543
+        }
544
+        
259
         // Check for scoring
545
         // Check for scoring
260
         checkBallPosition();
546
         checkBallPosition();
261
         
547
         
@@ -283,6 +569,9 @@ function handleEnhancedInput() {
283
     handleKeyboardInput();
569
     handleKeyboardInput();
284
     handleMouseTouchInput();
570
     handleMouseTouchInput();
285
     
571
     
572
+    // Handle bop mechanics
573
+    handleBopInput();
574
+    
286
     // Handle AI if enabled
575
     // Handle AI if enabled
287
     if (aiEnabled && gameStarted) {
576
     if (aiEnabled && gameStarted) {
288
         handleAI();
577
         handleAI();
@@ -448,13 +737,44 @@ function handleAITracking(currentTime, ballPos, ballVel, aiSettings) {
448
     desiredPaddleY = constrain(desiredPaddleY, 80, height - 80);
737
     desiredPaddleY = constrain(desiredPaddleY, 80, height - 80);
449
     
738
     
450
     // Estimate anchor position needed to get paddle to desired position
739
     // Estimate anchor position needed to get paddle to desired position
451
-    // This is tricky because spring physics affects paddle position
452
     let anchorOffsetNeeded = calculateAnchorOffset(desiredPaddleY, paddlePos, anchorPos);
740
     let anchorOffsetNeeded = calculateAnchorOffset(desiredPaddleY, paddlePos, anchorPos);
453
     let targetAnchorY = desiredPaddleY + anchorOffsetNeeded;
741
     let targetAnchorY = desiredPaddleY + anchorOffsetNeeded;
454
     
742
     
455
     // Always apply some level of paddle-aware tracking
743
     // Always apply some level of paddle-aware tracking
456
     aiState.targetY = lerp(aiState.targetY, targetAnchorY, trackingIntensity);
744
     aiState.targetY = lerp(aiState.targetY, targetAnchorY, trackingIntensity);
457
     
745
     
746
+    // AI Bop decision logic
747
+    if (ballApproaching && ballDistance < 150 && !aiState.consideringBop) {
748
+        // Consider bopping if ball is close and conditions are right
749
+        let shouldConsiderBop = ballSpeed > 8 &&  // Fast incoming ball
750
+                               Math.abs(ballVel.y) < 4 &&  // Not too much vertical movement
751
+                               random() < aiSettings.aggression * 0.4; // Chance based on aggression
752
+        
753
+        if (shouldConsiderBop) {
754
+            aiState.consideringBop = true;
755
+            aiState.bopDecisionTime = currentTime;
756
+        }
757
+    }
758
+    
759
+    // Execute bop at the right moment
760
+    if (aiState.consideringBop && ballApproaching) {
761
+        let timeToBop = currentTime - aiState.bopDecisionTime;
762
+        let shouldBop = timeToBop > aiState.bopTiming && 
763
+                       ballDistance < 100 && 
764
+                       !bopState.right.active &&
765
+                       currentTime - bopState.right.lastBopTime > BOP_COOLDOWN;
766
+        
767
+        if (shouldBop) {
768
+            activateBop('right', currentTime);
769
+            aiState.consideringBop = false;
770
+        }
771
+        
772
+        // Cancel bop consideration if ball gets too far
773
+        if (ballDistance > 150) {
774
+            aiState.consideringBop = false;
775
+        }
776
+    }
777
+    
458
     // Only do advanced prediction and windup logic if enough time has passed (reaction delay)
778
     // Only do advanced prediction and windup logic if enough time has passed (reaction delay)
459
     if (currentTime - aiState.lastUpdateTime > aiSettings.reactionTime) {
779
     if (currentTime - aiState.lastUpdateTime > aiSettings.reactionTime) {
460
         
780
         
@@ -486,6 +806,7 @@ function handleAITracking(currentTime, ballPos, ballVel, aiSettings) {
486
                               ballDistance > 200 &&       // Lots of distance required  
806
                               ballDistance > 200 &&       // Lots of distance required  
487
                               Math.abs(ballVel.y) < 3 &&  // Very limited vertical movement
807
                               Math.abs(ballVel.y) < 3 &&  // Very limited vertical movement
488
                               Math.abs(ballVel.x) > 1 &&  // Ball must be actually moving toward AI
808
                               Math.abs(ballVel.x) > 1 &&  // Ball must be actually moving toward AI
809
+                              !aiState.consideringBop &&  // Don't windup if considering bop
489
                               random() < aiSettings.oscillation * aiState.aggressionLevel * 0.3; // Much lower chance
810
                               random() < aiSettings.oscillation * aiState.aggressionLevel * 0.3; // Much lower chance
490
             
811
             
491
             if (shouldWindUp) {
812
             if (shouldWindUp) {
@@ -529,28 +850,39 @@ function calculateAnchorOffset(targetPaddleY, currentPaddlePos, currentAnchorPos
529
 
850
 
530
 function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
851
 function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
531
     let windupTime = currentTime - aiState.windupStartTime;
852
     let windupTime = currentTime - aiState.windupStartTime;
532
-    let maxWindupTime = 500 / Math.max(aiState.aggressionLevel, 0.3); // Longer windup for bigger movement
533
     
853
     
534
-    // Calculate windup target with much bigger distance
854
+    // Smooth windup progression with easing
535
-    let paddlePos = rightPaddle.position;
855
+    let maxWindupTime = 800; // Longer time for smooth movement
536
-    let windupTarget = paddlePos.y + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel;
856
+    let timeProgress = Math.min(windupTime / maxWindupTime, 1.0);
537
-    windupTarget = constrain(windupTarget, 50, height - 50); // Allow closer to edges for big windup
857
+    
858
+    // Ease-in-out for smooth acceleration/deceleration
859
+    let easedProgress = timeProgress < 0.5 
860
+        ? 2 * timeProgress * timeProgress 
861
+        : 1 - Math.pow(-2 * timeProgress + 2, 3) / 2;
862
+    
863
+    aiState.windupProgress = easedProgress;
864
+    
865
+    // Calculate smooth windup target based on intercept position
866
+    let windupTargetY = aiState.interceptY + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel * easedProgress;
867
+    windupTargetY = constrain(windupTargetY, 50, height - 50);
538
     
868
     
539
     // Convert paddle target to anchor target using paddle awareness
869
     // Convert paddle target to anchor target using paddle awareness
540
-    let anchorOffsetNeeded = calculateAnchorOffset(windupTarget, paddlePos, rightSupport.position);
870
+    let anchorOffsetNeeded = calculateAnchorOffset(windupTargetY, rightPaddle.position, rightSupport.position);
541
-    aiState.targetY = windupTarget + anchorOffsetNeeded;
871
+    aiState.targetY = windupTargetY + anchorOffsetNeeded;
542
     
872
     
543
-    // Check if it's time to swing - allow more time for bigger windups
873
+    // Check if it's time to swing
544
     let ballDistance = width - ballPos.x;
874
     let ballDistance = width - ballPos.x;
545
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
875
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
546
     
876
     
547
     let shouldSwing = windupTime > maxWindupTime || 
877
     let shouldSwing = windupTime > maxWindupTime || 
548
                      ballDistance < 120 || 
878
                      ballDistance < 120 || 
549
-                     ballSpeed > 6; // Abort if ball speeds up even slightly
879
+                     ballSpeed > 6 ||
880
+                     easedProgress > 0.85; // Swing when windup is mostly complete
550
     
881
     
551
     if (shouldSwing) {
882
     if (shouldSwing) {
552
         aiState.mode = 'SWINGING';
883
         aiState.mode = 'SWINGING';
553
         aiState.swingStartTime = currentTime;
884
         aiState.swingStartTime = currentTime;
885
+        aiState.windupProgress = 0; // Reset for next time
554
     }
886
     }
555
 }
887
 }
556
 
888
 
@@ -608,25 +940,27 @@ function executeAIMovement(aiSettings) {
608
     let deltaY = aiState.targetY - currentY;
940
     let deltaY = aiState.targetY - currentY;
609
     
941
     
610
     if (Math.abs(deltaY) > 1) { // Very small dead zone for responsive tracking
942
     if (Math.abs(deltaY) > 1) { // Very small dead zone for responsive tracking
611
-        let baseSpeed = 0.08 * aiSettings.speed; // Back to more responsive speed
943
+        let baseSpeed = 0.12 * aiSettings.speed; // Increased base speed significantly
612
         
944
         
613
         // Apply swing power during swing phase
945
         // Apply swing power during swing phase
614
         if (aiState.mode === 'SWINGING') {
946
         if (aiState.mode === 'SWINGING') {
615
             baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
947
             baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
616
         } else if (aiState.mode === 'WINDING_UP') {
948
         } else if (aiState.mode === 'WINDING_UP') {
617
-            baseSpeed *= 0.6; // Slower during windup for more deliberate movement
949
+            // Slower at start of windup, faster as it progresses
950
+            let windupSpeedMultiplier = 0.3 + (aiState.windupProgress * 0.4);
951
+            baseSpeed *= windupSpeedMultiplier;
618
         }
952
         }
619
         
953
         
620
         // Apply aggression multiplier
954
         // Apply aggression multiplier
621
-        baseSpeed *= (1 + aiState.aggressionLevel * 0.2);
955
+        baseSpeed *= (1 + aiState.aggressionLevel * 0.3);
622
         
956
         
623
         let movement = deltaY * baseSpeed;
957
         let movement = deltaY * baseSpeed;
624
-        movement = constrain(movement, -SUPPORT_SPEED * 0.9, SUPPORT_SPEED * 0.9);
958
+        movement = constrain(movement, -SUPPORT_SPEED * 1.1, SUPPORT_SPEED * 1.1); // Allow slightly faster than player
625
         
959
         
626
         moveSupportEnhanced(rightSupport, movement);
960
         moveSupportEnhanced(rightSupport, movement);
627
         
961
         
628
         // Update input buffer for visual effects
962
         // Update input buffer for visual effects
629
-        inputBuffer.right = constrain(movement / (SUPPORT_SPEED * 0.9), -1, 1);
963
+        inputBuffer.right = constrain(movement / (SUPPORT_SPEED * 1.1), -1, 1);
630
     } else {
964
     } else {
631
         // Gradually reduce input buffer when AI is not moving
965
         // Gradually reduce input buffer when AI is not moving
632
         inputBuffer.right *= 0.95;
966
         inputBuffer.right *= 0.95;
@@ -864,11 +1198,24 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) {
864
     
1198
     
865
     // Check if this is the AI paddle
1199
     // Check if this is the AI paddle
866
     let isAI = aiEnabled && paddle === rightPaddle;
1200
     let isAI = aiEnabled && paddle === rightPaddle;
1201
+    let isLeft = paddle === leftPaddle;
867
     
1202
     
868
     // Calculate glow intensity based on ball proximity
1203
     // Calculate glow intensity based on ball proximity
869
     let glowIntensity = map(ballDistance, 0, PADDLE_GLOW_DISTANCE, 150, 0);
1204
     let glowIntensity = map(ballDistance, 0, PADDLE_GLOW_DISTANCE, 150, 0);
870
     glowIntensity = constrain(glowIntensity, 0, 150);
1205
     glowIntensity = constrain(glowIntensity, 0, 150);
871
     
1206
     
1207
+    // Add bop glow effect
1208
+    let bopGlow = 0;
1209
+    if (isLeft && bopState.left.active) {
1210
+        let bopProgress = (millis() - bopState.left.startTime) / bopState.left.duration;
1211
+        bopGlow = (1 - bopProgress) * 100; // Fade out over bop duration
1212
+    } else if (!isLeft && !isAI && bopState.right.active) {
1213
+        let bopProgress = (millis() - bopState.right.startTime) / bopState.right.duration;
1214
+        bopGlow = (1 - bopProgress) * 100;
1215
+    }
1216
+    
1217
+    glowIntensity += bopGlow;
1218
+    
872
     // Add AI state-based effects
1219
     // Add AI state-based effects
873
     if (isAI) {
1220
     if (isAI) {
874
         // Enhance glow during aggressive states
1221
         // Enhance glow during aggressive states
@@ -896,6 +1243,11 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) {
896
         paddleColor = [255, 50, 50]; // Bright red during swing
1243
         paddleColor = [255, 50, 50]; // Bright red during swing
897
     }
1244
     }
898
     
1245
     
1246
+    // Bop color override
1247
+    if ((isLeft && bopState.left.active) || (!isLeft && !isAI && bopState.right.active)) {
1248
+        paddleColor = [255, 255, 100]; // Bright yellow during bop
1249
+    }
1250
+    
899
     // Draw enhanced glow effect first
1251
     // Draw enhanced glow effect first
900
     if (glowIntensity > 0) {
1252
     if (glowIntensity > 0) {
901
         fill(paddleColor[0], paddleColor[1], paddleColor[2], glowIntensity * 0.6);
1253
         fill(paddleColor[0], paddleColor[1], paddleColor[2], glowIntensity * 0.6);
@@ -1109,9 +1461,9 @@ function drawMenu() {
1109
     textSize(12);
1461
     textSize(12);
1110
     fill(255, 100);
1462
     fill(255, 100);
1111
     if (menuState.selectedOption === 0) {
1463
     if (menuState.selectedOption === 0) {
1112
-        text("Controls: W/S keys or Mouse/Touch to move paddle", width/2, height - 30);
1464
+        text("Controls: W/S keys + LEFT SHIFT (bop) or Mouse/Touch", width/2, height - 30);
1113
     } else {
1465
     } else {
1114
-        text("Controls: Player 1 (W/S) | Player 2 (↑/↓) | Mouse/Touch", width/2, height - 30);
1466
+        text("Controls: P1 (W/S + L.Shift) | P2 (↑/↓ + Enter) | Mouse/Touch", width/2, height - 30);
1115
     }
1467
     }
1116
 }
1468
 }
1117
 
1469
 
@@ -1169,11 +1521,19 @@ function resetBall() {
1169
         World.remove(world, ball);
1521
         World.remove(world, ball);
1170
     }
1522
     }
1171
     
1523
     
1172
-    // Create new ball at center
1524
+    // Create new ball at center with collision filter
1173
     ball = Bodies.circle(width/2, height/2, BALL_RADIUS, {
1525
     ball = Bodies.circle(width/2, height/2, BALL_RADIUS, {
1174
         restitution: 1,
1526
         restitution: 1,
1175
         friction: 0,
1527
         friction: 0,
1176
-        frictionAir: 0
1528
+        frictionAir: 0,
1529
+        slop: 0.01,  // Tighter collision detection
1530
+        collisionFilter: {
1531
+            category: 0x0001,
1532
+            mask: 0xFFFF
1533
+        },
1534
+        render: {
1535
+            fillStyle: '#ff6464'
1536
+        }
1177
     });
1537
     });
1178
     
1538
     
1179
     if (world) {
1539
     if (world) {
@@ -1275,6 +1635,7 @@ function keyPressed() {
1275
         aiState.lastUpdateTime = 0;
1635
         aiState.lastUpdateTime = 0;
1276
         aiState.mode = 'ANTICIPATING';
1636
         aiState.mode = 'ANTICIPATING';
1277
         aiState.aggressionLevel = AI_SETTINGS[aiState.difficulty].aggression;
1637
         aiState.aggressionLevel = AI_SETTINGS[aiState.difficulty].aggression;
1638
+        aiState.windupProgress = 0;
1278
         
1639
         
1279
         // Clear particles
1640
         // Clear particles
1280
         particles = [];
1641
         particles = [];
@@ -1283,7 +1644,7 @@ function keyPressed() {
1283
     }
1644
     }
1284
     
1645
     
1285
     // Return to menu with ESC
1646
     // Return to menu with ESC
1286
-    if (keyCode === 27) {
1647
+    if (keyCode === 27) { // ESC key
1287
         gameState = 'menu';
1648
         gameState = 'menu';
1288
         gameStarted = false;
1649
         gameStarted = false;
1289
         particles = [];
1650
         particles = [];