1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 | # Valley Blocks — serene Tetris, now with room-link multiplayer
A Monument Valley-inspired Tetris PWA, running on a serverless
[js-libp2p](https://libp2p.io) mesh. No accounts, no game servers, no database:
the production build is plain static files, and multiplayer rooms live entirely
in the players' browsers. The invite link **is** the room key.
- **Play:** https://game2.ardegazu.ro/
- **Clone:** `git clone https://git.ardegazu.ro/game2.git`
## How multiplayer works
- Open the game — the URL fragment (`#…`) is a fresh room secret. Share the
link; up to 16 players can walk in (a full mesh, so ~12 is the practical
ceiling, enforced by the host).
- Browsers are libp2p peers (`client/src/game2/net/`, vendored from
[neon-grid](https://git.ardegazu.ro/game1/) and game-agnostic except for the
protocol id): they dial the app's
circuit-relay-v2 node on `signal.ardegazu.ro` over WSS, discover each other
through gossipsub, and upgrade to direct WebRTC (the circuit doubles as
signaling and a rate-limited fallback path). The relay's identity is fetched
from `/.well-known/ap2p` at boot with a compiled-in fallback, so relay
rotations never need a client release.
- Rooms are enforced twice: a room-scoped protocol id
(`/game2/2/<HKDF(secret)[:16]>` — wrong rooms fail protocol negotiation) and
a mutual sealed hello (AES-256-GCM under per-sender keys derived from the
room secret, AAD-bound to both noise-authenticated peer ids). After the
hello, game frames ride one long-lived stream per pair — libp2p's noise
layer already encrypts every pair end-to-end, even across the relay. TURN
fallback carries still-encrypted traffic when a direct connection is
impossible.
- **One peer referees.** Host conflicts (cold boots claiming before discovery
finishes, suspend/resume split-brain) resolve by a shared precedence rule:
an active round outranks an idle claim, a joined player outranks a parked
tab, ties break to lowest peer id. The host heartbeats its roster so
late-booting peers learn the incumbent instead of electing themselves; on
host loss the round voids and the lowest surviving id takes over. Rounds are
seat-indexed so roster churn can't corrupt a live round; late joiners get a
snapshot and spectate until the next round.
- Tetris boards are latency-sensitive, so each player simulates their own
board from the host-dealt shared seed (identical pieces for everyone) and
broadcasts it as their input — compact, throttled, deduped `bs` frames.
The host referees everything global: roster, seating, modes
(Race / Race + Survival / Last Standing), win/lose, and scores. The board
simulation ([`game/core/game.cljs`](client/src/game2/game/core/game.cljs)),
the wire protocol ([`game/frames.cljs`](client/src/game2/game/frames.cljs))
and the referee ([`game/party.cljs`](client/src/game2/game/party.cljs)) are
pure enough to be driven headlessly, and golden vectors extracted from the
previous TypeScript implementation pin all three byte for byte.
## The game
SRS rotation with wall kicks, 7-bag randomizer, ghost piece, hold, lock delay,
guideline scoring, speed curve, five Monument Valley palettes (light/dark),
synthesized Web Audio sound, and the rare steerable 🐍 snake bonus piece.
Solo play works fully offline (installable PWA, iOS safe-area aware — the p2p
layer failing to boot never blocks the game); the same build hosts multiplayer
rooms.
Controls: ← → / A D move · ↑ X / Z rotate · ↓ S soft drop · Space hard drop ·
C/Shift hold · P pause. Touch: drag / flick / tap gestures plus an optional
one-handed button bar.
## Develop
Needs node ≥ 22, a JVM ≥ 17 and the `clojure` CLI (shadow-cljs).
```bash
cd client
npm install
npm run dev # watch build → http://localhost:5173
npm test # golden vectors + catalog parity + the source lint
```
Open two tabs on the same `#room` URL to test multiplayer locally. While the
shadow server runs, port 4173 serves the last release build from `client/dist`
(the relay's origin allowlist covers 4173/5173 only). `window.__app` and
`window.__party` expose the app and the net brain in the console.
## Build & deploy
```bash
cd client && npm run build # → client/dist/, plain static files (~416 KB gz with libp2p)
```
Deployed by pinning `client/dist/` to IPFS and publishing its CID to the
site's IPNS name (`ird ipfs add` + `ird ipfs ipns publish game2.ardegazu.ro`);
the domain is an IPFS gateway domain (DNSLink) — no origin server at all.
The repo itself is published the same way: `deploy/publish-repo.sh` builds a
dumb-HTTP-clonable bare mirror (loose objects — IPFS-gateway-friendly) plus a
landing page into `deploy/.site/`, assembled into the shared source root by
`../git/assemble.sh game2` and served at
https://git.ardegazu.ro/game2/ (clone: https://git.ardegazu.ro/game2.git).
## Layout
```
client/
deps.edn shadow-cljs.edn the ClojureScript build (release only; the
committed dist is byte-deterministic)
public/ the static shell: index.html, style.css, icons,
webmanifest
scripts/ dev / build wrappers + the gensym normalizer
src/game2/net/ libp2p layer (bootstrap, node, peers, room
crypto, TURN) — kept byte-identical to
neon-grid's so fixes can be re-copied; the only
game-specific line is the protocol id in
peers.cljs
src/game2/id/boot.cljs the suite identity bridge (vendored, verbatim)
src/game2/i18n/ runtime.cljs (vendored) + en/ro/hu catalogs
src/game2/game/ the game: core/ render/ input/ ui/ audio/
storage/ + frames.cljs (wire) and party.cljs
(the net brain / referee)
src/game2/main.cljs wiring: room secret → create-net → party → app
src/game2/social.cljs the lazy social chunk (friends, invites,
leaderboards, cross-device sync)
test/ node --test suites + the golden vectors
site/ landing page for the published repo
deploy/ publish-repo.sh (mirror builder with anonymity
gate)
```
MIT — see LICENSE.
|