Reactive Flappy Bird clone built entirely with Observables using RxJS. All inputs, time, physics, collisions, state transitions, and rendering are modeled as streams and pure transformations (FRP style).
- Framework: Vite + TypeScript + RxJS 7
- Rendering: SVG
- Architecture: Pure state reducers scanned over action streams; rendering is derived data; DOM updates are contained to a single subscription
- Extras: Ghost runs with a simple leaderboard, dynamic pipe score labels, theme toggle, lives with brief invulnerability
- Click anywhere: start the game
- Space: jump
- R / Enter / Escape: restart after game over
- Theme toggle button (
#themeToggle): switch light/dark
npm installnpm run devCtrl-click the URL printed in the terminal to open the app.
npm test # watch mode
npm run test:ui
npm run test:run # headless/CInpm run buildGenerates a new CSV to assets/map.csv with randomized pipe gaps and times.
npm run generate-pipessrc/main.ts: Entrypoint. Wires streams together, fetches the CSV map, starts rendering on first click.src/state.ts: Createstick$,jump$,restart$and scans pure reducers into the mainStateobservable.src/physics.ts: Pure physics/jump tick reducers.src/collision.ts: Pure collision detection and status updates.src/rendering.ts: ConvertsStateto render data (SVG element descriptors). DOM writes are centralized elsewhere.src/ghost.ts: Ghost run sampling/visibility and leaderboard computation.src/config.ts: Central config for assets, physics, gameplay, UI selectors, and timings.src/utils.ts: Observable factory helpers (time, keys, combination utilities) and dependency providers.src/types.ts: Shared types, constants, and render/state shapes.src/styles/style.css: Styles including theme classes.index.html: SVG canvas and UI elements (ids must match selectors in config).assets/:birb.pngsprite andmap.csvlevel data.tests/main.test.ts: Sample tests using Vitest.
- Game actions are streams:
tick$emits on an intervaljump$emits on Space keyrestart$emits on R/Enter/Escape (only restarts when the run has ended)
- Actions are pure functions
(State) -> Statecomposed viamergeand accumulated withscanto producestate$. - Rendering derives immutable
RenderDatafromstate$and applies DOM updates in a single subscription, keeping side-effects contained. - Pipe layout is loaded from
assets/map.csvviafromFetch. The first body click starts the game loop. - Ghost positions are sampled and displayed semi-transparently; a small leaderboard tracks top runs.
Most knobs live in src/config.ts:
- Assets:
assets/birb.png,assets/map.csv - Physics: gravity, jump force, horizontal speed, invulnerability window
- Gameplay: initial lives, sampling rates, leaderboard size
- UI: SVG/viewBox, selectors (e.g.,
#svgCanvas,#gameOver,#themeToggle) - Timing: tick rate
This repo uses Prettier. To format:
npx prettier . --writeThe configuration is in .prettierrc.json. If using VS Code, install the Prettier extension and enable format-on-save if desired.
This project demonstrates FRP style using RxJS and immutable state updates. Keep logic pure and side-effects at the boundaries to align with assessment criteria.