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@@ -559,9 +559,4 @@ function executeAIMovement(aiSettings, rightSupport) { | ||
| 559 | 559 | window.inputBuffer.right *= 0.9; // Slower decay for more visible movement |
| 560 | 560 | } |
| 561 | 561 | } |
| 562 | -} | |
| 563 | - | |
| 564 | -// Utility function - should match p5.js lerp | |
| 565 | -function lerp(start, stop, amt) { | |
| 566 | - return amt * (stop - start) + start; | |
| 567 | 562 | } |
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,38 @@ 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); | |
| 352 | 358 | |
| 353 | 359 | // AI technique indicators |
| 354 | 360 | if (aiState.mode === 'WINDING_UP') { |
| 355 | 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 | 364 | if (aiState.comboBop) { |
| 359 | 365 | fill(255, 50, 255, 200); |
| 360 | - text("⚡ COMBO PLANNED!", 10, 190); | |
| 366 | + text("⚡ COMBO PLANNED!", 10, 220); | |
| 361 | 367 | } |
| 362 | 368 | } else if (aiState.mode === 'SWINGING') { |
| 363 | 369 | fill(255, 50, 50, 200); |
| 364 | - text("AI POWER SWING!", 10, 175); | |
| 370 | + text("AI POWER SWING!", 10, 205); | |
| 365 | 371 | } else if (aiState.consideringBop) { |
| 366 | 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 | 376 | if (bopState.right.active) { |
| 371 | 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 | 388 | textSize(14); |
| 383 | 389 | |
| 384 | 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 | 392 | text(`AI Difficulty: ${aiDifficulty.toUpperCase()}`, width/2, height/2 + 145); |
| 387 | 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 | 398 | textSize(12); |
@@ -451,9 +458,9 @@ function drawMenu(menuState) { | ||
| 451 | 458 | textSize(12); |
| 452 | 459 | fill(255, 100); |
| 453 | 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 | 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 | 49 | window.inputBuffer = inputBuffer; |
| 50 | 50 | window.moveSupportEnhanced = moveSupportEnhanced; |
| 51 | 51 | window.ball = null; |
| 52 | +window.rotationState = rotationState; | |
| 52 | 53 | |
| 53 | 54 | function setup() { |
| 54 | 55 | let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); |
@@ -125,6 +126,7 @@ function draw() { | ||
| 125 | 126 | function handleEnhancedInput() { |
| 126 | 127 | handleKeyboardInput(); |
| 127 | 128 | handleMouseTouchInput(); |
| 129 | + handleRotationInput(); // Add rotation handling | |
| 128 | 130 | handleBopInput(keys, aiEnabled, millis(), leftPaddle, rightPaddle, |
| 129 | 131 | leftSupport, rightSupport, engine, particles); |
| 130 | 132 | |