testing rotation
- SHA
5b429e81ef6d7f51823ac44a313cabde0f813df5- Parents
-
f1fe736 - Tree
242bb4a
5b429e8
5b429e81ef6d7f51823ac44a313cabde0f813df5f1fe736
242bb4a| Status | File | + | - |
|---|---|---|---|
| A |
.DS_Store
|
bin | |
| M |
js/ai.js
|
0 | 5 |
| M |
js/game-systems.js
|
116 | 2 |
| M |
js/rendering.js
|
19 | 12 |
| M |
js/sprong.js
|
2 | 0 |
.DS_Storeaddedjs/ai.jsmodified@@ -560,8 +560,3 @@ function executeAIMovement(aiSettings, rightSupport) { | |||
| 560 | } | 560 | } |
| 561 | } | 561 | } |
| 562 | } | 562 | } |
| 563 | - | ||
| 564 | -// Utility function - should match p5.js lerp | ||
| 565 | -function lerp(start, stop, amt) { | ||
| 566 | - return amt * (stop - start) + start; | ||
| 567 | -} | ||
js/game-systems.jsmodified@@ -1,5 +1,4 @@ | |||
| 1 | // game-systems.js - Core game mechanics and physics | 1 | // game-systems.js - Core game mechanics and physics |
| 2 | -// TODO redocument | ||
| 3 | 2 | ||
| 4 | // ============= CONSTANTS ============= | 3 | // ============= CONSTANTS ============= |
| 5 | // Canvas settings | 4 | // Canvas settings |
@@ -48,6 +47,18 @@ const BOP_COOLDOWN = 0; // also also self expl. PULL it. | |||
| 48 | const ANCHOR_RECOIL = 60; // How far the anchor moves backward during bop | 47 | const ANCHOR_RECOIL = 60; // How far the anchor moves backward during bop |
| 49 | const BOP_VELOCITY_BOOST = 5; // Initial velocity boost for paddle | 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 | // ============= BOP SYSTEM ============= | 62 | // ============= BOP SYSTEM ============= |
| 52 | let bopState = { | 63 | let bopState = { |
| 53 | left: { | 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 | function handleBopInput(keys, aiEnabled, currentTime, leftPaddle, rightPaddle, leftSupport, rightSupport, engine, particles) { | 104 | function handleBopInput(keys, aiEnabled, currentTime, leftPaddle, rightPaddle, leftSupport, rightSupport, engine, particles) { |
| 74 | // Left player bop - use Left Shift for both modes | 105 | // Left player bop - use Left Shift for both modes |
| 75 | let leftBopPressed = keys['Shift'] && !keys['Control']; | 106 | let leftBopPressed = keys['Shift'] && !keys['Control']; |
@@ -356,7 +387,6 @@ function resetBall(ball, world, width, height) { | |||
| 356 | return ball; | 387 | return ball; |
| 357 | } | 388 | } |
| 358 | 389 | ||
| 359 | -// ============= COLLISION ============= | ||
| 360 | // ============= COLLISION ============= | 390 | // ============= COLLISION ============= |
| 361 | function setupCollisionHandlers(engine, ball, leftPaddle, rightPaddle, particles) { | 391 | function setupCollisionHandlers(engine, ball, leftPaddle, rightPaddle, particles) { |
| 362 | const Body = Matter.Body; | 392 | const Body = Matter.Body; |
@@ -508,3 +538,87 @@ function setupCollisionHandlers(engine, ball, leftPaddle, rightPaddle, particles | |||
| 508 | } | 538 | } |
| 509 | }); | 539 | }); |
| 510 | } | 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; | ||
| 624 | +} | ||
js/rendering.jsmodified@@ -344,32 +344,38 @@ function drawDebugInfo(ball, leftSupport, leftPaddle, rightSupport, rightPaddle, | |||
| 344 | text(`R Spring: ${Math.round(rightSpringLength)}px (${((SPRING_LENGTH/rightSpringLength - 1) * 100).toFixed(0)}%)`, 10, 95); | 344 | text(`R Spring: ${Math.round(rightSpringLength)}px (${((SPRING_LENGTH/rightSpringLength - 1) * 100).toFixed(0)}%)`, 10, 95); |
| 345 | text(`Input: L=${inputBuffer.left.toFixed(2)} R=${inputBuffer.right.toFixed(2)}`, 10, 110); | 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 | // AI debug info | 353 | // AI debug info |
| 348 | if (aiEnabled) { | 354 | if (aiEnabled) { |
| 349 | - text(`AI State: ${aiState.mode} | Aggression: ${aiState.aggressionLevel.toFixed(2)}`, 10, 125); | 355 | + text(`AI State: ${aiState.mode} | Aggression: ${aiState.aggressionLevel.toFixed(2)}`, 10, 155); |
| 350 | - text(`Target: ${Math.round(aiState.targetY)} | Intercept: ${Math.round(aiState.interceptY)}`, 10, 140); | 356 | + text(`Target: ${Math.round(aiState.targetY)} | Intercept: ${Math.round(aiState.interceptY)}`, 10, 170); |
| 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); | 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); |
| 352 | 358 | ||
| 353 | // AI technique indicators | 359 | // AI technique indicators |
| 354 | if (aiState.mode === 'WINDING_UP') { | 360 | if (aiState.mode === 'WINDING_UP') { |
| 355 | fill(255, 150, 50, 200); | 361 | 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); | 362 | + text(`AI WINDING UP | Phase: ${(aiState.windupPhase % (Math.PI * 2)).toFixed(2)} | Velocity: ${aiState.currentVelocity.toFixed(1)}`, 10, 205); |
| 357 | 363 | ||
| 358 | if (aiState.comboBop) { | 364 | if (aiState.comboBop) { |
| 359 | fill(255, 50, 255, 200); | 365 | fill(255, 50, 255, 200); |
| 360 | - text("⚡ COMBO PLANNED!", 10, 190); | 366 | + text("⚡ COMBO PLANNED!", 10, 220); |
| 361 | } | 367 | } |
| 362 | } else if (aiState.mode === 'SWINGING') { | 368 | } else if (aiState.mode === 'SWINGING') { |
| 363 | fill(255, 50, 50, 200); | 369 | fill(255, 50, 50, 200); |
| 364 | - text("AI POWER SWING!", 10, 175); | 370 | + text("AI POWER SWING!", 10, 205); |
| 365 | } else if (aiState.consideringBop) { | 371 | } else if (aiState.consideringBop) { |
| 366 | fill(255, 255, 100, 200); | 372 | fill(255, 255, 100, 200); |
| 367 | - text("AI PREPARING BOP!", 10, 175); | 373 | + text("AI PREPARING BOP!", 10, 205); |
| 368 | } | 374 | } |
| 369 | 375 | ||
| 370 | if (bopState.right.active) { | 376 | if (bopState.right.active) { |
| 371 | fill(255, 255, 0, 255); | 377 | fill(255, 255, 0, 255); |
| 372 | - text("AI BOPPING!", 10, 190); | 378 | + text("AI BOPPING!", 10, 220); |
| 373 | } | 379 | } |
| 374 | } | 380 | } |
| 375 | } | 381 | } |
@@ -382,10 +388,11 @@ function drawStartMessage(aiEnabled, aiDifficulty) { | |||
| 382 | textSize(14); | 388 | textSize(14); |
| 383 | 389 | ||
| 384 | if (aiEnabled) { | 390 | if (aiEnabled) { |
| 385 | - text("Player vs CPU | Left paddle: W/S or Mouse/Touch", width/2, height/2 + 125); | 391 | + text("Player vs CPU | Move: W/S or Mouse/Touch | Rotate: A/D | Bop: Left Shift", width/2, height/2 + 125); |
| 386 | text(`AI Difficulty: ${aiDifficulty.toUpperCase()}`, width/2, height/2 + 145); | 392 | text(`AI Difficulty: ${aiDifficulty.toUpperCase()}`, width/2, height/2 + 145); |
| 387 | } else { | 393 | } else { |
| 388 | - text("2 Player Mode | P1: W/S | P2: ↑/↓ | Mouse/Touch: Drag paddles", width/2, height/2 + 125); | 394 | + text("2 Player Mode | P1: W/S + A/D + L.Shift | P2: ↑/↓ + ←/→ + Enter", width/2, height/2 + 125); |
| 395 | + text("Mouse/Touch: Drag paddles", width/2, height/2 + 145); | ||
| 389 | } | 396 | } |
| 390 | 397 | ||
| 391 | textSize(12); | 398 | textSize(12); |
@@ -451,9 +458,9 @@ function drawMenu(menuState) { | |||
| 451 | textSize(12); | 458 | textSize(12); |
| 452 | fill(255, 100); | 459 | fill(255, 100); |
| 453 | if (menuState.selectedOption === 0) { | 460 | if (menuState.selectedOption === 0) { |
| 454 | - text("Controls: W/S keys + LEFT SHIFT (bop) or Mouse/Touch", width/2, height - 30); | 461 | + text("Controls: W/S (move) + A/D (rotate) + LEFT SHIFT (bop) or Mouse/Touch", width/2, height - 30); |
| 455 | } else { | 462 | } else { |
| 456 | - text("Controls: P1 (W/S + L.Shift) | P2 (↑/↓ + Enter) | Mouse/Touch", width/2, height - 30); | 463 | + text("P1: W/S + A/D + L.Shift | P2: ↑/↓ + ←/→ + Enter | Mouse/Touch", width/2, height - 30); |
| 457 | } | 464 | } |
| 458 | } | 465 | } |
| 459 | 466 | ||
js/sprong.jsmodified@@ -49,6 +49,7 @@ let mouseInput = { | |||
| 49 | window.inputBuffer = inputBuffer; | 49 | window.inputBuffer = inputBuffer; |
| 50 | window.moveSupportEnhanced = moveSupportEnhanced; | 50 | window.moveSupportEnhanced = moveSupportEnhanced; |
| 51 | window.ball = null; | 51 | window.ball = null; |
| 52 | +window.rotationState = rotationState; | ||
| 52 | 53 | ||
| 53 | function setup() { | 54 | function setup() { |
| 54 | let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); | 55 | let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); |
@@ -125,6 +126,7 @@ function draw() { | |||
| 125 | function handleEnhancedInput() { | 126 | function handleEnhancedInput() { |
| 126 | handleKeyboardInput(); | 127 | handleKeyboardInput(); |
| 127 | handleMouseTouchInput(); | 128 | handleMouseTouchInput(); |
| 129 | + handleRotationInput(); // Add rotation handling | ||
| 128 | handleBopInput(keys, aiEnabled, millis(), leftPaddle, rightPaddle, | 130 | handleBopInput(keys, aiEnabled, millis(), leftPaddle, rightPaddle, |
| 129 | leftSupport, rightSupport, engine, particles); | 131 | leftSupport, rightSupport, engine, particles); |
| 130 | 132 | ||