Breakout

Canvas build · 400×600 · grid 8×4 · 32 bricks · 320 max

State READY
Hi-Score 0
FPS 60

Collision resolution, by hand

No physics engine. Each fixed 1/60s tick is split into sub-steps so the ball can never tunnel through a 20px brick, then the closest brick to the ball centre wins — one brick per frame, one reflection, one score event. The contact normal is snapped to its dominant axis so the ball always leaves along a clean horizontal or vertical vector.

// closest point on the brick's AABB to the ball centre
const nx = clamp(ball.x, b.x, b.x + BW);
const ny = clamp(ball.y, b.y, b.y + BH);
const dx = ball.x - nx, dy = ball.y - ny;
if (dx*dx + dy*dy <= R*R) {          // circle vs AABB
  // snap normal to dominant axis → flip exactly one component
  (Math.abs(dx) > Math.abs(dy)) ? ball.dx = sign(dx)*speed
                                : ball.dy = sign(dy)*speed;
  ball.x = nx + sign(dx) * (R + .5); // depenetrate, never stick
}
← →A / DSPACE launchP pauseR restartM mutedrag on canvas

Edge cases handled

  • Launch vector — velocity seeded from (2, −2.8) normalised to 3.44 px/frame, side alternates per life.
  • Paddle english — contact offset maps to ±60° exit angle; paddle motion adds spin; |vy| floor prevents flat shots.
  • No double hits — one brick per frame, depenetration pushes the ball clear of the grid.
  • Paddle sides — a ball arriving at the paddle flank reflects on X, not Y.
  • Frame-rate independence — fixed-step accumulator, so 120 Hz phones run at the same speed as 60 Hz.
  • Bounds — paddle clamped to 0–340 px; ball clamped inside the walls after every reflection.