Clumsy Bird¶
Flappy-style endless flyer with audio, score and restart.
▶ Run in browserUpstream: 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()