mirror of
https://github.com/ThisTine/Showdown.git
synced 2026-08-18 23:18:48 +07:00
first commit
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
# Showdown — room protocol
|
||||
|
||||
Contract between the frontend and the backend (Rust, in `backend/`). The
|
||||
design goal is **no database**: each room lives entirely in server memory and
|
||||
disappears when its last player disconnects.
|
||||
|
||||
## Transport
|
||||
|
||||
- One WebSocket per player: `GET /ws/room/{roomId}` (upgrade).
|
||||
- All frames are JSON text messages.
|
||||
- The server serves the built frontend (`frontend/dist`) for every other
|
||||
route, so deep links like `/room/lucky-coyote-07` load the SPA.
|
||||
|
||||
## Room lifecycle
|
||||
|
||||
- A room is created in memory the first time someone connects to its id.
|
||||
- Presence **is** the connection: when a socket closes, that player is removed
|
||||
and a new state snapshot is broadcast (this is why friends vanish when they
|
||||
close the tab).
|
||||
- When the last socket closes, the room is deleted. Watchers count as
|
||||
connections (a TV keeps the room alive) but never as players.
|
||||
- `playerId` is generated by the browser and persisted in localStorage. A
|
||||
reconnect with the same `playerId` replaces the old seat (refresh keeps your
|
||||
identity and your vote).
|
||||
|
||||
## Client → server messages
|
||||
|
||||
```jsonc
|
||||
{ "type": "join", "playerId": "uuid", "name": "Billy", "emoji": "🤠" } // first message on every (re)connect
|
||||
{ "type": "watch" } // alternative first message: spectator (TV display)
|
||||
{ "type": "vote", "value": "5" } // value: deck card, or null to retract
|
||||
{ "type": "reveal" } // anyone may reveal; ignored if no votes yet
|
||||
{ "type": "reset" } // anyone may start a new round
|
||||
{ "type": "deck", "cards": ["1","2","3"] } // anyone may swap the deck; implies reset
|
||||
```
|
||||
|
||||
A socket that opens with `watch` instead of `join` is a **watcher**: it
|
||||
receives every state snapshot but never appears in `players`, and any other
|
||||
message it sends is ignored. The frontend's TV view (`/room/{id}/tv`) connects
|
||||
this way.
|
||||
|
||||
## Server → client messages
|
||||
|
||||
The server replies to **every** change (join, leave, vote, reveal, reset,
|
||||
deck) by broadcasting one full-state snapshot to every socket in the room.
|
||||
Snapshots are small (≤ ~16 players), so no deltas are needed.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"type": "state",
|
||||
"state": {
|
||||
"roomId": "lucky-coyote-07",
|
||||
"deck": ["0","1","2","3","5","8","13","21","34","?","☕"],
|
||||
"revealed": false,
|
||||
"round": 3,
|
||||
"players": [
|
||||
{ "id": "uuid", "name": "Billy", "emoji": "🤠", "voted": true, "vote": null }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Rules the server must enforce:
|
||||
|
||||
- `vote` is hidden (`null`) in snapshots until `revealed` is true — only
|
||||
`voted` leaks before the showdown.
|
||||
- Votes are rejected while `revealed` is true.
|
||||
- `reset` and `deck` increment `round`, clear all votes and set
|
||||
`revealed = false`. Clients use the `round` counter to clear local UI state.
|
||||
- `reveal` with zero votes is a no-op.
|
||||
|
||||
## Server shape (implemented in `backend/`)
|
||||
|
||||
All rooms in one `Mutex<HashMap<roomId, Room>>`. Each socket is a single
|
||||
async task: it registers (join/watch), subscribes to the room's broadcast
|
||||
channel, then loops — relaying snapshots out and applying client messages in.
|
||||
Disconnect cleanup removes the seat (and the room, when its socket count hits
|
||||
zero). No persistence anywhere.
|
||||
Reference in New Issue
Block a user