zeroed-some/sprong / a64f1ed

Browse files

make ai more aggressive, windups, spring length

Authored by espadonne
SHA
a64f1ed430a7d05cc16003c25bbb48f6a1df97a4
Parents
5fd18f3
Tree
9b64f46

1 changed file

StatusFile+-
M sprong.js 270 50
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 leftScore = 0;
21
-let rightScore = 0;
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;
2225
 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
 };
@@ -39,7 +39,27 @@ let aiState = {
3939
     reactionDelay: 0,
4040
     difficulty: 'medium', // 'easy', 'medium', 'hard'
4141
     lastBallX: 0,
42
-    lastUpdateTime: 0
42
+    lastUpdateTime: 0,
43
+    
44
+    // Advanced AI state machine
45
+    mode: 'TRACKING',     // TRACKING, WINDING_UP, SWINGING, RECOVERING, ANTICIPATING
46
+    windupStartTime: 0,
47
+    swingStartTime: 0,
48
+    interceptY: 200,
49
+    windupDirection: 1,   // 1 for up, -1 for down
50
+    aggressionLevel: 0.5, // 0 = defensive, 1 = maximum aggression
51
+    lastHitTime: 0,
52
+    
53
+    // Oscillation parameters (tuned for smoother movement)
54
+    windupDistance: 40,   // Slightly increased from 35 with longer spring
55
+    swingPower: 1.05,     // Reduced from 1.1 for more control
56
+    timingWindow: 40,     // Slightly longer execution window
57
+    
58
+    // Lifelike movement
59
+    idleTarget: 200,      // Where AI "wants" to be when idle
60
+    microAdjustment: 0,   // Small random movements
61
+    breathingOffset: 0,   // Subtle breathing-like motion
62
+    lastMicroTime: 0      // For micro-movement timing
4363
 };
4464
 
4565
 // Player input
@@ -81,9 +101,9 @@ const MOUSE_SPEED_LIMIT = 4; // Max speed for mouse movement
81101
 const MOUSE_LAG_FACTOR  = 0.12; // How much lag in mouse following
82102
 const TOUCH_SENSITIVITY = 1.2;  // Touch movement multiplier
83103
 
84
-// Spring physics constants (toned down for stability)
104
+// Spring physics constants (adjusted for better oscillation)
85105
 const PADDLE_MASS       = 0.8;  // Back to more stable value
86
-const SPRING_LENGTH     = 40;
106
+const SPRING_LENGTH     = 50;   // Increased from 40 for more room
87107
 const SPRING_DAMPING    = 0.6;  // More damping = less erratic
88108
 const SPRING_STIFFNESS  = 0.025; // Slightly lower for smoother motion
89109
 
@@ -93,19 +113,25 @@ const AI_SETTINGS = {
93113
         reactionTime: 400,    // ms delay
94114
         accuracy: 0.7,        // 70% accuracy
95115
         speed: 0.6,           // 60% of normal speed
96
-        prediction: 0.3       // 30% prediction vs reaction
116
+        prediction: 0.3,      // 30% prediction vs reaction
117
+        aggression: 0.2,      // Low aggression
118
+        oscillation: 0.3      // Minimal oscillation
97119
     },
98120
     medium: {
99121
         reactionTime: 250,
100122
         accuracy: 0.85,
101123
         speed: 0.8,
102
-        prediction: 0.6
124
+        prediction: 0.6,
125
+        aggression: 0.5,      // Moderate aggression
126
+        oscillation: 0.7      // Good oscillation technique
103127
     },
104128
     hard: {
105129
         reactionTime: 150,
106130
         accuracy: 0.95,
107131
         speed: 1.0,
108
-        prediction: 0.8
132
+        prediction: 0.8,
133
+        aggression: 0.8,      // High aggression
134
+        oscillation: 1.0      // Master-level oscillation
109135
     }
110136
 };
111137
 
@@ -152,11 +178,6 @@ function setup() {
152178
         leftSupport, leftPaddle, leftSpring,
153179
         rightSupport, rightPaddle, rightSpring
154180
     ]);
155
-    
156
-    console.log("🎮 Sprong Phase 5 Complete!");
157
-    console.log("✓ Particle effects system");
158
-    console.log("✓ Tuned physics for maximum bounce");
159
-    console.log("✓ Faster, more responsive paddles");
160181
 }
161182
 
162183
 function createSpringPaddleSystem(side) {
@@ -339,45 +360,212 @@ function handleAI() {
339360
     let ballVel = ball.velocity;
340361
     let aiSettings = AI_SETTINGS[aiState.difficulty];
341362
     
342
-    // Only update AI decision if enough time has passed (reaction delay)
343
-    if (currentTime - aiState.lastUpdateTime > aiSettings.reactionTime) {
363
+    // Update aggression based on score difference and time
364
+    updateAIAggression();
365
+    
366
+    // Add lifelike micro-movements
367
+    updateAILifelikeBehavior(currentTime);
368
+    
369
+    // Advanced AI state machine
370
+    switch (aiState.mode) {
371
+        case 'TRACKING':
372
+            handleAITracking(currentTime, ballPos, ballVel, aiSettings);
373
+            break;
374
+        case 'WINDING_UP':
375
+            handleAIWindup(currentTime, ballPos, ballVel, aiSettings);
376
+            break;
377
+        case 'SWINGING':
378
+            handleAISwing(currentTime, ballPos, ballVel, aiSettings);
379
+            break;
380
+        case 'RECOVERING':
381
+            handleAIRecovery(currentTime, ballPos, ballVel, aiSettings);
382
+            break;
383
+        case 'ANTICIPATING':
384
+            handleAIAnticipation(currentTime, ballPos, ballVel, aiSettings);
385
+            break;
386
+    }
387
+    
388
+    // Apply movement with spring physics awareness
389
+    executeAIMovement(aiSettings);
390
+}
391
+
392
+function updateAILifelikeBehavior(currentTime) {
393
+    // Subtle breathing-like motion when not actively engaged
394
+    aiState.breathingOffset = sin(currentTime * 0.003) * 3;
395
+    
396
+    // Random micro-adjustments every few seconds
397
+    if (currentTime - aiState.lastMicroTime > 2000 + random(1000)) {
398
+        aiState.microAdjustment = (random() - 0.5) * 15;
399
+        aiState.lastMicroTime = currentTime;
400
+    }
401
+    
402
+    // Gradually decay micro-adjustment
403
+    aiState.microAdjustment *= 0.98;
404
+    
405
+    // Update idle target with slight wandering
406
+    if (aiState.mode === 'ANTICIPATING' || aiState.mode === 'RECOVERING') {
407
+        let centerY = height / 2;
408
+        let wanderRadius = 25;
409
+        aiState.idleTarget = centerY + sin(currentTime * 0.002) * wanderRadius;
410
+    }
411
+}
412
+
413
+function updateAIAggression() {
414
+    // Increase aggression when losing
415
+    let scoreDiff = leftScore - rightScore;
416
+    let baseAggression = AI_SETTINGS[aiState.difficulty].aggression;
417
+    
418
+    // Rage mode when losing by 2+ points
419
+    if (scoreDiff >= 2) {
420
+        aiState.aggressionLevel = Math.min(1.0, baseAggression + 0.3);
421
+    } else if (scoreDiff >= 1) {
422
+        aiState.aggressionLevel = Math.min(1.0, baseAggression + 0.15);
423
+    } else {
424
+        aiState.aggressionLevel = baseAggression;
425
+    }
426
+}
427
+
428
+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
433
+    let ballApproaching = ballVel.x > 0;
434
+    let ballDistance = width - ballPos.x;
435
+    let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
436
+    
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
+        }
344448
         
345
-        // Calculate where ball will be (basic prediction)
346
-        let ballFutureX = ballPos.x + ballVel.x * 30; // Look 30 frames ahead
347
-        let ballFutureY = ballPos.y + ballVel.y * 30;
449
+        // Add accuracy error
450
+        let error = (random() - 0.5) * 35 * (1 - aiSettings.accuracy);
451
+        aiState.interceptY += error;
348452
         
349
-        // Only react if ball is moving toward AI paddle
350
-        if (ballVel.x > 0) {
351
-            // Mix prediction with current position based on AI skill
352
-            let targetY = lerp(ballPos.y, ballFutureY, aiSettings.prediction);
353
-            
354
-            // Add some inaccuracy to make it beatable
355
-            let error = (random() - 0.5) * 50 * (1 - aiSettings.accuracy);
356
-            targetY += error;
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;
458
+        
459
+        if (shouldWindUp) {
460
+            // Start winding up for power shot
461
+            aiState.mode = 'WINDING_UP';
462
+            aiState.windupStartTime = currentTime;
357463
             
358
-            // Keep target in bounds
359
-            targetY = constrain(targetY, 80, height - 80);
464
+            // Determine windup direction (opposite of intercept)
465
+            let currentY = rightSupport.position.y;
466
+            aiState.windupDirection = aiState.interceptY > currentY ? -1 : 1;
360467
             
361
-            aiState.targetY = targetY;
362
-            aiState.lastUpdateTime = currentTime;
468
+        } else {
469
+            // Simple tracking without oscillation
470
+            aiState.targetY = aiState.interceptY;
363471
         }
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
364479
     }
480
+}
481
+
482
+function handleAIWindup(currentTime, ballPos, ballVel, aiSettings) {
483
+    let windupTime = currentTime - aiState.windupStartTime;
484
+    let maxWindupTime = 350 / Math.max(aiState.aggressionLevel, 0.3); // Slower windup, minimum time
485
+    
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
365490
     
491
+    aiState.targetY = windupTarget;
492
+    
493
+    // Check if it's time to swing - more conservative timing
494
+    let ballDistance = width - ballPos.x;
495
+    let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
496
+    
497
+    let shouldSwing = windupTime > maxWindupTime || 
498
+                     ballDistance < 90 || 
499
+                     ballSpeed > 8; // Lower threshold - abort if ball speeds up
500
+    
501
+    if (shouldSwing) {
502
+        aiState.mode = 'SWINGING';
503
+        aiState.swingStartTime = currentTime;
504
+    }
505
+}
506
+
507
+function handleAISwing(currentTime, ballPos, ballVel, aiSettings) {
508
+    // Aggressive swing toward intercept point
509
+    aiState.targetY = aiState.interceptY;
510
+    
511
+    let swingTime = currentTime - aiState.swingStartTime;
512
+    let maxSwingTime = aiState.timingWindow;
513
+    
514
+    // Check if swing is complete
515
+    if (swingTime > maxSwingTime || Math.abs(ball.velocity.x) < 2) {
516
+        aiState.mode = 'RECOVERING';
517
+        aiState.lastHitTime = currentTime;
518
+    }
519
+}
520
+
521
+function handleAIRecovery(currentTime, ballPos, ballVel, aiSettings) {
522
+    // Return to idle position with lifelike movement
523
+    aiState.targetY = aiState.idleTarget + aiState.breathingOffset;
524
+    
525
+    let recoveryTime = currentTime - aiState.lastHitTime;
526
+    if (recoveryTime > 400) { // Faster recovery
527
+        aiState.mode = 'ANTICIPATING';
528
+    }
529
+}
530
+
531
+function handleAIAnticipation(currentTime, ballPos, ballVel, aiSettings) {
532
+    // Stay near center with subtle lifelike movements
533
+    aiState.targetY = aiState.idleTarget + aiState.breathingOffset + aiState.microAdjustment;
534
+    
535
+    // Switch back to tracking when ball changes direction
536
+    if (ballVel.x > 0) {
537
+        aiState.mode = 'TRACKING';
538
+    }
539
+}
540
+
541
+function executeAIMovement(aiSettings) {
366542
     // Move AI paddle toward target with speed limitation
367543
     let currentY = rightSupport.position.y;
368544
     let deltaY = aiState.targetY - currentY;
369545
     
370
-    if (Math.abs(deltaY) > 5) { // Dead zone
371
-        let movement = deltaY * 0.08 * aiSettings.speed; // Smooth movement
372
-        movement = constrain(movement, -SUPPORT_SPEED * 0.8, SUPPORT_SPEED * 0.8);
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
548
+        
549
+        // Apply swing power during swing phase
550
+        if (aiState.mode === 'SWINGING') {
551
+            baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
552
+        } else if (aiState.mode === 'WINDING_UP') {
553
+            baseSpeed *= 0.7; // Slower during windup for more deliberate movement
554
+        }
555
+        
556
+        // Apply aggression multiplier
557
+        baseSpeed *= (1 + aiState.aggressionLevel * 0.2);
558
+        
559
+        let movement = deltaY * baseSpeed;
560
+        movement = constrain(movement, -SUPPORT_SPEED * 0.9, SUPPORT_SPEED * 0.9);
373561
         
374562
         moveSupportEnhanced(rightSupport, movement);
375563
         
376564
         // Update input buffer for visual effects
377
-        inputBuffer.right = constrain(movement / (SUPPORT_SPEED * 0.8), -1, 1);
565
+        inputBuffer.right = constrain(movement / (SUPPORT_SPEED * 0.9), -1, 1);
378566
     } else {
379567
         // Gradually reduce input buffer when AI is not moving
380
-        inputBuffer.right *= 0.9;
568
+        inputBuffer.right *= 0.95;
381569
     }
382570
 }
383571
 
@@ -617,6 +805,19 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) {
617805
     let glowIntensity = map(ballDistance, 0, PADDLE_GLOW_DISTANCE, 150, 0);
618806
     glowIntensity = constrain(glowIntensity, 0, 150);
619807
     
808
+    // Add AI state-based effects
809
+    if (isAI) {
810
+        // Enhance glow during aggressive states
811
+        if (aiState.mode === 'WINDING_UP') {
812
+            glowIntensity += 50;
813
+        } else if (aiState.mode === 'SWINGING') {
814
+            glowIntensity += 100;
815
+        }
816
+        
817
+        // Aggression-based glow
818
+        glowIntensity += aiState.aggressionLevel * 30;
819
+    }
820
+    
620821
     push();
621822
     translate(pos.x, pos.y);
622823
     rotate(angle);
@@ -624,6 +825,13 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) {
624825
     // Different color scheme for AI paddle
625826
     let paddleColor = isAI ? [255, 100, 100] : [0, 255, 136]; // Red for AI, green for player
626827
     
828
+    // AI mode indicator colors
829
+    if (isAI && aiState.mode === 'WINDING_UP') {
830
+        paddleColor = [255, 150, 50]; // Orange during windup
831
+    } else if (isAI && aiState.mode === 'SWINGING') {
832
+        paddleColor = [255, 50, 50]; // Bright red during swing
833
+    }
834
+    
627835
     // Draw enhanced glow effect first
628836
     if (glowIntensity > 0) {
629837
         fill(paddleColor[0], paddleColor[1], paddleColor[2], glowIntensity * 0.6);
@@ -752,15 +960,25 @@ function drawDebugInfo() {
752960
     text(`R Spring: ${Math.round(rightSpringLength)}px (${((SPRING_LENGTH/rightSpringLength - 1) * 100).toFixed(0)}%)`, 10, 95);
753961
     text(`Input: L=${inputBuffer.left.toFixed(2)} R=${inputBuffer.right.toFixed(2)}`, 10, 110);
754962
     
755
-    // AI debug info
963
+    // Advanced AI debug info
756964
     if (aiEnabled) {
757
-        text(`AI Target: ${Math.round(aiState.targetY)} | Ball Y: ${Math.round(ball.position.y)}`, 10, 125);
758
-        text(`Ball Vel: X=${ball.velocity.x.toFixed(1)} Y=${ball.velocity.y.toFixed(1)}`, 10, 140);
965
+        text(`AI State: ${aiState.mode} | Aggression: ${aiState.aggressionLevel.toFixed(2)}`, 10, 125);
966
+        text(`Target: ${Math.round(aiState.targetY)} | Intercept: ${Math.round(aiState.interceptY)}`, 10, 140);
967
+        text(`Ball: (${Math.round(ball.position.x)}, ${Math.round(ball.position.y)}) Vel: (${ball.velocity.x.toFixed(1)}, ${ball.velocity.y.toFixed(1)})`, 10, 155);
968
+        
969
+        // Show AI technique indicators
970
+        if (aiState.mode === 'WINDING_UP') {
971
+            fill(255, 150, 50, 200);
972
+            text("AI WINDING UP FOR POWER SHOT", 10, 175);
973
+        } else if (aiState.mode === 'SWINGING') {
974
+            fill(255, 50, 50, 200);
975
+            text("⚡ AI POWER SWING!", 10, 175);
976
+        }
759977
     }
760978
     
761979
     // Mouse/touch input debug
762980
     if (mouseInput.active) {
763
-        text(`Mouse: Active | Side: ${mouseX < width/2 ? 'Left' : 'Right'} | Y: ${mouseY}`, 10, 155);
981
+        text(`Mouse: Active | Side: ${mouseX < width/2 ? 'Left' : 'Right'} | Y: ${mouseY}`, 10, 190);
764982
     }
765983
 }
766984
 
@@ -958,7 +1176,7 @@ function keyPressed() {
9581176
     if (key === 'm' || key === 'M') {
9591177
         aiEnabled = !aiEnabled;
9601178
         gameMode = aiEnabled ? 'vs-cpu' : 'vs-human';
961
-        console.log(`🎮 Switched to ${gameMode} mode`);
1179
+        console.log(`Switched to ${gameMode} mode`);
9621180
     }
9631181
     
9641182
     // Change AI difficulty (only during gameplay)
@@ -970,7 +1188,7 @@ function keyPressed() {
9701188
         } else {
9711189
             aiState.difficulty = 'easy';
9721190
         }
973
-        console.log(`🤖 AI difficulty: ${aiState.difficulty}`);
1191
+        console.log(`AI difficulty: ${aiState.difficulty}`);
9741192
     }
9751193
     
9761194
     // Reset game with spacebar
@@ -991,11 +1209,13 @@ function keyPressed() {
9911209
         // Reset AI state
9921210
         aiState.targetY = height / 2;
9931211
         aiState.lastUpdateTime = 0;
1212
+        aiState.mode = 'ANTICIPATING';
1213
+        aiState.aggressionLevel = AI_SETTINGS[aiState.difficulty].aggression;
9941214
         
9951215
         // Clear particles
9961216
         particles = [];
9971217
         
998
-        console.log("🔄 Game reset!");
1218
+        console.log("Game reset!");
9991219
     }
10001220
     
10011221
     // Return to menu with ESC
@@ -1003,7 +1223,7 @@ function keyPressed() {
10031223
         gameState = 'menu';
10041224
         gameStarted = false;
10051225
         particles = [];
1006
-        console.log("📋 Returned to menu");
1226
+        console.log("Returned to menu");
10071227
     }
10081228
 }
10091229
 
@@ -1065,7 +1285,7 @@ function startGameWithSelection() {
10651285
     // Clear particles
10661286
     particles = [];
10671287
     
1068
-    console.log(`🎮 Started ${gameMode} mode${aiEnabled ? ' - Difficulty: ' + aiState.difficulty : ''}`);
1288
+    console.log(`Started ${gameMode} mode${aiEnabled ? ' - Difficulty: ' + aiState.difficulty : ''}`);
10691289
 }
10701290
 
10711291
 function keyReleased() {