zeroed-some/sprong / 45be1de

Browse files

some constants+more for the ai, remove emoji

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
45be1dead2d21ea218afd80413c334b05e3c338d
Parents
dffea2f
Tree
f241842

2 changed files

StatusFile+-
M js/ai.js 169 23
M js/rendering.js 10 4
js/ai.jsmodified
@@ -1,5 +1,16 @@
11
 // ai.js - AI logic and behavior
22
 
3
+// ============= AI TECHNIQUE CONSTANTS =============
4
+const AI_WINDUP_SPEED = 0.15;        // Base oscillation speed
5
+const AI_WINDUP_SMOOTHNESS = 0.92;   // Smooth transitions
6
+const AI_WINDUP_RADIUS = 40;         // Circular motion radius
7
+const AI_WINDUP_MIN_TIME = 300;      // Minimum windup duration
8
+const AI_WINDUP_MAX_TIME = 600;      // Maximum windup duration
9
+const AI_BOP_AT_PEAK_CHANCE = 0.4;   // Chance to bop at windup peak
10
+const AI_CIRCULAR_MOTION = 0.7;      // How circular vs linear the motion is
11
+const AI_MOMENTUM_CARRY = 0.85;      // How much momentum carries between moves
12
+const AI_PHASE_SPEED = 0.08;         // Speed of phase progression (radians per frame)
13
+
314
 // ============= AI SETTINGS =============
415
 const AI_SETTINGS = {
516
     easy: {
@@ -9,7 +20,13 @@ const AI_SETTINGS = {
920
         prediction: 0.3,
1021
         aggression: 0.2,
1122
         oscillation: 0.3,
12
-        bopChance: 0.25
23
+        bopChance: 0.25,
24
+        // New windup parameters
25
+        windupSpeed: 0.1,
26
+        windupRadius: 30,
27
+        comboBopChance: 0.1,
28
+        circularMotion: 0.4,
29
+        phaseSpeed: 0.06
1330
     },
1431
     medium: {
1532
         reactionTime: 250,
@@ -18,7 +35,13 @@ const AI_SETTINGS = {
1835
         prediction: 0.6,
1936
         aggression: 0.5,
2037
         oscillation: 0.7,
21
-        bopChance: 0.55
38
+        bopChance: 0.55,
39
+        // New windup parameters
40
+        windupSpeed: 0.15,
41
+        windupRadius: 40,
42
+        comboBopChance: 0.3,
43
+        circularMotion: 0.6,
44
+        phaseSpeed: 0.08
2245
     },
2346
     hard: {
2447
         reactionTime: 150,
@@ -27,7 +50,13 @@ const AI_SETTINGS = {
2750
         prediction: 0.8,
2851
         aggression: 0.8,
2952
         oscillation: 1.0,
30
-        bopChance: 0.85
53
+        bopChance: 0.85,
54
+        // New windup parameters
55
+        windupSpeed: 0.2,
56
+        windupRadius: 50,
57
+        comboBopChance: 0.5,
58
+        circularMotion: 0.8,
59
+        phaseSpeed: 0.1
3160
     }
3261
 };
3362
 
@@ -48,7 +77,22 @@ let aiState = {
4877
     aggressionLevel: 0.5,
4978
     lastHitTime: 0,
5079
     
51
-    // Oscillation parameters
80
+    // Enhanced windup system
81
+    windupPhase: 0,          // 0 to 2π for circular motion
82
+    windupVelocity: 0,       // Current oscillation speed
83
+    windupMomentum: {x: 0, y: 0}, // Momentum vector
84
+    windupCenter: 200,       // Center point of circular motion
85
+    peakReached: false,      // Track if we hit peak velocity
86
+    comboBop: false,         // Planning windup+bop combo
87
+    maxVelocityPhase: 0,     // Phase where max velocity occurs
88
+    
89
+    // Motion tracking
90
+    lastPositions: [],       // Track last N positions for smoothing
91
+    currentVelocity: 0,      // Actual paddle velocity
92
+    targetVelocity: 0,       // Desired paddle velocity
93
+    smoothedTarget: 200,     // Smoothed target position
94
+    
95
+    // Original oscillation parameters (keeping for compatibility)
5296
     windupDistance: 120,
5397
     swingPower: 1.05,
5498
     timingWindow: 40,
@@ -182,25 +226,48 @@ function handleAITracking(currentTime, ballPos, ballVel, aiSettings,
182226
         }
183227
     }
184228
     
185
-    // Execute bop
229
+    // Execute bop at the right moment
186230
     if (aiState.consideringBop && ballApproaching) {
187231
         let timeToBop = currentTime - aiState.bopDecisionTime;
188232
         let paddleY = rightPaddle.position.y;
189233
         let distanceToBall = Math.abs(ballPos.y - paddleY);
190234
         
235
+        // Refined bop execution conditions
191236
         let shouldBop = timeToBop > aiState.bopTiming && 
192
-                       ballDistance < 150 &&
193
-                       distanceToBall < PADDLE_HEIGHT / 2 + 20 &&
237
+                       ballDistance < 150 && // Close enough
238
+                       distanceToBall < PADDLE_HEIGHT / 2 + 20 && // Paddle can reach ball
194239
                        !bopState.right.active;
195240
         
241
+        // Special handling for combo bops during windup
242
+        if (aiState.comboBop && aiState.mode === 'WINDING_UP') {
243
+            // Execute bop at peak velocity during windup
244
+            shouldBop = shouldBop || (aiState.peakReached && 
245
+                                     ballDistance < 180 && 
246
+                                     distanceToBall < PADDLE_HEIGHT / 2 + 30);
247
+        }
248
+        
196249
         if (shouldBop) {
197250
             activateBop('right', currentTime, rightPaddle, rightSupport, engine, particles);
198251
             aiState.consideringBop = false;
199
-            console.log(`AI BOP! Difficulty: ${aiState.difficulty}, Speed: ${ballSpeed.toFixed(1)}`);
252
+            
253
+            if (aiState.comboBop) {
254
+                console.log(`AI COMBO BOP! Phase: ${(aiState.windupPhase % (Math.PI * 2)).toFixed(2)}, Velocity: ${aiState.currentVelocity.toFixed(1)}`);
255
+                aiState.comboBop = false;
256
+                
257
+                // Transition to swing after combo
258
+                if (aiState.mode === 'WINDING_UP') {
259
+                    aiState.mode = 'SWINGING';
260
+                    aiState.swingStartTime = currentTime;
261
+                }
262
+            } else {
263
+                console.log(`AI BOP! Difficulty: ${aiState.difficulty}, Speed: ${ballSpeed.toFixed(1)}`);
264
+            }
200265
         }
201266
         
267
+        // Cancel bop if opportunity missed
202268
         if (ballDistance > 200 || ballDistance < 50) {
203269
             aiState.consideringBop = false;
270
+            aiState.comboBop = false;
204271
         }
205272
     }
206273
     
@@ -232,8 +299,20 @@ function handleAITracking(currentTime, ballPos, ballVel, aiSettings,
232299
                               Math.random() < aiSettings.oscillation * aiState.aggressionLevel * 0.3;
233300
             
234301
             if (shouldWindUp) {
302
+                // Start winding up for power shot
235303
                 aiState.mode = 'WINDING_UP';
236304
                 aiState.windupStartTime = currentTime;
305
+                
306
+                // Initialize circular windup
307
+                aiState.windupCenter = paddlePos.y; // Start from current position
308
+                aiState.windupPhase = 0;
309
+                aiState.windupMomentum = {x: 0, y: 0};
310
+                aiState.smoothedTarget = paddlePos.y;
311
+                aiState.lastPositions = [paddlePos.y];
312
+                aiState.peakReached = false;
313
+                aiState.comboBop = false;
314
+                
315
+                // Determine initial direction based on intercept position
237316
                 aiState.windupDirection = aiState.interceptY > paddlePos.y ? -1 : 1;
238317
             } else {
239318
                 aiState.targetY = lerp(aiState.targetY, targetAnchorForIntercept, 0.3);
@@ -247,34 +326,86 @@ function handleAITracking(currentTime, ballPos, ballVel, aiSettings,
247326
 function handleAIWindup(currentTime, ballPos, ballVel, aiSettings, 
248327
                        rightPaddle, rightSupport, height, width) {
249328
     let windupTime = currentTime - aiState.windupStartTime;
250
-    let maxWindupTime = 800;
251
-    let timeProgress = Math.min(windupTime / maxWindupTime, 1.0);
329
+    let maxWindupTime = AI_WINDUP_MIN_TIME + (AI_WINDUP_MAX_TIME - AI_WINDUP_MIN_TIME) * aiState.aggressionLevel;
252330
     
253
-    let easedProgress = timeProgress < 0.5 
254
-        ? 2 * timeProgress * timeProgress 
255
-        : 1 - Math.pow(-2 * timeProgress + 2, 3) / 2;
331
+    // Update windup phase for circular motion
332
+    let phaseSpeed = aiSettings.phaseSpeed * (1 + aiState.aggressionLevel * 0.5);
333
+    aiState.windupPhase += phaseSpeed;
256334
     
257
-    aiState.windupProgress = easedProgress;
335
+    // Calculate circular motion with momentum
336
+    let radius = aiSettings.windupRadius * aiState.aggressionLevel;
337
+    let circularBlend = aiSettings.circularMotion;
258338
     
259
-    let windupTargetY = aiState.interceptY + aiState.windupDirection * 
260
-                       aiState.windupDistance * aiState.aggressionLevel * easedProgress;
261
-    windupTargetY = Math.max(50, Math.min(height - 50, windupTargetY));
339
+    // Pure circular motion components
340
+    let circularX = Math.sin(aiState.windupPhase) * radius * 0.3; // Slight X movement
341
+    let circularY = Math.cos(aiState.windupPhase) * radius;
262342
     
263
-    let anchorOffsetNeeded = calculateAnchorOffset(windupTargetY, rightPaddle.position, rightSupport.position);
264
-    aiState.targetY = windupTargetY + anchorOffsetNeeded;
343
+    // Add momentum for more natural motion
344
+    let targetDeltaY = circularY - (aiState.smoothedTarget - aiState.windupCenter);
345
+    aiState.windupMomentum.y = aiState.windupMomentum.y * AI_MOMENTUM_CARRY + targetDeltaY * (1 - AI_MOMENTUM_CARRY);
346
+    
347
+    // Calculate the target position with smooth circular motion
348
+    let windupTargetY = aiState.windupCenter + aiState.windupMomentum.y;
349
+    
350
+    // Smooth the target for more fluid motion
351
+    aiState.smoothedTarget = aiState.smoothedTarget * AI_WINDUP_SMOOTHNESS + 
352
+                            windupTargetY * (1 - AI_WINDUP_SMOOTHNESS);
353
+    
354
+    // Keep within bounds
355
+    aiState.smoothedTarget = Math.max(50, Math.min(height - 50, aiState.smoothedTarget));
356
+    
357
+    // Convert paddle target to anchor target
358
+    let anchorOffsetNeeded = calculateAnchorOffset(aiState.smoothedTarget, rightPaddle.position, rightSupport.position);
359
+    aiState.targetY = aiState.smoothedTarget + anchorOffsetNeeded;
360
+    
361
+    // Track velocity for combo detection
362
+    if (aiState.lastPositions.length > 5) {
363
+        aiState.lastPositions.shift();
364
+    }
365
+    aiState.lastPositions.push(aiState.smoothedTarget);
265366
     
367
+    // Calculate current velocity
368
+    if (aiState.lastPositions.length > 1) {
369
+        let recentDelta = aiState.lastPositions[aiState.lastPositions.length - 1] - 
370
+                         aiState.lastPositions[aiState.lastPositions.length - 2];
371
+        aiState.currentVelocity = Math.abs(recentDelta);
372
+        
373
+        // Check if we're at peak velocity (good time for combo bop)
374
+        if (aiState.currentVelocity > radius * phaseSpeed * 0.8 && !aiState.peakReached) {
375
+            aiState.peakReached = true;
376
+            aiState.maxVelocityPhase = aiState.windupPhase;
377
+            
378
+            // Consider combo bop at peak
379
+            if (!aiState.comboBop && !aiState.consideringBop && !bopState.right.active &&
380
+                Math.random() < aiSettings.comboBopChance * aiState.aggressionLevel) {
381
+                aiState.comboBop = true;
382
+                aiState.consideringBop = true;
383
+                aiState.bopDecisionTime = currentTime;
384
+                aiState.bopTiming = 50; // Quick bop at peak
385
+                console.log("AI planning COMBO: Windup + Bop!");
386
+            }
387
+        }
388
+    }
389
+    
390
+    // Check if it's time to swing
266391
     let ballDistance = width - ballPos.x;
267392
     let ballSpeed = Math.sqrt(ballVel.x * ballVel.x + ballVel.y * ballVel.y);
268393
     
269394
     let shouldSwing = windupTime > maxWindupTime || 
270395
                      ballDistance < 120 || 
271396
                      ballSpeed > 6 ||
272
-                     easedProgress > 0.85;
397
+                     (aiState.windupPhase > Math.PI * 2 && ballDistance < 200);
273398
     
274399
     if (shouldSwing) {
275400
         aiState.mode = 'SWINGING';
276401
         aiState.swingStartTime = currentTime;
277
-        aiState.windupProgress = 0;
402
+        aiState.windupPhase = 0;
403
+        aiState.peakReached = false;
404
+        aiState.comboBop = false;
405
+        aiState.lastPositions = [];
406
+        
407
+        // Carry momentum into swing
408
+        aiState.targetVelocity = aiState.currentVelocity * 2;
278409
     }
279410
 }
280411
 
@@ -333,13 +464,27 @@ function executeAIMovement(aiSettings, rightSupport) {
333464
     if (Math.abs(deltaY) > 1) {
334465
         let baseSpeed = 0.12 * aiSettings.speed;
335466
         
467
+        // Apply swing power during swing phase
336468
         if (aiState.mode === 'SWINGING') {
337469
             baseSpeed *= aiState.swingPower * (1 + aiState.aggressionLevel * 0.3);
470
+            
471
+            // Add momentum from windup if available
472
+            if (aiState.targetVelocity > 0) {
473
+                baseSpeed *= (1 + aiState.targetVelocity * 0.1);
474
+                aiState.targetVelocity *= 0.9; // Decay momentum
475
+            }
338476
         } else if (aiState.mode === 'WINDING_UP') {
339
-            let windupSpeedMultiplier = 0.3 + (aiState.windupProgress * 0.4);
340
-            baseSpeed *= windupSpeedMultiplier;
477
+            // Enhanced windup speed based on phase and settings
478
+            let windupSpeedMultiplier = aiSettings.windupSpeed / AI_WINDUP_SPEED;
479
+            baseSpeed *= (1.5 + windupSpeedMultiplier);
480
+            
481
+            // Add extra speed at peak velocity points
482
+            if (aiState.peakReached) {
483
+                baseSpeed *= 1.3;
484
+            }
341485
         }
342486
         
487
+        // Apply aggression multiplier
343488
         baseSpeed *= (1 + aiState.aggressionLevel * 0.3);
344489
         
345490
         let movement = deltaY * baseSpeed;
@@ -352,6 +497,7 @@ function executeAIMovement(aiSettings, rightSupport) {
352497
         // Update input buffer for visual effects
353498
         window.inputBuffer.right = movement / (SUPPORT_SPEED * 1.1);
354499
     } else {
500
+        // Gradually reduce input buffer when AI is not moving
355501
         window.inputBuffer.right *= 0.95;
356502
     }
357503
 }
js/rendering.jsmodified
@@ -350,20 +350,26 @@ function drawDebugInfo(ball, leftSupport, leftPaddle, rightSupport, rightPaddle,
350350
         text(`Target: ${Math.round(aiState.targetY)} | Intercept: ${Math.round(aiState.interceptY)}`, 10, 140);
351351
         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);
352352
         
353
+        // AI technique indicators
353354
         if (aiState.mode === 'WINDING_UP') {
354355
             fill(255, 150, 50, 200);
355
-            text("🔄 AI WINDING UP FOR POWER SHOT", 10, 175);
356
+            text(`AI WINDING UP | Phase: ${(aiState.windupPhase % (Math.PI * 2)).toFixed(2)} | Velocity: ${aiState.currentVelocity.toFixed(1)}`, 10, 175);
357
+            
358
+            if (aiState.comboBop) {
359
+                fill(255, 50, 255, 200);
360
+                text("⚡ COMBO PLANNED!", 10, 190);
361
+            }
356362
         } else if (aiState.mode === 'SWINGING') {
357363
             fill(255, 50, 50, 200);
358
-            text("⚡ AI POWER SWING!", 10, 175);
364
+            text("AI POWER SWING!", 10, 175);
359365
         } else if (aiState.consideringBop) {
360366
             fill(255, 255, 100, 200);
361
-            text("💥 AI PREPARING BOP!", 10, 175);
367
+            text("AI PREPARING BOP!", 10, 175);
362368
         }
363369
         
364370
         if (bopState.right.active) {
365371
             fill(255, 255, 0, 255);
366
-            text("🚀 AI BOPPING!", 10, 190);
372
+            text("AI BOPPING!", 10, 190);
367373
         }
368374
     }
369375
 }