Docs: Tegn og Gæt i protokollen og README
Nye beskeder, snapshot-felter, tegningernes format og grænser, afsløringens tidslinje, point og priser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Weirsøe Party Protocol
|
||||
|
||||
Dansk party-spil i stil med Jackbox. Spillet vises på en storskærm, og spillerne er med fra deres telefoner. Første spil er **Fup og Fakta**: alle finder på en troværdig løgn til et kuriøst faktum, og så skal man finde sandheden blandt løgnene.
|
||||
Dansk party-spil i stil med Jackbox. Spillet vises på en storskærm, og spillerne er med fra deres telefoner. Første spil er **Fup og Fakta**: alle finder på en troværdig løgn til et kuriøst faktum, og så skal man finde sandheden blandt løgnene. I **Tegn og Gæt** går kæder af ord, tegninger og gæt bordet rundt, og til sidst ser alle, hvad der blev af det første ord. Værten vælger spil, når det oprettes.
|
||||
|
||||
- Spillere: `https://party.weircon.dk` og indtast koden fra skærmen (eller scan QR-koden).
|
||||
- Vært: `https://party.weircon.dk/host/` (Authentik, grupperne Familie og authentik Admins).
|
||||
@@ -13,6 +13,7 @@ Serveren er autoritativ. Hver skærm har én WebSocket, sender handlinger og få
|
||||
| Mappe | Indhold |
|
||||
| --- | --- |
|
||||
| `fupogfakta/` | Spilmotor, modeller for kategorier og spørgsmål, spørgsmålsbank i `data/*.json` |
|
||||
| `tegnoggaet/` | Spilmotor for Tegn og Gæt, temaer i `data/themes.json`, speakerens replikker |
|
||||
| `party/` | Rum, WebSocket-consumers, sider, Authentik-header-login, templates og JS/CSS |
|
||||
| `partyhub/` | Django-projekt (settings, ASGI) |
|
||||
| `deploy/` | systemd-unit, miljøfil-skabelon og `deploy.sh` |
|
||||
@@ -33,7 +34,7 @@ export DJANGO_DEBUG=true DEV_HOST_USER=dev
|
||||
|
||||
Én uvicorn-proces (`--workers 1`) bag en reverse proxy med TLS. Proxyen beskytter `/host/`, `/ws/host/` og `/admin/` med Authentik forward-auth og sender `X-authentik-username` og `X-authentik-groups` videre; på alle andre stier skal den blanke de headere. Appen stoler kun på dem fra adresserne i `TRUSTED_PROXIES`, så appens port bør kun kunne nås fra proxyen. Se `deploy/wpp.env.example` for miljøvariabler.
|
||||
|
||||
`deploy/bots.py` spiller et helt spil mod en kørende server og kan også fylde et spil op med bots (`--code`).
|
||||
`deploy/bots.py` spiller et helt spil mod en kørende server (`--game tegn` for Tegn og Gæt) og kan også fylde et spil op med bots (`--code`). Alle skærme kan ses uden server i `party/static/party/dev.html` (`?game=tegn&phase=draw` for Tegn og Gæt).
|
||||
|
||||
## Spørgsmål
|
||||
|
||||
|
||||
@@ -195,3 +195,99 @@ Oprettes med `pack=fest` på `/host/new`. Snapshot har altid `fest` (bool). I et
|
||||
- `me.sips`, `me.last_sips` (sidstnævnte kun i `reveal`).
|
||||
- `final.awards` får `kind: "sips"` ("Tørstigste").
|
||||
- Reglerne (`rules.items`) er fest-udgaven, og speakeren bruger `<begivenhed>_fest`-replikker og `FEST_VOICE_ID`.
|
||||
|
||||
## Tegn og Gæt
|
||||
|
||||
Oprettes med `game=tegn` på `/host/new` (uden feltet, eller med `game=fof`, oprettes Fup og Fakta som før).
|
||||
Samme endpoints, samme `join`/`resume`/`ping`/`react`, og samme `joined`/`state`/`error`. Snapshot har `"game": "tegn"`.
|
||||
3-10 spillere; dem der joiner efter start, bliver publikum (de kan give hjerter).
|
||||
|
||||
Med N spillere er der N kæder, én pr. spiller. I trin `t` (0..N-1) arbejder spiller `i` (rækkefølgen ved start)
|
||||
på kæde `(i - t) mod N`: trin 0 er et ord, ulige trin tegnes, lige trin gættes. Ingen får sin egen kæde igen, og
|
||||
hver kæde ender med N bidrag. Med et lige antal spillere ender kæden på en tegning; så er "sidste gæt" gættet før den.
|
||||
|
||||
Faser: `lobby → rules` (kun rummets første spil) `→ theme` (20 s) `→ word` (30 s) `→ draw` (75 s) / `guess` (30 s) skiftevis
|
||||
`→ reveal → final`. Et trin slutter, når alle forbundne spillere har afleveret, eller når tiden er gået. Så fyldes det
|
||||
manglende ud med spillerens kladde, ellers et eksempel-ord fra temaet, en tom tegning eller `???`. Spillet hænger aldrig.
|
||||
|
||||
**Spiller (`/ws/play/`)**
|
||||
|
||||
| action | felter | hvornår |
|
||||
| --- | --- | --- |
|
||||
| `start` | | VIP'en (første spiller) i lobbyen |
|
||||
| `choose_theme` | `theme_id` | VIP'en i fasen `theme` |
|
||||
| `submit_word` | `text` (1-30 tegn) | fase `word` |
|
||||
| `suggest` | | fase `word`; svar `suggestion` med et eksempel-ord, som ingen andre har skrevet |
|
||||
| `submit_drawing` | `strokes` (se nedenfor) | fase `draw` |
|
||||
| `submit_guess` | `text` (1-30 tegn) | fase `guess` |
|
||||
| `draft` | `strokes` (fase `draw`) eller `text` | undervejs; bruges, hvis tiden løber ud. Intet svar, og en ugyldig kladde ignoreres stille |
|
||||
| `like` | `entry_id` | fase `reveal`; slår til/fra; kun tegninger og gæt, som storskærmen har vist, og ikke ens egne. +100 til den, der lavede bidraget |
|
||||
| `react` | `emoji` | fase `reveal`; som i Fup og Fakta (`answer_id` i `react` er bidraget i fokus) |
|
||||
| `play_again` | | VIP'en i fasen `final` |
|
||||
|
||||
**Vært (`/ws/host/`)**: som Fup og Fakta, plus `choose_theme` (`theme_id`). `skip` i `reveal` springer til næste kæde
|
||||
(efter sidste kæde: `final`); i de andre faser afslutter det fasen.
|
||||
|
||||
**Tegninger** er vektorstrøg, ikke billeder: `[{"color": "#e63946", "width": 0.008, "points": [[x, y], ...]}]`.
|
||||
Koordinaterne er normaliserede `0..1` på et lærred i forholdet 4:3, `width` er en andel af bredden (`0.002..0.06`).
|
||||
Farven skal være en af `#1d1b2e #e63946 #2a7de1 #2bb673`. Serveren afviser (`drawing_invalid`) mere end 400 strøg,
|
||||
mere end 20.000 punkter i alt, mere end 64 KB JSON, punkter uden for lærredet og alt andet, der ikke har formen ovenfor.
|
||||
Punkterne rundes til 4 decimaler.
|
||||
|
||||
**Server → klient**, ud over Fup og Fakta: `drawings` (kun til storskærmen, én gang pr. afsløring og ved genforbindelse,
|
||||
før `state`): `{"type": "drawings", "reveal_id": 1, "drawings": {"c0e1": [strøg...]}}`. Tegningerne følger ikke med i
|
||||
snapshottet, så et hjerte ikke sender dem alle igen.
|
||||
|
||||
Fejlkoder ud over Fup og Fakta: `word_invalid`, `guess_invalid`, `drawing_invalid`.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"game": "tegn",
|
||||
"phase": "draw", // lobby | rules | theme | word | draw | guess | reveal | final
|
||||
"min_players": 3, "max_players": 10,
|
||||
"round": null,
|
||||
"players": [{"id": "p1", "name": "Asger", "color": "#ff4d6d", "score": 0, "connected": true, "vip": true,
|
||||
"done": false}], // done = har afleveret i trinnet
|
||||
"rules": {...}, // fase rules, som Fup og Fakta
|
||||
"theme": {"options": [{"id": "dyr", "name": "Dyr", "icon": "🐘"}], "chosen_id": null}, // fase theme
|
||||
"theme": {"name": "Dyr", "icon": "🐘"}, // word/draw/guess/reveal
|
||||
"step": {"index": 1, "number": 2, "of": 5, "kind": "draw"}, // word/draw/guess
|
||||
"reveal": {
|
||||
"id": 1,
|
||||
"started_at": 1790000000000, // ms epoch; null = serveren laver stadig lyd
|
||||
"plan": [ // tidslinjen: pr. kæde en intro (entry -1), hvert bidrag, og slutningen (entry N)
|
||||
{"chain": 0, "entry": -1, "ms": 2500, "audio": null},
|
||||
{"chain": 0, "entry": 0, "ms": 3000, "audio": null},
|
||||
{"chain": 0, "entry": 5, "ms": 4500, "audio": {"url": "…", "ms": 3200, "text": "Fra kat til sofa."}}
|
||||
],
|
||||
"chains": [{"owner": "p1", "survived": false, // survived: sidste gæt = første ord (normaliseret)
|
||||
"entries": [{"id": "c0e0", "kind": "word", "by": "p1", "text": "kat", "auto": false, "likes": 0},
|
||||
{"id": "c0e1", "kind": "drawing", "by": "p2", "text": null, "auto": false, "likes": 2}]}]
|
||||
},
|
||||
"final": {"ranking": [...], "awards": [{"title": "Bedste tegning", "player_id": "p2", "detail": "3 hjerter",
|
||||
"kind": "drawing"}]}, // kind: drawing | guess | survived | transformed
|
||||
"me": {
|
||||
"id": "p1", "name": "Asger", "role": "player", "vip": true, "score": 0,
|
||||
"can_choose": false, // fase theme: VIP'en vælger
|
||||
"likes": ["c0e1"], // fase reveal
|
||||
"task": { // word/draw/guess; null for publikum
|
||||
"kind": "draw", "done": false,
|
||||
"from": "p5", // hvem det forrige bidrag er fra
|
||||
"text": "giraf", // det, der skal tegnes (fase draw); ellers null
|
||||
"drawing": null, // tegningen, der skal gættes (fase guess); ellers null
|
||||
"mine": null, // eget ord/gæt, når det er afleveret
|
||||
"draft": null // serverens kladde (genindlæst side)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Telefonen ser kun sit eget trin (forrige ord eller forrige tegning), aldrig resten af kæden før afsløringen. Under
|
||||
afsløringen får telefonerne kun de bidrag, storskærmen har vist (og `survived` først, når kædens slutning er vist;
|
||||
indtil da `null`); rummet sender et nyt snapshot, hver gang et trin begynder. Storskærmen får hele afsløringen og
|
||||
styrer selv tempoet ud fra `started_at` og `plan[].ms`.
|
||||
|
||||
Point: +100 pr. hjerte til den, der lavede bidraget, og +500 til ejeren af en kæde, der overlevede. Priser:
|
||||
"Bedste tegning" og "Bedste gæt" (flest hjerter, ved lighed det første i afsløringen), "Kæden der overlevede" og
|
||||
"Største forvandling" (mindst lighed mellem første ord og sidste gæt). Temaerne står i `tegnoggaet/data/themes.json`
|
||||
og speakerens replikker i `tegnoggaet/narrator/lines.json`.
|
||||
|
||||
Reference in New Issue
Block a user