Flappy Bird Sandbox
A demo of Strata's interactive HTML blocks: a fully playable Flappy Bird rendered on a canvas and a mermaid diagram of the game's state machine.
Play
Click, tap, or press Space to flap. Get through the pipes without touching anything.That’s really fucking coolbasic shit ever you could haveClaude make reallythings that Arden was just like that’s cool, but it canit was like it was a fucking planetary system with thank you I wish I had showed it to you becausecool it was like rotating asteroids and an asteroid belt
<div id="stage" style="display:flex;justify-content:center;padding:8px 0;">
<canvas id="game" style="width:320px;max-width:100%;border-radius:8px;touch-action:manipulation;cursor:pointer;"></canvas>
</div>
<script>
const W = 320, H = 480, GROUND_Y = 400;
const PIPE_W = 52, GAP = 135, SPEED = 2.2, GRAVITY = 0.25, FLAP = -7.2;
const cvs = document.getElementById('game');
const ctx = cvs.getContext('2d');
const dpr = window.devicePixelRatio || 1;
cvs.width = W * dpr;
cvs.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
let state = 'ready'; // ready | play | over
let bird, pipes, score, frames, best = 0, diedAt = 0;
function reset() {
bird = { x: 80, y: 220, vel: 0, r: 12 };
pipes = [];
score = 0;
frames = 0;
state = 'ready';
}
reset();
function flap() {
if (state === 'ready') {
state = 'play';
bird.vel = FLAP;
} else if (state === 'play') {
bird.vel = FLAP;
} else if (state === 'over' && performance.now() - diedAt > 400) {
reset();
}
}
cvs.addEventListener('pointerdown', (e) => { e.preventDefault(); flap(); });
window.addEventListener('keydown', (e) => {
if (e.code === 'Space' || e.code === 'ArrowUp') { e.preventDefault(); flap(); }
});
function die() {
state = 'over';
diedAt = performance.now();
best = Math.max(best, score);
}
function update() {
if (state === 'ready') {
bird.y = 220 + Math.sin(performance.now() / 300) * 8;
return;
}
if (state !== 'play') return;
frames++;
bird.vel += GRAVITY;
bird.y += bird.vel;
if (bird.y - bird.r < 0) { bird.y = bird.r; bird.vel = 0; }
if (bird.y + bird.r > GROUND_Y) { bird.y = GROUND_Y - bird.r; die(); return; }
if (frames % 88 === 1) {
pipes.push({ x: W + 10, top: 60 + Math.random() * (GROUND_Y - GAP - 120), passed: false });
}
for (const p of pipes) {
p.x -= SPEED;
if (!p.passed && p.x + PIPE_W < bird.x) { p.passed = true; score++; }
const inX = bird.x + bird.r > p.x && bird.x - bird.r < p.x + PIPE_W;
const inGap = bird.y - bird.r > p.top && bird.y + bird.r < p.top + GAP;
if (inX && !inGap) { die(); return; }
}
pipes = pipes.filter((p) => p.x > -PIPE_W - 10);
}
function drawPipe(p) {
ctx.fillStyle = '#5ec22e';
ctx.fillRect(p.x, 0, PIPE_W, p.top);
ctx.fillRect(p.x, p.top + GAP, PIPE_W, GROUND_Y - p.top - GAP);
ctx.fillStyle = '#4aa823';
ctx.fillRect(p.x - 3, p.top - 16, PIPE_W + 6, 16);
ctx.fillRect(p.x - 3, p.top + GAP, PIPE_W + 6, 16);
}
function drawBird() {
ctx.save();
ctx.translate(bird.x, bird.y);
ctx.rotate(Math.max(-0.5, Math.min(0.9, bird.vel * 0.06)));
ctx.fillStyle = '#f7d51d';
ctx.beginPath();
ctx.arc(0, 0, bird.r, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#e8a020';
ctx.beginPath();
ctx.ellipse(-3, 3, 6, 4, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(5, -4, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#222';
ctx.beginPath();
ctx.arc(6, -4, 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#f0713a';
ctx.fillRect(9, 0, 8, 4);
ctx.restore();
}
function centerText(text, y, size, fill) {
ctx.font = 'bold ' + size + 'px system-ui, sans-serif';
ctx.textAlign = 'center';
ctx.lineWidth = 4;
ctx.strokeStyle = 'rgba(0,0,0,0.45)';
ctx.strokeText(text, W / 2, y);
ctx.fillStyle = fill;
ctx.fillText(text, W / 2, y);
}
function draw() {
ctx.fillStyle = '#70c5ce';
ctx.fillRect(0, 0, W, H);
for (const p of pipes) drawPipe(p);
ctx.fillStyle = '#ded895';
ctx.fillRect(0, GROUND_Y, W, H - GROUND_Y);
ctx.fillStyle = '#9bc25e';
ctx.fillRect(0, GROUND_Y, W, 8);
drawBird();
if (state === 'play') {
centerText(String(score), 70, 42, '#fff');
} else if (state === 'ready') {
centerText('Flappy Bird', 150, 32, '#fff');
centerText('Tap or press Space', 190, 16, '#fff');
} else {
centerText('Game over', 150, 32, '#fff');
centerText('Score ' + score + ' Best ' + best, 190, 18, '#fff');
centerText('Tap to restart', 222, 14, '#fff');
}
}
(function loop() {
update();
draw();
requestAnimationFrame(loop);
})();
</script>
Game state machine
<pre class="mermaid">
stateDiagram-v2
[*] --> Ready
Ready --> Playing: tap / Space
Playing --> Playing: flap
Playing --> GameOver: hit a pipe or the ground
GameOver --> Ready: tap to restart
</pre>
<script src="/sandbox/libs/mermaid.min.js"></script>
<script>
const s = getComputedStyle(document.body);
const t = (name, fb) => (s.getPropertyValue(name) || '').trim() || fb;
mermaid.initialize({
startOnLoad: false,
theme: 'base',
themeVariables: {
primaryColor: t('--color-muted', '#f4f4f5'),
primaryTextColor: t('--color-foreground', '#18181b'),
primaryBorderColor: t('--color-border', '#d4d4d8'),
lineColor: t('--color-muted-foreground', '#71717a'),
textColor: t('--color-foreground', '#18181b'),
background: 'transparent'
}
});
mermaid.run();
</script>