One tap. Two rings. Don't clip a bar.
A minimal arcade game in a single HTML file. No build step, no dependencies, no framework — open it in a browser and it runs.
Built by AnbuTech.
You're pinned to the top of the screen while the orbit spins past you. Tap to jump between the inner and outer ring. Dark bars block one ring at a time — switch before you reach them. Gold pips are worth more than survival.
| Action | Reward |
|---|---|
| Pass a bar | +1 |
| Collect a pip | +3 |
| Clip a bar | Run over |
Speed scales with your score, capped so it stays playable rather than becoming a coin flip.
| Input | Effect |
|---|---|
| Tap / click anywhere | Switch ring (or start / restart) |
Space, ↑, Enter |
Same |
One input, one meaning, in every game state. There is nothing else to learn.
git clone https://github.com/<your-org>/switchback.git
cd switchback
open switchback.html # or just drag it into a browserFor local testing on a phone, serve it over your network:
python3 -m http.server 8000
# then visit http://<your-lan-ip>:8000/switchback.htmlThe core trick is that the player never moves angularly. Instead of orbiting the player around the circle, the player is fixed at the top (TOP = -π/2) and the entire world rotates beneath. Every obstacle stores a w value — its position along an ever-increasing world angle — and renders at TOP + (w - dist), where dist is how far the world has spun.
This makes collision detection trivial and stable. There is no wrapping, no modular arithmetic, no drift. A bar is dangerous when |w - dist| is small and the player's current radius is close to that bar's ring radius:
const near = Math.abs(b.w - G.dist) < b.hw + pAng;
if (near && Math.abs(G.r - ringR[b.ring]) < BAR_W / 2 + PLAYER_R + 2) die();Because the radius is a live tweened value rather than a discrete ring index, a switch made too late can clip either ring. That is intentional — committing early is the skill the game is actually testing.
Obstacles are generated lazily ahead of the player and culled behind, so memory stays flat no matter how long the run lasts.
Single <canvas>, scaled for device pixel ratio (capped at 2x to spare low-end GPUs). Everything is drawn per frame with requestAnimationFrame and a delta clamped to 33 ms, so a background tab or a stutter can't teleport the player through a bar.
loadBest() / saveBest() currently call window.storage, an async key–value API that exists in the environment this prototype was authored in. Both calls are wrapped in try/catch and fail silently, so the game runs fine without it — the score just won't survive a reload.
Before shipping, swap in localStorage:
function loadBest(){
G.best = parseInt(localStorage.getItem('switchback:best') || '0', 10);
elBest.textContent = G.best;
}
function saveBest(){
localStorage.setItem('switchback:best', String(G.best));
}Everything that affects feel lives at the top of the script or in update():
| Constant | Default | Effect |
|---|---|---|
PLAYER_R |
9 |
Player size, and therefore hitbox generosity |
BAR_W |
14 |
Bar thickness — raises collision tolerance too |
ringR |
R*0.50, R*0.74 |
Ring separation; wider means longer, riskier switches |
G.tween + dt * 8 |
8 |
Switch speed. Lower is floatier, higher is snappier |
1.5 + min(1.75, score*0.012) |
— | Difficulty ramp: base speed, ceiling, and rate |
gapFor() |
1.45 → 0.82 |
Angular spacing between bars as score climbs |
If the game feels unfair, raise the ramp ceiling last. Nine times out of ten the problem is spacing, not speed.
| Token | Value | Use |
|---|---|---|
| Field | #1F3AC4 |
Background |
| Deep | #152C96 |
Ring shadow |
| Ink | #0B1440 |
Bars |
| Chalk | #F2EFE4 |
Rings, type |
| Signal | #FF4D8D |
Player, trail |
| Solar | #FFC93C |
Pips, accents |
Type is Bricolage Grotesque for display and IBM Plex Mono for telemetry. prefers-reduced-motion disables screen shake and the pulsing prompt.
- Replace
window.storagewithlocalStorage - Wrap with Capacitor for an Android build
- AdMob: interstitial on every third game over, rewarded video for a one-time continue
- Audio: a short tick on switch, a thud on death
- Daily seed mode for comparable scores
AnbuTech — an elite engineering unit that ships. anbu-tech.com · LinkedIn
Copyright © 2026 AnbuTech. All rights reserved.
This software and its source code are the proprietary property of AnbuTech. No part of this repository may be reproduced, distributed, modified, or used to create derivative works without prior written permission from AnbuTech.