Clumsy Bird

Flappy-style endless flyer with audio, score and restart.

▶ Run in browser

Upstream: https://github.com/ellisonleao/clumsy-bird

Tags: port tier-0

Clumsy Bird (SimVX port)

SimVX port of ellisonleao/clumsy-bird, a MelonJS Flappy Bird clone: flap through pipe gaps, score per pipe passed, crash restarts.

Licensing: GPL-3.0-or-later (matching upstream code); all art is CC0 (Kenney Tappy Plane) or procedural, all audio synthesised. See ATTRIBUTION.md.

Run

uv run python examples/ports/clumsy_bird/main.py            # interactive
uv run python examples/ports/clumsy_bird/main.py --test     # headless capture
uv run simvx export web examples/ports/clumsy_bird/main.py -o /tmp/clumsy_bird.html

Controls

  • Space / Left click: flap (also starts the run and restarts after a crash)

  • Esc: quit

Source

 1#!/usr/bin/env python3
 2"""Clumsy Bird: Flappy-style endless flyer with audio, score and restart.
 3
 4# /// simvx
 5# tags = ["port", "tier-0"]
 6# upstream = "https://github.com/ellisonleao/clumsy-bird"
 7# web = { width = 900, height = 600, responsive = false }
 8# ///
 9
10Run desktop:   uv run python examples/ports/clumsy_bird/main.py
11Headless:      uv run python examples/ports/clumsy_bird/main.py --test
12Web export:    uv run simvx export web examples/ports/clumsy_bird/main.py -o /tmp/clumsy_bird.html
13
14Controls:
15    Space / Click    Flap
16    Esc              Quit
17"""
18
19# /// script
20# requires-python = ">=3.14"
21# dependencies = ["simvx-core", "simvx-graphics", "numpy", "pillow"]
22# ///
23
24from __future__ import annotations
25
26import sys
27from pathlib import Path
28
29_PORT_DIR = Path(__file__).resolve().parent
30sys.path.insert(0, str(_PORT_DIR))
31
32from config import HEIGHT, WIDTH  # noqa: E402
33from nodes.play_scene import PlayScene  # noqa: E402
34
35from simvx.core import InputMap, Key, MouseButton  # noqa: E402
36from simvx.graphics import App  # noqa: E402
37
38
39class Game(PlayScene):
40    """Root scene: wraps PlayScene to register InputMap actions in on_ready."""
41
42    def on_ready(self):
43        # InputMap actions MUST live in the root node's on_ready, not main(),
44        # so the web exporter sees them (it skips main()).
45        InputMap.add_action("flap", [Key.SPACE, MouseButton.LEFT])
46        super().on_ready()
47
48
49def main():
50    test_mode = "--test" in sys.argv
51    app = App(width=WIDTH, height=HEIGHT, title="Clumsy Bird", visible=not test_mode)
52
53    if test_mode:
54        from simvx.graphics import save_png
55        out = _PORT_DIR / "screenshots"
56        out.mkdir(exist_ok=True)
57        frames = app.run_headless(Game(), frames=180, capture_frames=[60, 120, 179])
58        for idx, n in enumerate([60, 120, 179]):
59            save_png(out / f"frame_{n}.png", frames[idx])
60        print(f"Wrote {len(frames)} screenshots to {out}")
61        app.quit()
62        return
63
64    app.run(Game())
65
66
67if __name__ == "__main__":
68    main()