sprong Public
Comparing changes
Choose two branches to see what's changed or to start a new pull request.
Able to merge.
These branches can be automatically merged.
2 commits
5 files changed
1 contributor
Commits on rotation
.DS_StoreaddedBinary file changed.
js/ai.jsmodified@@ -16,6 +16,13 @@ const AI_NERVOUS_ENERGY = 0.5; // Random fidgeting energy | ||
| 16 | 16 | const AI_PREDICTIVE_MOVEMENT = 0.7; // How much AI moves based on prediction |
| 17 | 17 | const AI_TRACKING_AGGRESSION = 0.15; // How aggressively AI tracks the ball |
| 18 | 18 | |
| 19 | +// AI Rotation constants | |
| 20 | +const AI_ROTATION_UPDATE_RATE = 150; // How often AI reconsiders rotation (ms) | |
| 21 | +const AI_ROTATION_SMOOTHING = 0.08; // How smoothly AI rotates | |
| 22 | +const AI_DEFENSIVE_ANGLE = 0.2; // Slight angle for defensive shots | |
| 23 | +const AI_OFFENSIVE_ANGLE = 0.4; // Larger angle for offensive shots | |
| 24 | +const AI_TRICK_SHOT_ANGLE = 0.6; // Maximum angle for trick shots | |
| 25 | +const AI_ROTATION_PREDICTION = 1.2; // How far ahead AI predicts for rotation | |
| 19 | 26 | |
| 20 | 27 | // ============= AI SETTINGS ============= |
| 21 | 28 | const AI_SETTINGS = { |
@@ -33,7 +40,12 @@ const AI_SETTINGS = { | ||
| 33 | 40 | circularMotion: 0.4, |
| 34 | 41 | phaseSpeed: 0.06, |
| 35 | 42 | idleMovement: 0.3, |
| 36 | - trackingAggression: 0.1 | |
| 43 | + trackingAggression: 0.1, | |
| 44 | + // Rotation settings | |
| 45 | + rotationUse: 0.2, // How often AI uses rotation | |
| 46 | + rotationAccuracy: 0.5, // How accurately AI calculates angle | |
| 47 | + rotationSpeed: 0.6, // How fast AI rotates | |
| 48 | + rotationAnticipation: 0.3 // How well AI predicts needed angle | |
| 37 | 49 | }, |
| 38 | 50 | medium: { |
| 39 | 51 | reactionTime: 200, |
@@ -49,7 +61,12 @@ const AI_SETTINGS = { | ||
| 49 | 61 | circularMotion: 0.6, |
| 50 | 62 | phaseSpeed: 0.08, |
| 51 | 63 | idleMovement: 0.5, |
| 52 | - trackingAggression: 0.15 | |
| 64 | + trackingAggression: 0.15, | |
| 65 | + // Rotation settings | |
| 66 | + rotationUse: 0.5, | |
| 67 | + rotationAccuracy: 0.7, | |
| 68 | + rotationSpeed: 0.8, | |
| 69 | + rotationAnticipation: 0.6 | |
| 53 | 70 | }, |
| 54 | 71 | hard: { |
| 55 | 72 | reactionTime: 100, |
@@ -65,7 +82,12 @@ const AI_SETTINGS = { | ||
| 65 | 82 | circularMotion: 0.8, |
| 66 | 83 | phaseSpeed: 0.12, |
| 67 | 84 | idleMovement: 0.8, |
| 68 | - trackingAggression: 0.25 | |
| 85 | + trackingAggression: 0.25, | |
| 86 | + // Rotation settings | |
| 87 | + rotationUse: 0.8, | |
| 88 | + rotationAccuracy: 0.9, | |
| 89 | + rotationSpeed: 1.0, | |
| 90 | + rotationAnticipation: 0.85 | |
| 69 | 91 | } |
| 70 | 92 | }; |
| 71 | 93 | |
@@ -116,7 +138,14 @@ let aiState = { | ||
| 116 | 138 | // AI Bop system |
| 117 | 139 | consideringBop: false, |
| 118 | 140 | bopDecisionTime: 0, |
| 119 | - bopTiming: 200 | |
| 141 | + bopTiming: 200, | |
| 142 | + | |
| 143 | + // AI Rotation system | |
| 144 | + targetRotation: 0, // Desired rotation angle | |
| 145 | + rotationMode: 'NEUTRAL', // NEUTRAL, OFFENSIVE, DEFENSIVE, TRICK_SHOT | |
| 146 | + lastRotationUpdate: 0, // Time of last rotation decision | |
| 147 | + plannedShotAngle: 0, // Angle AI wants to send ball | |
| 148 | + rotationConfidence: 0 // How confident AI is in its rotation choice | |
| 120 | 149 | }; |
| 121 | 150 | |
| 122 | 151 | // ============= MAIN AI HANDLER ============= |
@@ -129,6 +158,7 @@ function handleAI(currentTime, ball, rightPaddle, rightSupport, | ||
| 129 | 158 | |
| 130 | 159 | updateAIAggression(leftScore, rightScore); |
| 131 | 160 | updateAILifelikeBehavior(currentTime, height); |
| 161 | + updateAIRotation(currentTime, ball, rightPaddle, width, height, aiSettings); | |
| 132 | 162 | |
| 133 | 163 | switch (aiState.mode) { |
| 134 | 164 | case 'TRACKING': |
@@ -561,7 +591,126 @@ function executeAIMovement(aiSettings, rightSupport) { | ||
| 561 | 591 | } |
| 562 | 592 | } |
| 563 | 593 | |
| 564 | -// Utility function - should match p5.js lerp | |
| 565 | -function lerp(start, stop, amt) { | |
| 566 | - return amt * (stop - start) + start; | |
| 567 | -} | |
| 594 | +// ============= AI ROTATION SYSTEM ============= | |
| 595 | +function updateAIRotation(currentTime, ball, rightPaddle, width, height, aiSettings) { | |
| 596 | + // Only update rotation decision periodically | |
| 597 | + if (currentTime - aiState.lastRotationUpdate < AI_ROTATION_UPDATE_RATE) { | |
| 598 | + // Still apply rotation smoothly even if not updating decision | |
| 599 | + applyAIRotation(aiSettings); | |
| 600 | + return; | |
| 601 | + } | |
| 602 | + | |
| 603 | + aiState.lastRotationUpdate = currentTime; | |
| 604 | + | |
| 605 | + let ballPos = ball.position; | |
| 606 | + let ballVel = ball.velocity; | |
| 607 | + let paddlePos = rightPaddle.position; | |
| 608 | + | |
| 609 | + // Check if AI should use rotation | |
| 610 | + if (Math.random() > aiSettings.rotationUse) { | |
| 611 | + aiState.rotationMode = 'NEUTRAL'; | |
| 612 | + aiState.targetRotation = 0; | |
| 613 | + applyAIRotation(aiSettings); | |
| 614 | + return; | |
| 615 | + } | |
| 616 | + | |
| 617 | + // Calculate ball approach | |
| 618 | + let ballApproaching = ballVel.x > 0; | |
| 619 | + let ballDistance = width - ballPos.x; | |
| 620 | + let timeToReach = ballDistance / Math.abs(ballVel.x); | |
| 621 | + | |
| 622 | + // Predict where ball will be | |
| 623 | + let predictedBallY = ballPos.y + ballVel.y * timeToReach * AI_ROTATION_PREDICTION * aiSettings.rotationAnticipation; | |
| 624 | + | |
| 625 | + // Bounce prediction | |
| 626 | + if (predictedBallY < 50) { | |
| 627 | + predictedBallY = 100 - predictedBallY; | |
| 628 | + } else if (predictedBallY > height - 50) { | |
| 629 | + predictedBallY = 2 * (height - 50) - predictedBallY; | |
| 630 | + } | |
| 631 | + | |
| 632 | + // Decide rotation strategy | |
| 633 | + if (!ballApproaching || ballDistance > 300) { | |
| 634 | + // Return to neutral when ball is far | |
| 635 | + aiState.rotationMode = 'NEUTRAL'; | |
| 636 | + aiState.targetRotation = 0; | |
| 637 | + } else if (aiState.mode === 'WINDING_UP' || aiState.mode === 'SWINGING') { | |
| 638 | + // Offensive rotation during power shots | |
| 639 | + aiState.rotationMode = 'OFFENSIVE'; | |
| 640 | + | |
| 641 | + // Calculate desired shot angle | |
| 642 | + let targetY = height / 2; // Aim for center by default | |
| 643 | + | |
| 644 | + // Try to aim away from player | |
| 645 | + if (paddlePos.y < height / 2) { | |
| 646 | + targetY = height - 80; // Aim down | |
| 647 | + } else { | |
| 648 | + targetY = 80; // Aim up | |
| 649 | + } | |
| 650 | + | |
| 651 | + // Calculate required angle | |
| 652 | + let deltaY = targetY - paddlePos.y; | |
| 653 | + let desiredAngle = Math.atan2(deltaY, width - paddlePos.x) * AI_OFFENSIVE_ANGLE; | |
| 654 | + | |
| 655 | + // Add some inaccuracy based on difficulty | |
| 656 | + let error = (Math.random() - 0.5) * (1 - aiSettings.rotationAccuracy) * 0.5; | |
| 657 | + desiredAngle += error; | |
| 658 | + | |
| 659 | + // Clamp angle | |
| 660 | + aiState.targetRotation = Math.max(-ROTATION_MAX_ANGLE, Math.min(ROTATION_MAX_ANGLE, desiredAngle)); | |
| 661 | + | |
| 662 | + } else if (aiState.consideringBop || bopState.right.active) { | |
| 663 | + // Trick shot rotation during bop | |
| 664 | + aiState.rotationMode = 'TRICK_SHOT'; | |
| 665 | + | |
| 666 | + // More extreme angles for bop shots | |
| 667 | + let trickDirection = (paddlePos.y < height / 2) ? 1 : -1; | |
| 668 | + aiState.targetRotation = trickDirection * AI_TRICK_SHOT_ANGLE * aiSettings.rotationAccuracy; | |
| 669 | + | |
| 670 | + } else if (Math.abs(predictedBallY - paddlePos.y) < 30) { | |
| 671 | + // Defensive slight angle when ball is coming straight | |
| 672 | + aiState.rotationMode = 'DEFENSIVE'; | |
| 673 | + | |
| 674 | + // Slight angle to control return | |
| 675 | + let defensiveDirection = (predictedBallY < height / 2) ? -1 : 1; | |
| 676 | + aiState.targetRotation = defensiveDirection * AI_DEFENSIVE_ANGLE * aiSettings.rotationAccuracy; | |
| 677 | + | |
| 678 | + } else { | |
| 679 | + // Normal tracking | |
| 680 | + aiState.rotationMode = 'NEUTRAL'; | |
| 681 | + aiState.targetRotation = 0; | |
| 682 | + } | |
| 683 | + | |
| 684 | + // Apply rotation confidence based on AI state | |
| 685 | + aiState.rotationConfidence = aiSettings.rotationAccuracy; | |
| 686 | + if (aiState.mode === 'RECOVERING') { | |
| 687 | + aiState.rotationConfidence *= 0.5; // Less confident when recovering | |
| 688 | + } | |
| 689 | + | |
| 690 | + applyAIRotation(aiSettings); | |
| 691 | +} | |
| 692 | + | |
| 693 | +function applyAIRotation(aiSettings) { | |
| 694 | + // Get current rotation state | |
| 695 | + let rotState = window.rotationState.right; | |
| 696 | + | |
| 697 | + // Calculate rotation input needed | |
| 698 | + let angleDiff = aiState.targetRotation - rotState.currentAngle; | |
| 699 | + let rotationInput = 0; | |
| 700 | + | |
| 701 | + if (Math.abs(angleDiff) > 0.05) { | |
| 702 | + // Determine rotation direction | |
| 703 | + rotationInput = Math.sign(angleDiff); | |
| 704 | + | |
| 705 | + // Scale by AI rotation speed and confidence | |
| 706 | + rotationInput *= aiSettings.rotationSpeed * aiState.rotationConfidence; | |
| 707 | + | |
| 708 | + // Add some imperfection for easier difficulties | |
| 709 | + if (aiSettings.rotationAccuracy < 0.8) { | |
| 710 | + rotationInput += (Math.random() - 0.5) * 0.2 * (1 - aiSettings.rotationAccuracy); | |
| 711 | + } | |
| 712 | + } | |
| 713 | + | |
| 714 | + // Update rotation physics for AI paddle | |
| 715 | + updateRotationPhysics('right', rotationInput, window.rightPaddle); | |
| 716 | +} | |
js/game-systems.jsmodified@@ -1,5 +1,4 @@ | ||
| 1 | 1 | // game-systems.js - Core game mechanics and physics |
| 2 | -// TODO redocument | |
| 3 | 2 | |
| 4 | 3 | // ============= CONSTANTS ============= |
| 5 | 4 | // Canvas settings |
@@ -48,6 +47,18 @@ const BOP_COOLDOWN = 0; // also also self expl. PULL it. | ||
| 48 | 47 | const ANCHOR_RECOIL = 60; // How far the anchor moves backward during bop |
| 49 | 48 | const BOP_VELOCITY_BOOST = 5; // Initial velocity boost for paddle |
| 50 | 49 | |
| 50 | +// Rotation control constants | |
| 51 | +const ROTATION_SPEED = 0.05; // Base rotation speed (radians per frame) | |
| 52 | +const ROTATION_SMOOTHING = 0.15; // Input smoothing for rotation (0-1) | |
| 53 | +const ROTATION_DAMPING = 0.92; // Angular velocity damping (0-1) | |
| 54 | +const ROTATION_MAX_SPEED = 0.2; // Maximum angular velocity (radians per frame) | |
| 55 | +const ROTATION_RESISTANCE = 3.0; // How much harder it is to rotate against momentum | |
| 56 | +const ROTATION_MOMENTUM = 0.85; // How much angular momentum is preserved (0-1) | |
| 57 | +const ROTATION_RETURN_FORCE = 0.02; // Force returning paddle to neutral position | |
| 58 | +const ROTATION_MAX_ANGLE = Math.PI / 4; // Maximum rotation angle (45 degrees) | |
| 59 | +const ROTATION_WITH_MOMENTUM_BOOST = 1.5; // Speed multiplier when rotating with momentum | |
| 60 | +const ROTATION_AGAINST_MOMENTUM_LAG = 0.05; // Lag multiplier when rotating against momentum | |
| 61 | + | |
| 51 | 62 | // ============= BOP SYSTEM ============= |
| 52 | 63 | let bopState = { |
| 53 | 64 | left: { |
@@ -70,6 +81,26 @@ let bopState = { | ||
| 70 | 81 | } |
| 71 | 82 | }; |
| 72 | 83 | |
| 84 | +// ============= ROTATION SYSTEM ============= | |
| 85 | +let rotationState = { | |
| 86 | + left: { | |
| 87 | + targetAngle: 0, // Target angle based on input | |
| 88 | + currentAngle: 0, // Current visual angle | |
| 89 | + angularVelocity: 0, // Current angular velocity | |
| 90 | + angularMomentum: 0, // Angular momentum | |
| 91 | + inputBuffer: 0, // Smoothed rotation input (-1 to 1) | |
| 92 | + lastDirection: 0 // Last input direction for momentum checks | |
| 93 | + }, | |
| 94 | + right: { | |
| 95 | + targetAngle: 0, | |
| 96 | + currentAngle: 0, | |
| 97 | + angularVelocity: 0, | |
| 98 | + angularMomentum: 0, | |
| 99 | + inputBuffer: 0, | |
| 100 | + lastDirection: 0 | |
| 101 | + } | |
| 102 | +}; | |
| 103 | + | |
| 73 | 104 | function handleBopInput(keys, aiEnabled, currentTime, leftPaddle, rightPaddle, leftSupport, rightSupport, engine, particles) { |
| 74 | 105 | // Left player bop - use Left Shift for both modes |
| 75 | 106 | let leftBopPressed = keys['Shift'] && !keys['Control']; |
@@ -356,7 +387,6 @@ function resetBall(ball, world, width, height) { | ||
| 356 | 387 | return ball; |
| 357 | 388 | } |
| 358 | 389 | |
| 359 | -// ============= COLLISION ============= | |
| 360 | 390 | // ============= COLLISION ============= |
| 361 | 391 | function setupCollisionHandlers(engine, ball, leftPaddle, rightPaddle, particles) { |
| 362 | 392 | const Body = Matter.Body; |
@@ -507,4 +537,88 @@ function setupCollisionHandlers(engine, ball, leftPaddle, rightPaddle, particles | ||
| 507 | 537 | } |
| 508 | 538 | } |
| 509 | 539 | }); |
| 540 | +} | |
| 541 | + | |
| 542 | +// ============= ROTATION SYSTEM ============= | |
| 543 | +function handleRotationInput() { | |
| 544 | + // Left paddle rotation (A/D keys) | |
| 545 | + let leftRotationInput = 0; | |
| 546 | + if (keys['a'] || keys['A']) leftRotationInput -= 1; | |
| 547 | + if (keys['d'] || keys['D']) leftRotationInput += 1; | |
| 548 | + | |
| 549 | + // Right paddle rotation (Left/Right arrows, only if AI disabled) | |
| 550 | + let rightRotationInput = 0; | |
| 551 | + if (!aiEnabled) { | |
| 552 | + if (keys['ArrowLeft']) rightRotationInput -= 1; | |
| 553 | + if (keys['ArrowRight']) rightRotationInput += 1; | |
| 554 | + } | |
| 555 | + | |
| 556 | + // Update rotation states | |
| 557 | + updateRotationPhysics('left', leftRotationInput, leftPaddle); | |
| 558 | + if (!aiEnabled) { | |
| 559 | + updateRotationPhysics('right', rightRotationInput, rightPaddle); | |
| 560 | + } | |
| 561 | +} | |
| 562 | + | |
| 563 | +function updateRotationPhysics(side, input, paddle) { | |
| 564 | + const Body = Matter.Body; | |
| 565 | + let state = rotationState[side]; | |
| 566 | + | |
| 567 | + // Smooth the input | |
| 568 | + state.inputBuffer = lerp(state.inputBuffer, input, ROTATION_SMOOTHING); | |
| 569 | + | |
| 570 | + // Calculate resistance based on momentum | |
| 571 | + let rotatingWithMomentum = (state.inputBuffer * state.angularVelocity) > 0; | |
| 572 | + let effectiveInput = state.inputBuffer; | |
| 573 | + | |
| 574 | + if (rotatingWithMomentum && Math.abs(state.inputBuffer) > 0.1) { | |
| 575 | + // Easier to rotate with momentum | |
| 576 | + effectiveInput *= ROTATION_WITH_MOMENTUM_BOOST; | |
| 577 | + } else if (!rotatingWithMomentum && Math.abs(state.inputBuffer) > 0.1) { | |
| 578 | + // Harder to rotate against momentum | |
| 579 | + effectiveInput *= ROTATION_AGAINST_MOMENTUM_LAG; | |
| 580 | + } | |
| 581 | + | |
| 582 | + // Apply torque based on input | |
| 583 | + let torque = effectiveInput * ROTATION_SPEED; | |
| 584 | + | |
| 585 | + // Update angular velocity with torque | |
| 586 | + state.angularVelocity += torque; | |
| 587 | + | |
| 588 | + // Apply damping | |
| 589 | + state.angularVelocity *= ROTATION_DAMPING; | |
| 590 | + | |
| 591 | + // Limit maximum angular velocity | |
| 592 | + state.angularVelocity = Math.max(-ROTATION_MAX_SPEED, | |
| 593 | + Math.min(ROTATION_MAX_SPEED, state.angularVelocity)); | |
| 594 | + | |
| 595 | + // Apply return-to-center force when no input | |
| 596 | + if (Math.abs(state.inputBuffer) < 0.1) { | |
| 597 | + let returnForce = -state.currentAngle * ROTATION_RETURN_FORCE; | |
| 598 | + state.angularVelocity += returnForce; | |
| 599 | + } | |
| 600 | + | |
| 601 | + // Update current angle | |
| 602 | + state.currentAngle += state.angularVelocity; | |
| 603 | + | |
| 604 | + // Limit maximum rotation angle | |
| 605 | + state.currentAngle = Math.max(-ROTATION_MAX_ANGLE, | |
| 606 | + Math.min(ROTATION_MAX_ANGLE, state.currentAngle)); | |
| 607 | + | |
| 608 | + // Apply rotation to the paddle body | |
| 609 | + Body.setAngle(paddle, state.currentAngle); | |
| 610 | + | |
| 611 | + // Update angular momentum for next frame | |
| 612 | + state.angularMomentum = state.angularMomentum * ROTATION_MOMENTUM + | |
| 613 | + state.angularVelocity * (1 - ROTATION_MOMENTUM); | |
| 614 | + | |
| 615 | + // Track last direction for momentum calculations | |
| 616 | + if (Math.abs(input) > 0.1) { | |
| 617 | + state.lastDirection = Math.sign(input); | |
| 618 | + } | |
| 619 | +} | |
| 620 | + | |
| 621 | +// ============= HELPER FUNCTIONS ============= | |
| 622 | +function lerp(start, stop, amt) { | |
| 623 | + return amt * (stop - start) + start; | |
| 510 | 624 | } |
js/rendering.jsmodified@@ -344,32 +344,44 @@ function drawDebugInfo(ball, leftSupport, leftPaddle, rightSupport, rightPaddle, | ||
| 344 | 344 | text(`R Spring: ${Math.round(rightSpringLength)}px (${((SPRING_LENGTH/rightSpringLength - 1) * 100).toFixed(0)}%)`, 10, 95); |
| 345 | 345 | text(`Input: L=${inputBuffer.left.toFixed(2)} R=${inputBuffer.right.toFixed(2)}`, 10, 110); |
| 346 | 346 | |
| 347 | + // Rotation info | |
| 348 | + if (window.rotationState) { | |
| 349 | + text(`L Rot: ${(window.rotationState.left.currentAngle * 180 / Math.PI).toFixed(1)}° (vel: ${window.rotationState.left.angularVelocity.toFixed(3)})`, 10, 125); | |
| 350 | + text(`R Rot: ${(window.rotationState.right.currentAngle * 180 / Math.PI).toFixed(1)}° (vel: ${window.rotationState.right.angularVelocity.toFixed(3)})`, 10, 140); | |
| 351 | + } | |
| 352 | + | |
| 347 | 353 | // AI debug info |
| 348 | 354 | if (aiEnabled) { |
| 349 | - text(`AI State: ${aiState.mode} | Aggression: ${aiState.aggressionLevel.toFixed(2)}`, 10, 125); | |
| 350 | - text(`Target: ${Math.round(aiState.targetY)} | Intercept: ${Math.round(aiState.interceptY)}`, 10, 140); | |
| 351 | - 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); | |
| 355 | + text(`AI State: ${aiState.mode} | Aggression: ${aiState.aggressionLevel.toFixed(2)}`, 10, 155); | |
| 356 | + text(`Target: ${Math.round(aiState.targetY)} | Intercept: ${Math.round(aiState.interceptY)}`, 10, 170); | |
| 357 | + text(`Ball: (${Math.round(ball.position.x)}, ${Math.round(ball.position.y)}) Vel: (${ball.velocity.x.toFixed(1)}, ${ball.velocity.y.toFixed(1)})`, 10, 185); | |
| 358 | + | |
| 359 | + // AI rotation info | |
| 360 | + if (aiState.rotationMode !== 'NEUTRAL') { | |
| 361 | + fill(150, 200, 255, 200); | |
| 362 | + text(`AI Rotation: ${aiState.rotationMode} | Target: ${(aiState.targetRotation * 180 / Math.PI).toFixed(1)}°`, 10, 200); | |
| 363 | + } | |
| 352 | 364 | |
| 353 | 365 | // AI technique indicators |
| 354 | 366 | if (aiState.mode === 'WINDING_UP') { |
| 355 | 367 | fill(255, 150, 50, 200); |
| 356 | - text(`AI WINDING UP | Phase: ${(aiState.windupPhase % (Math.PI * 2)).toFixed(2)} | Velocity: ${aiState.currentVelocity.toFixed(1)}`, 10, 175); | |
| 368 | + text(`AI WINDING UP | Phase: ${(aiState.windupPhase % (Math.PI * 2)).toFixed(2)} | Velocity: ${aiState.currentVelocity.toFixed(1)}`, 10, 215); | |
| 357 | 369 | |
| 358 | 370 | if (aiState.comboBop) { |
| 359 | 371 | fill(255, 50, 255, 200); |
| 360 | - text("⚡ COMBO PLANNED!", 10, 190); | |
| 372 | + text("⚡ COMBO PLANNED!", 10, 230); | |
| 361 | 373 | } |
| 362 | 374 | } else if (aiState.mode === 'SWINGING') { |
| 363 | 375 | fill(255, 50, 50, 200); |
| 364 | - text("AI POWER SWING!", 10, 175); | |
| 376 | + text("AI POWER SWING!", 10, 215); | |
| 365 | 377 | } else if (aiState.consideringBop) { |
| 366 | 378 | fill(255, 255, 100, 200); |
| 367 | - text("AI PREPARING BOP!", 10, 175); | |
| 379 | + text("AI PREPARING BOP!", 10, 215); | |
| 368 | 380 | } |
| 369 | 381 | |
| 370 | 382 | if (bopState.right.active) { |
| 371 | 383 | fill(255, 255, 0, 255); |
| 372 | - text("AI BOPPING!", 10, 190); | |
| 384 | + text("AI BOPPING!", 10, 230); | |
| 373 | 385 | } |
| 374 | 386 | } |
| 375 | 387 | } |
@@ -382,10 +394,11 @@ function drawStartMessage(aiEnabled, aiDifficulty) { | ||
| 382 | 394 | textSize(14); |
| 383 | 395 | |
| 384 | 396 | if (aiEnabled) { |
| 385 | - text("Player vs CPU | Left paddle: W/S or Mouse/Touch", width/2, height/2 + 125); | |
| 397 | + text("Player vs CPU | Move: W/S or Mouse/Touch | Rotate: A/D | Bop: Left Shift", width/2, height/2 + 125); | |
| 386 | 398 | text(`AI Difficulty: ${aiDifficulty.toUpperCase()}`, width/2, height/2 + 145); |
| 387 | 399 | } else { |
| 388 | - text("2 Player Mode | P1: W/S | P2: ↑/↓ | Mouse/Touch: Drag paddles", width/2, height/2 + 125); | |
| 400 | + text("2 Player Mode | P1: W/S + A/D + L.Shift | P2: ↑/↓ + ←/→ + Enter", width/2, height/2 + 125); | |
| 401 | + text("Mouse/Touch: Drag paddles", width/2, height/2 + 145); | |
| 389 | 402 | } |
| 390 | 403 | |
| 391 | 404 | textSize(12); |
@@ -451,9 +464,9 @@ function drawMenu(menuState) { | ||
| 451 | 464 | textSize(12); |
| 452 | 465 | fill(255, 100); |
| 453 | 466 | if (menuState.selectedOption === 0) { |
| 454 | - text("Controls: W/S keys + LEFT SHIFT (bop) or Mouse/Touch", width/2, height - 30); | |
| 467 | + text("Controls: W/S (move) + A/D (rotate) + LEFT SHIFT (bop) or Mouse/Touch", width/2, height - 30); | |
| 455 | 468 | } else { |
| 456 | - text("Controls: P1 (W/S + L.Shift) | P2 (↑/↓ + Enter) | Mouse/Touch", width/2, height - 30); | |
| 469 | + text("P1: W/S + A/D + L.Shift | P2: ↑/↓ + ←/→ + Enter | Mouse/Touch", width/2, height - 30); | |
| 457 | 470 | } |
| 458 | 471 | } |
| 459 | 472 | |
js/sprong.jsmodified@@ -49,6 +49,10 @@ let mouseInput = { | ||
| 49 | 49 | window.inputBuffer = inputBuffer; |
| 50 | 50 | window.moveSupportEnhanced = moveSupportEnhanced; |
| 51 | 51 | window.ball = null; |
| 52 | +window.rotationState = rotationState; | |
| 53 | +window.rightPaddle = null; | |
| 54 | +window.width = CANVAS_WIDTH; | |
| 55 | +window.height = CANVAS_HEIGHT; | |
| 52 | 56 | |
| 53 | 57 | function setup() { |
| 54 | 58 | let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); |
@@ -79,6 +83,7 @@ function setup() { | ||
| 79 | 83 | // Create ball |
| 80 | 84 | ball = resetBall(null, world, width, height); |
| 81 | 85 | window.ball = ball; |
| 86 | + window.rightPaddle = rightPaddle; | |
| 82 | 87 | |
| 83 | 88 | // Add everything to the world |
| 84 | 89 | World.add(world, [ |
@@ -125,6 +130,7 @@ function draw() { | ||
| 125 | 130 | function handleEnhancedInput() { |
| 126 | 131 | handleKeyboardInput(); |
| 127 | 132 | handleMouseTouchInput(); |
| 133 | + handleRotationInput(); // Add rotation handling | |
| 128 | 134 | handleBopInput(keys, aiEnabled, millis(), leftPaddle, rightPaddle, |
| 129 | 135 | leftSupport, rightSupport, engine, particles); |
| 130 | 136 | |