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
@@ -1,5 +1,5 @@
11
 // Matter.js module aliases
2
-const Body  = Matter.Body;
2
+const Body = Matter.Body;
33
 const World = Matter.World;
44
 const Engine = Matter.Engine;
55
 const Bodies = Matter.Bodies;
@@ -17,18 +17,18 @@ let leftSupport, leftPaddle, leftSpring;
1717
 let rightSupport, rightPaddle, rightSpring;
1818
 
1919
 // Game state
20
-let gameMode    = 'vs-cpu';  // 'vs-cpu' or 'vs-human'
21
-let leftScore   = 0;
22
-let aiEnabled   = true;
23
-let gameState   = 'menu';   // 'menu', 'playing', 'paused'
24
-let rightScore  = 0;
20
+let leftScore = 0;
21
+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 = {
29
-    selectedOption: 0,        // 0 = 1 Player, 1 = 2 Player
29
+    selectedOption: 0, // 0 = 1 Player, 1 = 2 Player
3030
     options: ['1 Player vs CPU', '2 Player'],
31
-    difficultySelected: 1,    // 0 = Easy, 1 = Medium, 2 = Hard
31
+    difficultySelected: 1, // 0 = Easy, 1 = Medium, 2 = Hard
3232
     difficulties: ['Easy', 'Medium', 'Hard'],
3333
     showDifficulty: true
3434
 };
@@ -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
     
@@ -91,21 +91,21 @@ const PADDLE_WIDTH = 20;
9191
 const PADDLE_HEIGHT = 80;
9292
 
9393
 // 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
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
9898
 
9999
 // 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
102
-const TOUCH_SENSITIVITY = 1.2;  // Touch movement multiplier
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
103103
 
104104
 // 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
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
109109
 
110110
 // AI difficulty settings
111111
 const AI_SETTINGS = {
@@ -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) {
438
-        // Calculate intercept point with advanced prediction
439
-        let timeToReach = ballDistance / Math.abs(ballVel.x);
440
-        aiState.interceptY = ballPos.y + ballVel.y * timeToReach;
441
-        
442
-        // 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;
447
-        }
448
-        
449
-        // Add accuracy error
450
-        let error = (random() - 0.5) * 35 * (1 - aiSettings.accuracy);
451
-        aiState.interceptY += error;
452
-        
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;
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) {
458460
         
459
-        if (shouldWindUp) {
460
-            // Start winding up for power shot
461
-            aiState.mode = 'WINDING_UP';
462
-            aiState.windupStartTime = currentTime;
461
+        if (ballApproaching && ballDistance < 300) {
462
+            // Calculate intercept point with advanced prediction
463
+            let timeToReach = ballDistance / Math.abs(ballVel.x);
464
+            let predictedBallY = ballPos.y + ballVel.y * timeToReach;
465
+            
466
+            // Account for wall bounces
467
+            if (predictedBallY < 50) {
468
+                predictedBallY = 100 - predictedBallY;
469
+            } else if (predictedBallY > height - 50) {
470
+                predictedBallY = 2 * (height - 50) - predictedBallY;
471
+            }
463472
             
464
-            // Determine windup direction (opposite of intercept)
465
-            let currentY = rightSupport.position.y;
466
-            aiState.windupDirection = aiState.interceptY > currentY ? -1 : 1;
473
+            // Add accuracy error
474
+            let error = (random() - 0.5) * 35 * (1 - aiSettings.accuracy);
475
+            predictedBallY += error;
467476
             
468
-        } else {
469
-            // Simple tracking without oscillation
470
-            aiState.targetY = aiState.interceptY;
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
490
+            
491
+            if (shouldWindUp) {
492
+                // Start winding up for power shot
493
+                aiState.mode = 'WINDING_UP';
494
+                aiState.windupStartTime = currentTime;
495
+                
496
+                // Determine windup direction (opposite of where paddle needs to be)
497
+                aiState.windupDirection = aiState.interceptY > paddlePos.y ? -1 : 1;
498
+                
499
+            } else {
500
+                // Use paddle-aware intercept positioning
501
+                aiState.targetY = lerp(aiState.targetY, targetAnchorForIntercept, 0.3);
502
+            }
503
+            
504
+            aiState.lastUpdateTime = currentTime;
471505
         }
472
-        
473
-        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
479506
     }
480507
 }
481508
 
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
+}
529
+
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() {