zeroed-some/sprong / 420b0f7

Browse files

make ai less stupid

Authored by espadonne
SHA
420b0f7d994fa8a51ad4eb494d46ff049a6914eb
Parents
a64f1ed
Tree
6798017

1 changed file

StatusFile+-
M sprong.js 148 84
sprong.jsmodified
@@ -17,12 +17,12 @@ let leftSupport, leftPaddle, leftSpring;
17
 let rightSupport, rightPaddle, rightSpring;
17
 let rightSupport, rightPaddle, rightSpring;
18
 
18
 
19
 // Game state
19
 // Game state
20
-let gameMode    = 'vs-cpu';  // 'vs-cpu' or 'vs-human'
21
 let leftScore = 0;
20
 let leftScore = 0;
22
-let aiEnabled   = true;
23
-let gameState   = 'menu';   // 'menu', 'playing', 'paused'
24
 let rightScore = 0;
21
 let rightScore = 0;
25
 let gameStarted = false;
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;
26
 
26
 
27
 // Menu state
27
 // Menu state
28
 let menuState = {
28
 let menuState = {
@@ -50,8 +50,8 @@ let aiState = {
50
     aggressionLevel: 0.5, // 0 = defensive, 1 = maximum aggression
50
     aggressionLevel: 0.5, // 0 = defensive, 1 = maximum aggression
51
     lastHitTime: 0,
51
     lastHitTime: 0,
52
     
52
     
53
-    // Oscillation parameters (tuned for smoother movement)
53
+    // Oscillation parameters (increased for better windup)
54
-    windupDistance: 40,   // Slightly increased from 35 with longer spring
54
+    windupDistance: 120,  // Much bigger - about half canvas height
55
     swingPower: 1.05,     // Reduced from 1.1 for more control
55
     swingPower: 1.05,     // Reduced from 1.1 for more control
56
     timingWindow: 40,     // Slightly longer execution window
56
     timingWindow: 40,     // Slightly longer execution window
57
     
57
     
@@ -97,8 +97,8 @@ const INPUT_SMOOTHING = 0.25; // More responsive
97
 const SUPPORT_MAX_SPEED = 8;      // Higher max speed
97
 const SUPPORT_MAX_SPEED = 8;      // Higher max speed
98
 
98
 
99
 // Touch/mouse control constants
99
 // Touch/mouse control constants
100
-const MOUSE_SPEED_LIMIT = 4;    // Max speed for mouse movement
101
 const MOUSE_LAG_FACTOR  = 0.12;   // How much lag in mouse following
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
102
 const TOUCH_SENSITIVITY = 1.2;    // Touch movement multiplier
103
 
103
 
104
 // Spring physics constants (adjusted for better oscillation)
104
 // Spring physics constants (adjusted for better oscillation)
@@ -178,6 +178,11 @@ function setup() {
178
         leftSupport, leftPaddle, leftSpring,
178
         leftSupport, leftPaddle, leftSpring,
179
         rightSupport, rightPaddle, rightSpring
179
         rightSupport, rightPaddle, rightSpring
180
     ]);
180
     ]);
181
+    
182
+    console.log("Sprong Phase 5 Complete!");
183
+    console.log("Particle effects system");
184
+    console.log("Tuned physics for maximum bounce");
185
+    console.log("Faster, more responsive paddles");
181
 }
186
 }
182
 
187
 
183
 function createSpringPaddleSystem(side) {
188
 function createSpringPaddleSystem(side) {
@@ -426,77 +431,122 @@ function updateAIAggression() {
426
 }
431
 }
427
 
432
 
428
 function handleAITracking(currentTime, ballPos, ballVel, aiSettings) {
433
 function handleAITracking(currentTime, ballPos, ballVel, aiSettings) {
429
-    // Only react if enough time has passed (reaction delay)
434
+    // Always track ball position for more responsive movement
430
-    if (currentTime - aiState.lastUpdateTime < aiSettings.reactionTime) return;
431
-    
432
-    // Check if ball is approaching AI paddle
433
     let ballApproaching = ballVel.x > 0;
435
     let ballApproaching = ballVel.x > 0;
434
     let ballDistance = width - ballPos.x;
436
     let ballDistance = width - ballPos.x;
435
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
437
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
436
     
438
     
437
-    if (ballApproaching && ballDistance < 280) {
439
+    // Calculate where the AI paddle currently is (not the anchor)
440
+    let paddlePos = rightPaddle.position;
441
+    let anchorPos = rightSupport.position;
442
+    
443
+    // Continuous ball tracking with paddle awareness
444
+    let trackingIntensity = ballApproaching ? 0.08 : 0.03;
445
+    
446
+    // Calculate where anchor should be to position PADDLE at target Y
447
+    let desiredPaddleY = ballPos.y + aiState.microAdjustment;
448
+    desiredPaddleY = constrain(desiredPaddleY, 80, height - 80);
449
+    
450
+    // 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);
453
+    let targetAnchorY = desiredPaddleY + anchorOffsetNeeded;
454
+    
455
+    // Always apply some level of paddle-aware tracking
456
+    aiState.targetY = lerp(aiState.targetY, targetAnchorY, trackingIntensity);
457
+    
458
+    // Only do advanced prediction and windup logic if enough time has passed (reaction delay)
459
+    if (currentTime - aiState.lastUpdateTime > aiSettings.reactionTime) {
460
+        
461
+        if (ballApproaching && ballDistance < 300) {
438
             // Calculate intercept point with advanced prediction
462
             // Calculate intercept point with advanced prediction
439
             let timeToReach = ballDistance / Math.abs(ballVel.x);
463
             let timeToReach = ballDistance / Math.abs(ballVel.x);
440
-        aiState.interceptY = ballPos.y + ballVel.y * timeToReach;
464
+            let predictedBallY = ballPos.y + ballVel.y * timeToReach;
441
             
465
             
442
             // Account for wall bounces
466
             // Account for wall bounces
443
-        if (aiState.interceptY < 50) {
467
+            if (predictedBallY < 50) {
444
-            aiState.interceptY = 100 - aiState.interceptY;
468
+                predictedBallY = 100 - predictedBallY;
445
-        } else if (aiState.interceptY > height - 50) {
469
+            } else if (predictedBallY > height - 50) {
446
-            aiState.interceptY = 2 * (height - 50) - aiState.interceptY;
470
+                predictedBallY = 2 * (height - 50) - predictedBallY;
447
             }
471
             }
448
             
472
             
449
             // Add accuracy error
473
             // Add accuracy error
450
             let error = (random() - 0.5) * 35 * (1 - aiSettings.accuracy);
474
             let error = (random() - 0.5) * 35 * (1 - aiSettings.accuracy);
451
-        aiState.interceptY += error;
475
+            predictedBallY += error;
452
             
476
             
453
-        // Smart oscillation decision: lower velocity threshold and more distance
477
+            // Calculate where PADDLE needs to be to hit the ball
454
-        let shouldWindUp = ballSpeed < 7 &&           // Reduced from 9 - easier to trigger
478
+            aiState.interceptY = predictedBallY;
455
-                          ballDistance > 160 &&       // More distance required  
479
+            
456
-                          Math.abs(ballVel.y) < 5 &&  // Stricter vertical movement limit
480
+            // Calculate where ANCHOR needs to be to position paddle correctly
457
-                          random() < aiSettings.oscillation * aiState.aggressionLevel;
481
+            let interceptAnchorOffset = calculateAnchorOffset(aiState.interceptY, paddlePos, anchorPos);
482
+            let targetAnchorForIntercept = aiState.interceptY + interceptAnchorOffset;
483
+            
484
+            // VERY selective windup decision: only for very slow balls
485
+            let shouldWindUp = ballSpeed < 4.5 &&         // Much stricter - very slow balls only
486
+                              ballDistance > 200 &&       // Lots of distance required  
487
+                              Math.abs(ballVel.y) < 3 &&  // Very limited vertical movement
488
+                              Math.abs(ballVel.x) > 1 &&  // Ball must be actually moving toward AI
489
+                              random() < aiSettings.oscillation * aiState.aggressionLevel * 0.3; // Much lower chance
458
             
490
             
459
             if (shouldWindUp) {
491
             if (shouldWindUp) {
460
                 // Start winding up for power shot
492
                 // Start winding up for power shot
461
                 aiState.mode = 'WINDING_UP';
493
                 aiState.mode = 'WINDING_UP';
462
                 aiState.windupStartTime = currentTime;
494
                 aiState.windupStartTime = currentTime;
463
                 
495
                 
464
-            // Determine windup direction (opposite of intercept)
496
+                // Determine windup direction (opposite of where paddle needs to be)
465
-            let currentY = rightSupport.position.y;
497
+                aiState.windupDirection = aiState.interceptY > paddlePos.y ? -1 : 1;
466
-            aiState.windupDirection = aiState.interceptY > currentY ? -1 : 1;
467
                 
498
                 
468
             } else {
499
             } else {
469
-            // Simple tracking without oscillation
500
+                // Use paddle-aware intercept positioning
470
-            aiState.targetY = aiState.interceptY;
501
+                aiState.targetY = lerp(aiState.targetY, targetAnchorForIntercept, 0.3);
471
             }
502
             }
472
             
503
             
473
             aiState.lastUpdateTime = currentTime;
504
             aiState.lastUpdateTime = currentTime;
474
-    } else {
475
-        // When ball isn't approaching, do lifelike tracking
476
-        let trackingY = ballPos.y + aiState.microAdjustment;
477
-        trackingY = constrain(trackingY, 80, height - 80);
478
-        aiState.targetY = lerp(aiState.targetY, trackingY, 0.02); // Very gentle tracking
479
         }
505
         }
480
     }
506
     }
507
+}
508
+
509
+// Helper function to estimate where anchor should be to position paddle at target Y
510
+function calculateAnchorOffset(targetPaddleY, currentPaddlePos, currentAnchorPos) {
511
+    // Calculate current spring vector
512
+    let springVectorY = currentPaddlePos.y - currentAnchorPos.y;
513
+    
514
+    // The paddle tends to lag behind the anchor due to spring physics
515
+    // We need to account for this offset when positioning
516
+    
517
+    // Simple approximation: if spring is compressed/extended, paddle will be offset
518
+    let currentSpringLength = dist(currentAnchorPos.x, currentAnchorPos.y, 
519
+                                  currentPaddlePos.x, currentPaddlePos.y);
520
+    let springCompression = SPRING_LENGTH - currentSpringLength;
521
+    
522
+    // Estimate the Y offset the paddle will have relative to anchor
523
+    // This is a simplified physics approximation
524
+    let estimatedPaddleOffset = springVectorY * 0.8; // Paddle lags behind anchor
525
+    
526
+    // Return the offset needed for anchor positioning
527
+    return -estimatedPaddleOffset;
528
+}
481
 
529
 
482
 function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
530
 function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
483
     let windupTime = currentTime - aiState.windupStartTime;
531
     let windupTime = currentTime - aiState.windupStartTime;
484
-    let maxWindupTime = 350 / Math.max(aiState.aggressionLevel, 0.3); // Slower windup, minimum time
532
+    let maxWindupTime = 500 / Math.max(aiState.aggressionLevel, 0.3); // Longer windup for bigger movement
485
     
533
     
486
-    // Calculate windup target (move away from intercept point)
534
+    // Calculate windup target with much bigger distance
487
-    let currentY = rightSupport.position.y;
535
+    let paddlePos = rightPaddle.position;
488
-    let windupTarget = currentY + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel;
536
+    let windupTarget = paddlePos.y + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel;
489
-    windupTarget = constrain(windupTarget, 70, height - 70); // More margin from edges
537
+    windupTarget = constrain(windupTarget, 50, height - 50); // Allow closer to edges for big windup
490
     
538
     
491
-    aiState.targetY = windupTarget;
539
+    // Convert paddle target to anchor target using paddle awareness
540
+    let anchorOffsetNeeded = calculateAnchorOffset(windupTarget, paddlePos, rightSupport.position);
541
+    aiState.targetY = windupTarget + anchorOffsetNeeded;
492
     
542
     
493
-    // Check if it's time to swing - more conservative timing
543
+    // Check if it's time to swing - allow more time for bigger windups
494
     let ballDistance = width - ballPos.x;
544
     let ballDistance = width - ballPos.x;
495
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
545
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
496
     
546
     
497
     let shouldSwing = windupTime > maxWindupTime || 
547
     let shouldSwing = windupTime > maxWindupTime || 
498
-                     ballDistance < 90 || 
548
+                     ballDistance < 120 || 
499
-                     ballSpeed > 8; // Lower threshold - abort if ball speeds up
549
+                     ballSpeed > 6; // Abort if ball speeds up even slightly
500
     
550
     
501
     if (shouldSwing) {
551
     if (shouldSwing) {
502
         aiState.mode = 'SWINGING';
552
         aiState.mode = 'SWINGING';
@@ -505,8 +555,12 @@ function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
505
 }
555
 }
506
 
556
 
507
 function handleAISwing(currentTime, ballPos, ballVel, aiSettings) {
557
 function handleAISwing(currentTime, ballPos, ballVel, aiSettings) {
508
-    // Aggressive swing toward intercept point
558
+    // Aggressive swing toward intercept point - but position anchor for paddle placement
509
-    aiState.targetY = aiState.interceptY;
559
+    let paddlePos = rightPaddle.position;
560
+    
561
+    // Calculate where anchor should be to get paddle to intercept point
562
+    let anchorOffsetNeeded = calculateAnchorOffset(aiState.interceptY, paddlePos, rightSupport.position);
563
+    aiState.targetY = aiState.interceptY + anchorOffsetNeeded;
510
     
564
     
511
     let swingTime = currentTime - aiState.swingStartTime;
565
     let swingTime = currentTime - aiState.swingStartTime;
512
     let maxSwingTime = aiState.timingWindow;
566
     let maxSwingTime = aiState.timingWindow;
@@ -529,8 +583,18 @@ function handleAIRecovery(currentTime, ballPos, ballVel, aiSettings) {
529
 }
583
 }
530
 
584
 
531
 function handleAIAnticipation(currentTime, ballPos, ballVel, aiSettings) {
585
 function handleAIAnticipation(currentTime, ballPos, ballVel, aiSettings) {
532
-    // Stay near center with subtle lifelike movements
586
+    // Stay near center with subtle lifelike movements, but use paddle-aware positioning
533
-    aiState.targetY = aiState.idleTarget + aiState.breathingOffset + aiState.microAdjustment;
587
+    let baseTarget = aiState.idleTarget + aiState.breathingOffset + aiState.microAdjustment;
588
+    let ballTrackingTarget = ballPos.y;
589
+    
590
+    // Blend idle position with gentle ball tracking
591
+    let desiredPaddleY = lerp(baseTarget, ballTrackingTarget, 0.15);
592
+    
593
+    // Convert paddle target to anchor target
594
+    let paddlePos = rightPaddle.position;
595
+    let anchorPos = rightSupport.position;
596
+    let anchorOffsetNeeded = calculateAnchorOffset(desiredPaddleY, paddlePos, anchorPos);
597
+    aiState.targetY = desiredPaddleY + anchorOffsetNeeded;
534
     
598
     
535
     // Switch back to tracking when ball changes direction
599
     // Switch back to tracking when ball changes direction
536
     if (ballVel.x > 0) {
600
     if (ballVel.x > 0) {
@@ -543,14 +607,14 @@ function executeAIMovement(aiSettings) {
543
     let currentY = rightSupport.position.y;
607
     let currentY = rightSupport.position.y;
544
     let deltaY = aiState.targetY - currentY;
608
     let deltaY = aiState.targetY - currentY;
545
     
609
     
546
-    if (Math.abs(deltaY) > 2) { // Smaller dead zone for more responsive micro-movements
610
+    if (Math.abs(deltaY) > 1) { // Very small dead zone for responsive tracking
547
-        let baseSpeed = 0.06 * aiSettings.speed; // Slightly slower base movement
611
+        let baseSpeed = 0.08 * aiSettings.speed; // Back to more responsive speed
548
         
612
         
549
         // Apply swing power during swing phase
613
         // Apply swing power during swing phase
550
         if (aiState.mode === 'SWINGING') {
614
         if (aiState.mode === 'SWINGING') {
551
             baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
615
             baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
552
         } else if (aiState.mode === 'WINDING_UP') {
616
         } else if (aiState.mode === 'WINDING_UP') {
553
-            baseSpeed *= 0.7; // Slower during windup for more deliberate movement
617
+            baseSpeed *= 0.6; // Slower during windup for more deliberate movement
554
         }
618
         }
555
         
619
         
556
         // Apply aggression multiplier
620
         // Apply aggression multiplier
@@ -969,7 +1033,7 @@ function drawDebugInfo() {
969
         // Show AI technique indicators
1033
         // Show AI technique indicators
970
         if (aiState.mode === 'WINDING_UP') {
1034
         if (aiState.mode === 'WINDING_UP') {
971
             fill(255, 150, 50, 200);
1035
             fill(255, 150, 50, 200);
972
-            text("AI WINDING UP FOR POWER SHOT", 10, 175);
1036
+            text("🔄 AI WINDING UP FOR POWER SHOT", 10, 175);
973
         } else if (aiState.mode === 'SWINGING') {
1037
         } else if (aiState.mode === 'SWINGING') {
974
             fill(255, 50, 50, 200);
1038
             fill(255, 50, 50, 200);
975
             text("⚡ AI POWER SWING!", 10, 175);
1039
             text("⚡ AI POWER SWING!", 10, 175);
@@ -1176,7 +1240,7 @@ function keyPressed() {
1176
     if (key === 'm' || key === 'M') {
1240
     if (key === 'm' || key === 'M') {
1177
         aiEnabled = !aiEnabled;
1241
         aiEnabled = !aiEnabled;
1178
         gameMode = aiEnabled ? 'vs-cpu' : 'vs-human';
1242
         gameMode = aiEnabled ? 'vs-cpu' : 'vs-human';
1179
-        console.log(`Switched to ${gameMode} mode`);
1243
+        console.log("Switched to " + gameMode + " mode");
1180
     }
1244
     }
1181
     
1245
     
1182
     // Change AI difficulty (only during gameplay)
1246
     // Change AI difficulty (only during gameplay)
@@ -1188,7 +1252,7 @@ function keyPressed() {
1188
         } else {
1252
         } else {
1189
             aiState.difficulty = 'easy';
1253
             aiState.difficulty = 'easy';
1190
         }
1254
         }
1191
-        console.log(`AI difficulty: ${aiState.difficulty}`);
1255
+        console.log("AI difficulty: " + aiState.difficulty);
1192
     }
1256
     }
1193
     
1257
     
1194
     // Reset game with spacebar
1258
     // Reset game with spacebar
@@ -1219,7 +1283,7 @@ function keyPressed() {
1219
     }
1283
     }
1220
     
1284
     
1221
     // Return to menu with ESC
1285
     // Return to menu with ESC
1222
-    if (keyCode === 27) { // ESC key
1286
+    if (keyCode === 27) {
1223
         gameState = 'menu';
1287
         gameState = 'menu';
1224
         gameStarted = false;
1288
         gameStarted = false;
1225
         particles = [];
1289
         particles = [];
@@ -1285,7 +1349,7 @@ function startGameWithSelection() {
1285
     // Clear particles
1349
     // Clear particles
1286
     particles = [];
1350
     particles = [];
1287
     
1351
     
1288
-    console.log(`Started ${gameMode} mode${aiEnabled ? ' - Difficulty: ' + aiState.difficulty : ''}`);
1352
+    console.log("Started " + gameMode + " mode" + (aiEnabled ? " - Difficulty: " + aiState.difficulty : ""));
1289
 }
1353
 }
1290
 
1354
 
1291
 function keyReleased() {
1355
 function keyReleased() {