From ef1acfbdace6ebcc56ac13abd3acbe8731462680 Mon Sep 17 00:00:00 2001 From: Asger Geel Weirsoee Date: Tue, 20 Oct 2026 17:34:15 +0200 Subject: [PATCH] =?UTF-8?q?Docs:=20Tegn=20og=20G=C3=A6t=20i=20protokollen?= =?UTF-8?q?=20og=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nye beskeder, snapshot-felter, tegningernes format og grænser, afsløringens tidslinje, point og priser. Co-Authored-By: Claude Opus 5.5 --- README.md | 5 ++- docs/PROTOCOL.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b5f490..90cd1e0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index e7f9eb1..9b0e23e 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -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 `_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`.