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:
agw
2026-10-20 17:34:15 +02:00
co-authored by Claude Opus 5.5
parent a3310c2325
commit ef1acfbdac
2 changed files with 99 additions and 2 deletions
+3 -2
View File
@@ -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
+96
View File
@@ -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`.