BREAKOUT

Canvas 2D · 400×600 field · 8×4 brick grid · fixed 60 Hz step · vanilla JS

FPS 60
STEP 16.6ms
POINTER IDLE
AUDIO ON
Score
0/ 320
GRID32ALIVE
Lives
BEST 0
DRAG THE FIELD · ARROWS · SPACE

One resolution per frame — the anti-stuck rule

// ball-vs-brick: nearest hit only, then break
for (b of bricks) {
  if (circleRect(ball, b)) pick nearest(b);
}
const ox = (R + b.w/2) - |ball.x - b.cx|;
const oy = (R + b.h/2) - |ball.y - b.cy|;
if (ox < oy) { dx = sign(ball.x-b.cx) * |dx|; ball.x += sign*(ox+.6); }
else         { dy = sign(ball.y-b.cy) * |dy|; ball.y += sign*(oy+.6); }
break;                        // no double flips, no tunnelling
clamp(|dy| >= 1.1)            // the ball never runs flat
renormalize(|v| = speed)      // magnitude always preserved

// paddle: angle from contact point, -60° … +60°
rel = (ball.x - padCenter) / (30);
v   = speed * [ sin(rel*60°), -cos(rel*60°) ] + 0.06 * padMomentum

Spec ledger

Field400 × 600
Brick grid8 × 4 · 50 × 20 px
Brick value+10 · max 320
Ballr = 3 px · yellow
Launch vector(2.00, −2.80) → 3.44
Ramp3.44 → 3.90 px/frame
Paddle60 × 10 · y = 580 · x 0–340
Lives3
LooprequestAnimationFrame + 60 Hz accumulator
Palette#1a1a1a / #00ffff / #fff / #ffd400