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;
1717
 let rightSupport, rightPaddle, rightSpring;
1818
 
1919
 // Game state
20
-let gameMode    = 'vs-cpu';  // 'vs-cpu' or 'vs-human'
2120
 let leftScore = 0;
22
-let aiEnabled   = true;
23
-let gameState   = 'menu';   // 'menu', 'playing', 'paused'
2421
 let rightScore = 0;
2522
 let gameStarted = false;
23
+let gameState = 'menu'; // 'menu', 'playing', 'paused'
24
+let gameMode = 'vs-cpu'; // 'vs-cpu' or 'vs-human'
25
+let aiEnabled = true;
2626
 
2727
 // Menu state
2828
 let menuState = {
@@ -50,8 +50,8 @@ let aiState = {
5050
     aggressionLevel: 0.5, // 0 = defensive, 1 = maximum aggression
5151
     lastHitTime: 0,
5252
     
53
-    // Oscillation parameters (tuned for smoother movement)
54
-    windupDistance: 40,   // Slightly increased from 35 with longer spring
53
+    // Oscillation parameters (increased for better windup)
54
+    windupDistance: 120,  // Much bigger - about half canvas height
5555
     swingPower: 1.05,     // Reduced from 1.1 for more control
5656
     timingWindow: 40,     // Slightly longer execution window
5757
     
@@ -97,8 +97,8 @@ const INPUT_SMOOTHING = 0.25; // More responsive
9797
 const SUPPORT_MAX_SPEED = 8;      // Higher max speed
9898
 
9999
 // Touch/mouse control constants
100
-const MOUSE_SPEED_LIMIT = 4;    // Max speed for mouse movement
101100
 const MOUSE_LAG_FACTOR  = 0.12;   // How much lag in mouse following
101
+const MOUSE_SPEED_LIMIT = 4;      // Max speed for mouse movement
102102
 const TOUCH_SENSITIVITY = 1.2;    // Touch movement multiplier
103103
 
104104
 // Spring physics constants (adjusted for better oscillation)
@@ -178,6 +178,11 @@ function setup() {
178178
         leftSupport, leftPaddle, leftSpring,
179179
         rightSupport, rightPaddle, rightSpring
180180
     ]);
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");
181186
 }
182187
 
183188
 function createSpringPaddleSystem(side) {
@@ -426,77 +431,122 @@ function updateAIAggression() {
426431
 }
427432
 
428433
 function handleAITracking(currentTime, ballPos, ballVel, aiSettings) {
429
-    // Only react if enough time has passed (reaction delay)
430
-    if (currentTime - aiState.lastUpdateTime < aiSettings.reactionTime) return;
431
-    
432
-    // Check if ball is approaching AI paddle
434
+    // Always track ball position for more responsive movement
433435
     let ballApproaching = ballVel.x > 0;
434436
     let ballDistance = width - ballPos.x;
435437
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
436438
     
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) {
438462
             // Calculate intercept point with advanced prediction
439463
             let timeToReach = ballDistance / Math.abs(ballVel.x);
440
-        aiState.interceptY = ballPos.y + ballVel.y * timeToReach;
464
+            let predictedBallY = ballPos.y + ballVel.y * timeToReach;
441465
             
442466
             // Account for wall bounces
443
-        if (aiState.interceptY < 50) {
444
-            aiState.interceptY = 100 - aiState.interceptY;
445
-        } else if (aiState.interceptY > height - 50) {
446
-            aiState.interceptY = 2 * (height - 50) - aiState.interceptY;
467
+            if (predictedBallY < 50) {
468
+                predictedBallY = 100 - predictedBallY;
469
+            } else if (predictedBallY > height - 50) {
470
+                predictedBallY = 2 * (height - 50) - predictedBallY;
447471
             }
448472
             
449473
             // Add accuracy error
450474
             let error = (random() - 0.5) * 35 * (1 - aiSettings.accuracy);
451
-        aiState.interceptY += error;
475
+            predictedBallY += error;
452476
             
453
-        // Smart oscillation decision: lower velocity threshold and more distance
454
-        let shouldWindUp = ballSpeed < 7 &&           // Reduced from 9 - easier to trigger
455
-                          ballDistance > 160 &&       // More distance required  
456
-                          Math.abs(ballVel.y) < 5 &&  // Stricter vertical movement limit
457
-                          random() < aiSettings.oscillation * aiState.aggressionLevel;
477
+            // Calculate where PADDLE needs to be to hit the ball
478
+            aiState.interceptY = predictedBallY;
479
+            
480
+            // Calculate where ANCHOR needs to be to position paddle correctly
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
458490
             
459491
             if (shouldWindUp) {
460492
                 // Start winding up for power shot
461493
                 aiState.mode = 'WINDING_UP';
462494
                 aiState.windupStartTime = currentTime;
463495
                 
464
-            // Determine windup direction (opposite of intercept)
465
-            let currentY = rightSupport.position.y;
466
-            aiState.windupDirection = aiState.interceptY > currentY ? -1 : 1;
496
+                // Determine windup direction (opposite of where paddle needs to be)
497
+                aiState.windupDirection = aiState.interceptY > paddlePos.y ? -1 : 1;
467498
                 
468499
             } else {
469
-            // Simple tracking without oscillation
470
-            aiState.targetY = aiState.interceptY;
500
+                // Use paddle-aware intercept positioning
501
+                aiState.targetY = lerp(aiState.targetY, targetAnchorForIntercept, 0.3);
471502
             }
472503
             
473504
             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
479505
         }
480506
     }
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
+}
481529
 
482530
 function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
483531
     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
485533
     
486
-    // Calculate windup target (move away from intercept point)
487
-    let currentY = rightSupport.position.y;
488
-    let windupTarget = currentY + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel;
489
-    windupTarget = constrain(windupTarget, 70, height - 70); // More margin from edges
534
+    // Calculate windup target with much bigger distance
535
+    let paddlePos = rightPaddle.position;
536
+    let windupTarget = paddlePos.y + aiState.windupDirection * aiState.windupDistance * aiState.aggressionLevel;
537
+    windupTarget = constrain(windupTarget, 50, height - 50); // Allow closer to edges for big windup
490538
     
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;
492542
     
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
494544
     let ballDistance = width - ballPos.x;
495545
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
496546
     
497547
     let shouldSwing = windupTime > maxWindupTime || 
498
-                     ballDistance < 90 || 
499
-                     ballSpeed > 8; // Lower threshold - abort if ball speeds up
548
+                     ballDistance < 120 || 
549
+                     ballSpeed > 6; // Abort if ball speeds up even slightly
500550
     
501551
     if (shouldSwing) {
502552
         aiState.mode = 'SWINGING';
@@ -505,8 +555,12 @@ function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
505555
 }
506556
 
507557
 function handleAISwing(currentTime, ballPos, ballVel, aiSettings) {
508
-    // Aggressive swing toward intercept point
509
-    aiState.targetY = aiState.interceptY;
558
+    // Aggressive swing toward intercept point - but position anchor for paddle placement
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;
510564
     
511565
     let swingTime = currentTime - aiState.swingStartTime;
512566
     let maxSwingTime = aiState.timingWindow;
@@ -529,8 +583,18 @@ function handleAIRecovery(currentTime, ballPos, ballVel, aiSettings) {
529583
 }
530584
 
531585
 function handleAIAnticipation(currentTime, ballPos, ballVel, aiSettings) {
532
-    // Stay near center with subtle lifelike movements
533
-    aiState.targetY = aiState.idleTarget + aiState.breathingOffset + aiState.microAdjustment;
586
+    // Stay near center with subtle lifelike movements, but use paddle-aware positioning
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;
534598
     
535599
     // Switch back to tracking when ball changes direction
536600
     if (ballVel.x > 0) {
@@ -543,14 +607,14 @@ function executeAIMovement(aiSettings) {
543607
     let currentY = rightSupport.position.y;
544608
     let deltaY = aiState.targetY - currentY;
545609
     
546
-    if (Math.abs(deltaY) > 2) { // Smaller dead zone for more responsive micro-movements
547
-        let baseSpeed = 0.06 * aiSettings.speed; // Slightly slower base movement
610
+    if (Math.abs(deltaY) > 1) { // Very small dead zone for responsive tracking
611
+        let baseSpeed = 0.08 * aiSettings.speed; // Back to more responsive speed
548612
         
549613
         // Apply swing power during swing phase
550614
         if (aiState.mode === 'SWINGING') {
551615
             baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
552616
         } 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
554618
         }
555619
         
556620
         // Apply aggression multiplier
@@ -969,7 +1033,7 @@ function drawDebugInfo() {
9691033
         // Show AI technique indicators
9701034
         if (aiState.mode === 'WINDING_UP') {
9711035
             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);
9731037
         } else if (aiState.mode === 'SWINGING') {
9741038
             fill(255, 50, 50, 200);
9751039
             text("⚡ AI POWER SWING!", 10, 175);
@@ -1176,7 +1240,7 @@ function keyPressed() {
11761240
     if (key === 'm' || key === 'M') {
11771241
         aiEnabled = !aiEnabled;
11781242
         gameMode = aiEnabled ? 'vs-cpu' : 'vs-human';
1179
-        console.log(`Switched to ${gameMode} mode`);
1243
+        console.log("Switched to " + gameMode + " mode");
11801244
     }
11811245
     
11821246
     // Change AI difficulty (only during gameplay)
@@ -1188,7 +1252,7 @@ function keyPressed() {
11881252
         } else {
11891253
             aiState.difficulty = 'easy';
11901254
         }
1191
-        console.log(`AI difficulty: ${aiState.difficulty}`);
1255
+        console.log("AI difficulty: " + aiState.difficulty);
11921256
     }
11931257
     
11941258
     // Reset game with spacebar
@@ -1219,7 +1283,7 @@ function keyPressed() {
12191283
     }
12201284
     
12211285
     // Return to menu with ESC
1222
-    if (keyCode === 27) { // ESC key
1286
+    if (keyCode === 27) {
12231287
         gameState = 'menu';
12241288
         gameStarted = false;
12251289
         particles = [];
@@ -1285,7 +1349,7 @@ function startGameWithSelection() {
12851349
     // Clear particles
12861350
     particles = [];
12871351
     
1288
-    console.log(`Started ${gameMode} mode${aiEnabled ? ' - Difficulty: ' + aiState.difficulty : ''}`);
1352
+    console.log("Started " + gameMode + " mode" + (aiEnabled ? " - Difficulty: " + aiState.difficulty : ""));
12891353
 }
12901354
 
12911355
 function keyReleased() {