๐ŸŸจ My First Browser Game ยท Episode 1 of 6 ยท See All Episodes
๐Ÿฆ Episode 1 ยท Beginner ยท No Experience Needed

Flap or Fall!
Flappy Bird Clone

Build the infamous one-button game right in your browser: an HTML canvas, gravity, pipe gaps and a score that makes your friends rage. No installs, a text editor and a browser is everything you need.

๐Ÿ‘ถ Ages 10+ โฑ๏ธ ~1.5 Hours ๐ŸŸจ JavaScript โœ“ Free
๐Ÿ–ผ๏ธ HTML canvas ๐Ÿ” requestAnimationFrame ๐ŸŒ Gravity ๐ŸŸฉ Pipe generation ๐Ÿ’ฅ Collision ๐Ÿ… Scoring
โญ
0 XP
Level 1
๐Ÿ”ฅ0
Your Progress 0 / 6 steps
1
๐Ÿ–ผ๏ธ
One File: HTML + Canvas
A single .html file with a game canvas
Active
๐ŸŽฏ
Goal for this step

Create the game page and get a canvas drawing context.

  • 1Create a file called flappy.html, the WHOLE game lives in this one file.
  • 2It contains a <canvas> element and a <script> tag; the script grabs the canvas 2D context, your drawing toolkit.
  • 3Open the file in a browser (double-click). A sky-blue rectangle = success.
  • 4No compiler, no installs. The browser IS the game engine, that is the superpower of JavaScript.
flappy.html
<!DOCTYPE html>
<html>
<body style="display:flex;justify-content:center;background:#222">
<canvas id="game" width="400" height="600"></canvas>
<script>
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

ctx.fillStyle = "#70c5ce";        // sky
ctx.fillRect(0, 0, 400, 600);
</script>
</body>
</html>
๐Ÿ‘จโ€๐Ÿ‘ง
Parent note: Everything runs locally in the browser, no downloads, no accounts. Any text editor works (Notepad counts!), though the free VS Code is nicer.
โœ๏ธ
Fill in the Blanks
+15 XP
The drawing toolkit comes from canvas.("2d"). Rectangles are drawn with ctx.(x, y, width, height).
๐Ÿง 
Knowledge Check
+15 XP
What is the <canvas> element?
AA picture file
BA blank pixel surface that JavaScript can draw on every frame, the browserโ€™s game screen
CA kind of button
2
๐Ÿฆ
The Bird & the Loop
requestAnimationFrame + gravity
Locked
๐ŸŽฏ
Goal for this step

Animate a bird that falls with gravity in a proper browser game loop.

  • 1The bird is a plain object: { x, y, vy, size }.
  • 2The browserโ€™s game loop is requestAnimationFrame(loop), the browser calls your function every screen refresh (~60/s).
  • 3Each frame: vy += gravity, y += vy, then redraw everything (clear first!).
  • 4Run it: the bird plummets. Perfect. Flapping is next.
flappy.html
const bird = { x: 80, y: 300, vy: 0, size: 24 };
const gravity = 0.45;

function loop() {
    bird.vy += gravity;
    bird.y += bird.vy;

    ctx.fillStyle = "#70c5ce";
    ctx.fillRect(0, 0, 400, 600);
    ctx.fillStyle = "#ffd166";
    ctx.fillRect(bird.x, bird.y, bird.size, bird.size);

    requestAnimationFrame(loop);
}
loop();
โŒจ๏ธ
Code Challenge
+20 XP
Apply gravity to the bird each frame:
flappy.html
bird.vy += ;
bird.y += bird.;
requestAnimationFrame();
๐Ÿ’ก Hint: Gravity grows the velocity; velocity moves the position; the loop schedules itself again by name.
๐Ÿง 
Knowledge Check
+15 XP
Why does loop() call requestAnimationFrame(loop) at its end?
ATo stop the game
BIt schedules the NEXT frame, the function re-books itself forever, creating the game loop
CIt reloads the page
3
๐Ÿ‘†
Flap!
One-button input: click, tap or space
Locked
๐ŸŽฏ
Goal for this step

Make every click/tap/spacebar press launch the bird upward.

  • 1A flap is just bird.vy = -8, replace the velocity, do not add to it (that is what gives Flappy its snap).
  • 2Listen for clicks on the canvas AND the spacebar, one flap function, two listeners.
  • 3Ceiling guard: never let y go below 0.
  • 4Feel the one-button magic: the whole game is now "when do I press?".
flappy.html
function flap() {
    bird.vy = -8;
}

canvas.addEventListener("mousedown", flap);
document.addEventListener("keydown", function(e) {
    if (e.code === "Space") flap();
});
๐Ÿ’ก
Setting vy (not adding) means a flap from a dive recovers instantly, try += instead and feel how mushy it becomes. Game feel lives in details like this.
โœ๏ธ
Fill in the Blanks
+15 XP
Events are hooked with . A flap SETS the velocity to rather than adding to it, which makes recovery instant.
๐Ÿง 
Knowledge Check
+15 XP
One flap function with two event listeners is better than two copies becauseโ€ฆ
ABrowsers limit functions
BChange the flap strength once and every input method updates, no duplicate logic
CEvents need sharing
4
๐ŸŸฉ
The Pipes
Scrolling pipe pairs with random gaps
Locked
๐ŸŽฏ
Goal for this step

Spawn pipe pairs that scroll left with a randomly placed gap.

  • 1One pipe entry = { x, gapY }, the gap centre; top and bottom pipes are drawn from it (gap 150 tall).
  • 2Every 90 frames push a new pipe at x 400 with random gapY (120-480).
  • 3Each frame: every pipe x โˆ’= 2.4; drop pipes past x โˆ’60.
  • 4Frame counting, spawning, scrolling, the arcade toolkit, now in JavaScript.
flappy.html
let pipes = [];
let frames = 0;
const GAP = 150;

// inside loop():
frames++;
if (frames % 90 === 0) {
    pipes.push({ x: 400, gapY: 120 + Math.random() * 360 });
}
for (const p of pipes) p.x -= 2.4;
pipes = pipes.filter(p => p.x > -60);

// drawing each pipe pair:
ctx.fillStyle = "#06d6a0";
for (const p of pipes) {
    ctx.fillRect(p.x, 0, 52, p.gapY - GAP / 2);                 // top
    ctx.fillRect(p.x, p.gapY + GAP / 2, 52, 600);               // bottom
}
โŒจ๏ธ
Code Challenge
+20 XP
Spawn a pipe every 90 frames and scroll them:
flappy.html
if (frames %  === 0)
    pipes.push({ x: 400, gapY: 120 + Math.() * 360 });
for (const p of pipes) p.x -= ;
๐Ÿ’ก Hint: The remainder operator fires on multiples of 90; randomness comes from the Math object; pipes move left a fixed amount.
๐Ÿง 
Knowledge Check
+15 XP
pipes.filter(p => p.x > -60) does the same job as which trick from other languages?
AThe 2D maze array
BremoveIf / erase-remove / the [:] copy, deleting finished objects from a list
CThe spawn timer
5
๐Ÿ’ฅ
Collision & Score
Rectangle overlap plus the pass-the-pipe point
Locked
๐ŸŽฏ
Goal for this step

Die on pipes, floor and ceiling; score one point per pipe passed.

  • 1Write a rects-overlap helper (AABB, the same test as colliderect/intersects, four comparisons).
  • 2Check the bird against BOTH rectangles of each pipe; also die past the floor.
  • 3Scoring: give pipes a passed flag; the first frame pipe-right edge < bird-left โ†’ score++, passed = true.
  • 4Draw the score huge, centred at the top, Flappy style.
flappy.html
function hits(ax, ay, aw, ah, bx, by, bw, bh) {
    return ax < bx + bw && ax + aw > bx &&
           ay < by + bh && ay + ah > by;
}

let score = 0, dead = false;

for (const p of pipes) {
    const topH = p.gapY - GAP / 2;
    if (hits(bird.x, bird.y, 24, 24, p.x, 0, 52, topH) ||
        hits(bird.x, bird.y, 24, 24, p.x, p.gapY + GAP/2, 52, 600))
        dead = true;
    if (!p.passed && p.x + 52 < bird.x) {
        p.passed = true;
        score++;
    }
}
if (bird.y > 576) dead = true;
โœ๏ธ
Fill in the Blanks
+15 XP
The overlap test is called (axis-aligned bounding box) collision. A pipe scores once thanks to its flag.
๐Ÿง 
Knowledge Check
+15 XP
Without the passed flag, flying beside a pipe wouldโ€ฆ
ACrash the game
BScore a point EVERY FRAME the condition holds, about 60 points per pipe
CScore nothing
6
๐Ÿ…
Game Over & Restart
Death screen, best score in localStorage, tap to retry
Locked
๐ŸŽฏ
Goal for this step

Finish the loop: death freezes play, best score persists, tap restarts.

  • 1While dead: skip physics/spawning, keep drawing, overlay "GAME OVER, tap to retry" plus score and best.
  • 2Best score lives in localStorage, the browserโ€™s built-in save file (this very workshop uses it for your XP!).
  • 3A tap while dead resets: bird position/velocity, pipes = [], frames, score, dead = false.
  • 4Tune to taste: gravity 0.45, flap โˆ’8, gap 150, speed 2.4. Two more px of speed = nightmare mode.
flappy.html
let best = Number(localStorage.getItem("flappy-best") || 0);

// on death (once):
if (score > best) {
    best = score;
    localStorage.setItem("flappy-best", best);
}

// in flap():
if (dead) {
    bird.y = 300; bird.vy = 0;
    pipes = []; frames = 0; score = 0;
    dead = false;
    return;
}
โŒจ๏ธ
Code Challenge
+20 XP
Save the best score in the browser:
flappy.html
if (score > best) {
    best = score;
    localStorage.("flappy-best", );
}
๐Ÿ’ก Hint: localStorage stores with a key and a value, write the new record under the same key you read at startup.
๐Ÿง 
Knowledge Check
+15 XP
localStorage survives closing the browser. Which feature of THIS website uses the exact same trick?
AThe fonts
BYour XP, streaks and workshop progress on My Progress
CThe navigation bar
๐ŸŽ‰๐Ÿ†๐ŸŽฎโœจ๐ŸŽ‰
Workshop Complete!
One HTML file, one bird, infinite rage, you built Flappy Bird from scratch in the browser. Episode 2: Memory Match and the DOM!
0
Total XP
1
Level
0
Best Streak
0%
Accuracy
โ–ถ Episode 2: Memory Match โ†’
โญ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi