@@ -1,5 +1,5 @@ |
| 1 | 1 | // Matter.js module aliases |
| 2 | | -const Body = Matter.Body; |
| 2 | +const Body = Matter.Body; |
| 3 | 3 | const World = Matter.World; |
| 4 | 4 | const Engine = Matter.Engine; |
| 5 | 5 | const Bodies = Matter.Bodies; |
@@ -17,18 +17,18 @@ let leftSupport, leftPaddle, leftSpring; |
| 17 | 17 | let rightSupport, rightPaddle, rightSpring; |
| 18 | 18 | |
| 19 | 19 | // 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; |
| 22 | 25 | 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 | 27 | // Menu state |
| 28 | 28 | let menuState = { |
| 29 | | - selectedOption: 0, // 0 = 1 Player, 1 = 2 Player |
| 29 | + selectedOption: 0, // 0 = 1 Player, 1 = 2 Player |
| 30 | 30 | 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 |
| 32 | 32 | difficulties: ['Easy', 'Medium', 'Hard'], |
| 33 | 33 | showDifficulty: true |
| 34 | 34 | }; |
@@ -39,7 +39,27 @@ let aiState = { |
| 39 | 39 | reactionDelay: 0, |
| 40 | 40 | difficulty: 'medium', // 'easy', 'medium', 'hard' |
| 41 | 41 | 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 |
| 43 | 63 | }; |
| 44 | 64 | |
| 45 | 65 | // Player input |
@@ -81,9 +101,9 @@ const MOUSE_SPEED_LIMIT = 4; // Max speed for mouse movement |
| 81 | 101 | const MOUSE_LAG_FACTOR = 0.12; // How much lag in mouse following |
| 82 | 102 | const TOUCH_SENSITIVITY = 1.2; // Touch movement multiplier |
| 83 | 103 | |
| 84 | | -// Spring physics constants (toned down for stability) |
| 104 | +// Spring physics constants (adjusted for better oscillation) |
| 85 | 105 | 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 |
| 87 | 107 | const SPRING_DAMPING = 0.6; // More damping = less erratic |
| 88 | 108 | const SPRING_STIFFNESS = 0.025; // Slightly lower for smoother motion |
| 89 | 109 | |
@@ -93,19 +113,25 @@ const AI_SETTINGS = { |
| 93 | 113 | reactionTime: 400, // ms delay |
| 94 | 114 | accuracy: 0.7, // 70% accuracy |
| 95 | 115 | 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 |
| 97 | 119 | }, |
| 98 | 120 | medium: { |
| 99 | 121 | reactionTime: 250, |
| 100 | 122 | accuracy: 0.85, |
| 101 | 123 | speed: 0.8, |
| 102 | | - prediction: 0.6 |
| 124 | + prediction: 0.6, |
| 125 | + aggression: 0.5, // Moderate aggression |
| 126 | + oscillation: 0.7 // Good oscillation technique |
| 103 | 127 | }, |
| 104 | 128 | hard: { |
| 105 | 129 | reactionTime: 150, |
| 106 | 130 | accuracy: 0.95, |
| 107 | 131 | speed: 1.0, |
| 108 | | - prediction: 0.8 |
| 132 | + prediction: 0.8, |
| 133 | + aggression: 0.8, // High aggression |
| 134 | + oscillation: 1.0 // Master-level oscillation |
| 109 | 135 | } |
| 110 | 136 | }; |
| 111 | 137 | |
@@ -152,11 +178,6 @@ function setup() { |
| 152 | 178 | leftSupport, leftPaddle, leftSpring, |
| 153 | 179 | rightSupport, rightPaddle, rightSpring |
| 154 | 180 | ]); |
| 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"); |
| 160 | 181 | } |
| 161 | 182 | |
| 162 | 183 | function createSpringPaddleSystem(side) { |
@@ -339,45 +360,212 @@ function handleAI() { |
| 339 | 360 | let ballVel = ball.velocity; |
| 340 | 361 | let aiSettings = AI_SETTINGS[aiState.difficulty]; |
| 341 | 362 | |
| 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 | + } |
| 344 | 448 | |
| 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; |
| 348 | 452 | |
| 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; |
| 357 | 463 | |
| 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; |
| 360 | 467 | |
| 361 | | - aiState.targetY = targetY; |
| 362 | | - aiState.lastUpdateTime = currentTime; |
| 468 | + } else { |
| 469 | + // Simple tracking without oscillation |
| 470 | + aiState.targetY = aiState.interceptY; |
| 363 | 471 | } |
| 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 |
| 364 | 479 | } |
| 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 |
| 365 | 490 | |
| 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) { |
| 366 | 542 | // Move AI paddle toward target with speed limitation |
| 367 | 543 | let currentY = rightSupport.position.y; |
| 368 | 544 | let deltaY = aiState.targetY - currentY; |
| 369 | 545 | |
| 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); |
| 373 | 561 | |
| 374 | 562 | moveSupportEnhanced(rightSupport, movement); |
| 375 | 563 | |
| 376 | 564 | // 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); |
| 378 | 566 | } else { |
| 379 | 567 | // Gradually reduce input buffer when AI is not moving |
| 380 | | - inputBuffer.right *= 0.9; |
| 568 | + inputBuffer.right *= 0.95; |
| 381 | 569 | } |
| 382 | 570 | } |
| 383 | 571 | |
@@ -617,6 +805,19 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) { |
| 617 | 805 | let glowIntensity = map(ballDistance, 0, PADDLE_GLOW_DISTANCE, 150, 0); |
| 618 | 806 | glowIntensity = constrain(glowIntensity, 0, 150); |
| 619 | 807 | |
| 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 | + |
| 620 | 821 | push(); |
| 621 | 822 | translate(pos.x, pos.y); |
| 622 | 823 | rotate(angle); |
@@ -624,6 +825,13 @@ function drawSinglePaddleEnhanced(paddle, ballDistance) { |
| 624 | 825 | // Different color scheme for AI paddle |
| 625 | 826 | let paddleColor = isAI ? [255, 100, 100] : [0, 255, 136]; // Red for AI, green for player |
| 626 | 827 | |
| 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 | + |
| 627 | 835 | // Draw enhanced glow effect first |
| 628 | 836 | if (glowIntensity > 0) { |
| 629 | 837 | fill(paddleColor[0], paddleColor[1], paddleColor[2], glowIntensity * 0.6); |
@@ -752,15 +960,25 @@ function drawDebugInfo() { |
| 752 | 960 | text(`R Spring: ${Math.round(rightSpringLength)}px (${((SPRING_LENGTH/rightSpringLength - 1) * 100).toFixed(0)}%)`, 10, 95); |
| 753 | 961 | text(`Input: L=${inputBuffer.left.toFixed(2)} R=${inputBuffer.right.toFixed(2)}`, 10, 110); |
| 754 | 962 | |
| 755 | | - // AI debug info |
| 963 | + // Advanced AI debug info |
| 756 | 964 | 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 | + } |
| 759 | 977 | } |
| 760 | 978 | |
| 761 | 979 | // Mouse/touch input debug |
| 762 | 980 | 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); |
| 764 | 982 | } |
| 765 | 983 | } |
| 766 | 984 | |
@@ -958,7 +1176,7 @@ function keyPressed() { |
| 958 | 1176 | if (key === 'm' || key === 'M') { |
| 959 | 1177 | aiEnabled = !aiEnabled; |
| 960 | 1178 | gameMode = aiEnabled ? 'vs-cpu' : 'vs-human'; |
| 961 | | - console.log(`🎮 Switched to ${gameMode} mode`); |
| 1179 | + console.log(`Switched to ${gameMode} mode`); |
| 962 | 1180 | } |
| 963 | 1181 | |
| 964 | 1182 | // Change AI difficulty (only during gameplay) |
@@ -970,7 +1188,7 @@ function keyPressed() { |
| 970 | 1188 | } else { |
| 971 | 1189 | aiState.difficulty = 'easy'; |
| 972 | 1190 | } |
| 973 | | - console.log(`🤖 AI difficulty: ${aiState.difficulty}`); |
| 1191 | + console.log(`AI difficulty: ${aiState.difficulty}`); |
| 974 | 1192 | } |
| 975 | 1193 | |
| 976 | 1194 | // Reset game with spacebar |
@@ -991,11 +1209,13 @@ function keyPressed() { |
| 991 | 1209 | // Reset AI state |
| 992 | 1210 | aiState.targetY = height / 2; |
| 993 | 1211 | aiState.lastUpdateTime = 0; |
| 1212 | + aiState.mode = 'ANTICIPATING'; |
| 1213 | + aiState.aggressionLevel = AI_SETTINGS[aiState.difficulty].aggression; |
| 994 | 1214 | |
| 995 | 1215 | // Clear particles |
| 996 | 1216 | particles = []; |
| 997 | 1217 | |
| 998 | | - console.log("🔄 Game reset!"); |
| 1218 | + console.log("Game reset!"); |
| 999 | 1219 | } |
| 1000 | 1220 | |
| 1001 | 1221 | // Return to menu with ESC |
@@ -1003,7 +1223,7 @@ function keyPressed() { |
| 1003 | 1223 | gameState = 'menu'; |
| 1004 | 1224 | gameStarted = false; |
| 1005 | 1225 | particles = []; |
| 1006 | | - console.log("📋 Returned to menu"); |
| 1226 | + console.log("Returned to menu"); |
| 1007 | 1227 | } |
| 1008 | 1228 | } |
| 1009 | 1229 | |
@@ -1065,7 +1285,7 @@ function startGameWithSelection() { |
| 1065 | 1285 | // Clear particles |
| 1066 | 1286 | particles = []; |
| 1067 | 1287 | |
| 1068 | | - console.log(`🎮 Started ${gameMode} mode${aiEnabled ? ' - Difficulty: ' + aiState.difficulty : ''}`); |
| 1288 | + console.log(`Started ${gameMode} mode${aiEnabled ? ' - Difficulty: ' + aiState.difficulty : ''}`); |
| 1069 | 1289 | } |
| 1070 | 1290 | |
| 1071 | 1291 | function keyReleased() { |