Pin Spin Game
Shoot needles into a rotating disc without hitting other needles — 10 levels of escalating speed
What is the Pin Spin Game?
Pin Spin is a casual browser game in the same family as the mobile hits AA and Pin Out. A white disc rotates at a constant speed in the center of the screen. You fire needles one at a time from the bottom, and each needle must land on the disc without hitting any needle that is already stuck in it. Each level gives you a fixed number of needles; clear them all to advance, and a single collision instantly ends the round. The rules are simple enough that anyone can pick it up in seconds, but the difficulty curve quickly turns it into a tight test of timing and self-control. The tool runs entirely in the browser with no signup or download required — click with the mouse on desktop, tap the screen on mobile.
How to Use
Steps
- Open the page — the game auto-starts at Level 1 with the disc already spinning
- Click the game area (or press Space / Enter) to fire a needle
- The needle flies up to the center and sticks into the disc — avoid hitting any needle already there
- Land all the needles for the level safely to advance to the next one
- Any collision ends the round — choose Retry Level or Restart to continue (note: pressing Space / Enter on the game-over screen triggers Restart and sends you back to Level 1, not Retry — use the button if you want to replay the same level)
Clearing Tips
- The disc rotates at constant speed, so finding the rhythm matters more than fast clicks: focus on the midline of the gap.
- Fire just after a neighboring needle has swept past — the needle takes ~120ms to fly, so lead the shot a little.
- Up to 3 queued shots fire in sequence; clicks beyond that limit are dropped, not queued, so there's no point spam-clicking.
- Later levels leave very narrow gaps and spin much faster; skipping a risky shot beats a greedy collision.
Difficulty by level
- Levels 1-3: many needles, wide gaps — get used to the rhythm.
- Levels 4-7: more needles already on the disc, you need to predict the rotation.
- Levels 8-10: the disc is nearly full, every single shot must be timed precisely.
Use Cases
Technical Principle
The main loop is driven by requestAnimationFrame. Each frame increments an internal rotation by a fixed speed value (degrees per frame), then walks every stuck needle and sets its transform to rotate(baseAngle + rotation). Because transform is a compositor-layer property, the browser hands rotation off to the GPU compositor thread without triggering layout or paint, so the final level with all 15 needles rotating still runs at 60fps. Collision detection uses an angle-difference-to-chord-length approach. Each stuck needle is represented by its baseAngle in the disc's coordinate system (a fixed offset relative to the disc, unchanged by rotation). When a fired needle reaches the center, its angle in world coordinates is TARGET_ANGLE = 0, which maps to disc coordinates as normalizeAngle(0 - rotation). The new baseAngle is then compared against every existing baseAngle: convert the angular difference Δθ into the chord length 2R·sin(Δθ/2) between the needle-tip dot centers (R = 111px from each tip-dot center to the disc center). If it falls below the 24px threshold, the shot collides and the round ends. Firing is gated by a small state machine. shootLocked marks a needle as in flight, preventing both the transitionend event and the 180ms setTimeout fallback from finishing the same shot twice. The CSS transition takes 120ms; the 180ms setTimeout is a safety net for the rare case where transitionend fails to fire (for example, when the DOM is mutated before the transition completes). pendingShots buffers click requests issued while a needle is still airborne, capped at 3 — clicks beyond that cap are dropped (not queued), which keeps fast-rep input feel while preventing multiple needles converging on the center at once. Both failure and clearing flip gameOver to true: the main loop keeps running but stops updating rotation, freezing the scene in place. For display scaling, the game keeps a fixed 420×720 design size, and the outer wrapper computes a scale factor from the viewport size and applies it via transform: scale. The current implementation caps scale at 1, so small viewports are scaled down proportionally while desktop viewports keep the original size — they don't enlarge. This means collision detection always works in the design coordinate system, with a single set of pixel thresholds, and judgements are identical on every screen.
- Main loop: requestAnimationFrame, rotation += speed per frame, all needles get transform: rotate(baseAngle + rotation).
- GPU acceleration: rotation uses transform rather than left/top, so the browser's compositor handles it — no layout or repaint.
- Hit detection: world-coordinate 0° projects back to disc coordinates as normalizeAngle(0 - rotation); chord length 2R·sin(Δθ/2) is compared against a 24px threshold.
- State machine: shootLocked blocks double-settlement, pendingShots is capped at 3 with overflow dropped (not queued), gameOver freezes rotation updates.
- Settlement callback: 120ms CSS flight, transitionend as primary settlement + 180ms setTimeout as a safety net for the rare case where transitionend doesn't fire.
- Scaling: fixed 420×720 design system; the wrapper applies transform: scale based on viewport size, capped at 1 so desktops keep the original size rather than enlarging.
- Storage: only the best cleared level is saved to localStorage. Nothing is uploaded; no accounts, no leaderboard.
Examples
Speed and needle counts across the 10 levels
Level Start Needles Speed (deg/frame)
1 2 8 1.35
2 3 9 1.50
3 3 10 1.65
4 4 11 1.80
5 4 12 2.00
6 5 13 2.20
7 5 14 2.45
8 6 15 2.70
9 7 15 3.00
10 8 15 3.35A typical run: failing on Level 5
Level 5: 4 starting needles, 12 to fire.
Shots 1-7: land cleanly, 5 left.
Shot 8: fired before the previous needle had settled — too eager.
Judgement: new baseAngle is 8° from shot 6, below DOT_HIT_DISTANCE.
Result: collision, round ends with 'Game Over'.
Click 'Retry Level' to restart from Level 5.Full run summary
Time to clear: ~2m 40s
Total shots fired: 117
Deaths: 3
Best level: 10
Message: 'All Levels Cleared!' button reads 'Play Again'.
Best level is written to localStorage and persists across page reloads.FAQ
I clicked on an empty spot — why did I still lose?
The needle is not instantaneous. From click to actual contact with the disc takes about 120ms, during which the disc keeps rotating. If the gap was wide enough at click time but another needle rotated into the target line during flight, the new needle lands on top of it. The simple fix is to fire just after a gap has swept past the line, leaving room for the rotation.
Can I hold the screen down for rapid fire?
You can click rapidly, but firing is throttled: no shot leaves while the previous one is still in flight. Up to 3 clicks during flight are buffered and auto-fired in sequence; anything beyond that is dropped, not queued. Slow-clicking and waiting for each needle to settle works better than mashing on later levels.
How does the difficulty scale up?
Each level has its own rotation speed, starting needle count, and number of shots required. Speed grows from 1.35 deg/frame on level 1 to 3.35 deg/frame on level 10 — roughly 2.5×, not quite triple. Starting needles climb from 2 to 8, so the surviving gaps shrink. Later levels need patience more than raw speed.
After a failure, do I start over or replay the same level?
Both are supported, but the trigger matters. The 'Retry Level' button restarts the level you failed with the same needle count and speed; the 'Restart' button — and pressing Space / Enter on the game-over screen — both go back to Level 1. There is no keyboard shortcut for Retry Level, so if you reflexively tap Space to continue, you'll drop back to Level 1. Your best cleared level is always kept in localStorage.
Why is the play area so narrow?
The design size is 420×720, matching a phone in portrait orientation. On mobile that's naturally full-screen; on desktop the wrapper scales it down proportionally, but the scale is capped at 1, so a large desktop window won't enlarge the game beyond 420×720. The game logic doesn't change with window size.
Is any data sent to a server?
No. The whole game runs locally in the browser. The only thing stored is a single number — your best cleared level — in localStorage. Clear site data or switch browsers to wipe it. There is no leaderboard and no score or input tracking on our side.
Anything to watch out for on mobile?
Enable full-screen mode in your browser to avoid the pull-down gesture triggering a page refresh. Landscape works but the original portrait ratio feels more natural. On lower-end phones with occasional frame drops, close other tabs or heavy animations elsewhere — the game itself only updates a few transform calls per frame, so its load on the device is minimal.