Skærmen følger speakeren, opsummering, musik, lydeffekter og drillerier
CI / test (push) Successful in 28s
CI / test (push) Successful in 28s
Faseskift venter, til speakeren er færdig med den aktuelle skærm, og replikker til en skærm, der er væk, afspilles ikke. Ny kategori-intro med en lyd pr. kategori, musik pr. fase med bløde overgange og lydeffekter til alt, der flyver over skærmen. Efter afsløringen eksploderer løgne, ingen valgte, og pointene tæller op. Hjerter gives kun under afsløringen og ses live på storskærmen. Lyv for mig giver halve point. Narrede spilleres telefon kan blive drillet med et fjollet billede og et buh. Alle lyd- og billedfiler serveres med versionerede navne og caches af browseren. Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
This commit is contained in:
+60
-15
@@ -31,7 +31,7 @@ Alle beskeder er JSON `{"action": "...", ...}`.
|
||||
| `submit_lie` | `text` (1-40 tegn) | fase `lie` |
|
||||
| `lie_for_me` | | fase `lie`; svar: `suggestion` |
|
||||
| `guess` | `answer_id` | fase `guess`; spillere og publikum, én gang |
|
||||
| `like` | `answer_id` | fase `guess` (efter eget gæt) og `reveal`; slår til/fra; ikke eget svar |
|
||||
| `like` | `answer_id` | kun fase `reveal`; slår til/fra; ikke eget svar og ikke sandheden (`answer_invalid`) |
|
||||
| `play_again` | | VIP i fasen `final` |
|
||||
| `ping` | | svar `pong` |
|
||||
|
||||
@@ -43,7 +43,8 @@ Alle beskeder er JSON `{"action": "...", ...}`.
|
||||
| --- | --- |
|
||||
| `state` | `state`: snapshot (se nedenfor) |
|
||||
| `joined` | `token`, `player_id`, `role` (`player`/`audience`) |
|
||||
| `suggestion` | `text` |
|
||||
| `suggestion` | `text` (svar på `lie_for_me`) |
|
||||
| `tease` | `text`, `image` (URL), `sound` (URL eller `null`) – kun til den spiller, der lige faldt for en løgn, i det øjeblik stemplet "LØGN" lander på storskærmen. Telefonen viser et fuldskærms-drilleri (~2,5 s, lukkes ved tryk), spiller `boo` og derefter `sound`, hvis lyden er låst op. Højst 2 pr. spørgsmål. |
|
||||
| `error` | `code`, `message` (dansk, kan vises direkte) |
|
||||
| `pong` | |
|
||||
|
||||
@@ -54,7 +55,7 @@ Fejlkoder: `unknown_room`, `unknown_token`, `name_taken`, `name_invalid`, `not_a
|
||||
```jsonc
|
||||
{
|
||||
"code": "KLMT",
|
||||
"phase": "lobby", // lobby | category | lie | guess | reveal | scores | final
|
||||
"phase": "lobby", // lobby | category | intro | lie | guess | reveal | scores | final
|
||||
"server_time": 1790000000000,// ms epoch; klienten udregner sin forskydning
|
||||
"deadline": 1790000060000, // ms epoch eller null
|
||||
"join_url": "https://party.weircon.dk/?code=KLMT",
|
||||
@@ -66,20 +67,34 @@ Fejlkoder: `unknown_room`, `unknown_token`, `name_taken`, `name_invalid`, `not_a
|
||||
"connected": true, "vip": true, "done": false} // done = har løjet/gættet i nuværende fase
|
||||
],
|
||||
"audience_count": 0,
|
||||
"category": {"chooser_id": "p2", "options": [{"id": 3, "name": "Dyreriget"}]}, // kun i fase category
|
||||
"question": {"category": "Dyreriget", "prompt": "En flok _____ kaldes en parlament."}, // lie/guess/reveal
|
||||
"answers": [{"id": "a1", "text": "ugler"}], // guess + reveal; spillerens egen løgn er udeladt i guess
|
||||
"category": {"chooser_id": "p2", "options": [{"id": 3, "name": "Dyreriget"}],
|
||||
"chosen_id": null}, // kun i fase category; id når der er valgt (ellers null)
|
||||
"intro": {"category": "Dyreriget", "slug": "dyreriget", "final": false}, // kun i fase intro
|
||||
"question": {"category": "Dyreriget", "prompt": "En flok _____ kaldes et parlament.",
|
||||
"slug": "dyreriget"}, // lie/guess/reveal
|
||||
"answers": [{"id": "a1", "text": "ugler"}], // guess + reveal (i reveal med "likes"); egen løgn udeladt i guess
|
||||
"reveal": { // kun i fase reveal
|
||||
"steps": [ // løgne først (kun dem der blev valgt), sandheden sidst
|
||||
{"answer_id": "a3", "text": "krager", "is_truth": false, "house": false,
|
||||
"authors": ["p2"], "guessed_by": ["p1", "p3"], "audience_votes": 2, "likes": 1,
|
||||
"points": [{"player_id": "p2", "delta": 1000}]},
|
||||
"authors": ["p2", "p5"], "assisted": ["p5"], // assisted = forfattere der brugte "Lyv for mig" uændret
|
||||
"guessed_by": ["p1", "p3"], "audience_votes": 2, "likes": 1,
|
||||
"points": [{"player_id": "p2", "delta": 1000}, {"player_id": "p5", "delta": 500}],
|
||||
"audio": {"url": "/media/tts/…mp3", "ms": 3100, "text": "…"}}, // speakerens replik til trinnet eller null
|
||||
{"answer_id": "a1", "text": "ugler", "is_truth": true, "house": false,
|
||||
"authors": [], "guessed_by": ["p4"], "audience_votes": 1, "likes": 0,
|
||||
"points": [{"player_id": "p4", "delta": 1000}]}
|
||||
"authors": [], "assisted": [], "guessed_by": ["p4"], "audience_votes": 1, "likes": 0,
|
||||
"points": [{"player_id": "p4", "delta": 1000}], "audio": null}
|
||||
],
|
||||
"step_ms": 4000, // værtsskærmen viser ét trin pr. step_ms
|
||||
"note": "Forklaring på faktummet" // vises under sandheden
|
||||
"all": [ // opsummeringen: ALLE svar, også dem ingen valgte
|
||||
{"id": "a7", "text": "skildpadder", "is_truth": false, "house": true,
|
||||
"authors": [], "assisted": [], "picked": false, "likes": 0} // picked=false → svaret "eksploderer"
|
||||
],
|
||||
"deltas": [{"player_id": "p1", "delta": 1500}], // point i dette spørgsmål pr. spiller (inkl. hjerter)
|
||||
"step_ms": 4000, // standardlængde pr. trin (bagudkompatibel)
|
||||
"steps_ms": [4200, 3900], // faktisk længde pr. trin (følger speakerens replik)
|
||||
"tail_ms": 8000, // opsummeringen efter sidste trin
|
||||
"started_at": 1790000001000, // ms epoch; null = serveren laver stadig lyd (vis "trommehvirvel")
|
||||
"note": "Forklaring på faktummet", // vises under sandheden
|
||||
"note_audio": {"url": "/media/tts/…mp3", "ms": 5200} // forklaringen læst op (starter med sandhedens trin) eller null
|
||||
},
|
||||
"deltas": [{"player_id": "p1", "delta": 1500}], // fase scores: point i den netop afsluttede runde
|
||||
"final": { // kun i fase final
|
||||
@@ -98,7 +113,34 @@ Fejlkoder: `unknown_room`, `unknown_token`, `name_taken`, `name_invalid`, `not_a
|
||||
}
|
||||
```
|
||||
|
||||
Værten får samme snapshot uden `me`, men med `"host": true`.
|
||||
Værten får samme snapshot uden `me`, men med `"host": true` og desuden:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"phase_key": "1.0.lie", // "<runde>.<spørgsmål>.<fase>" – identificerer den aktuelle skærm
|
||||
"narration": [ // speakerens seneste replikker (rullende vindue)
|
||||
{"id": 41, "url": "/media/tts/…mp3", "ms": 2300, "at": 1790000000000, "text": "…",
|
||||
"key": "1.0.intro"} // den skærm (phase_key) replikken hører til; er skærmen væk, springes den over
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Klienten afspiller nye replikker (`id` > sidst sete) i rækkefølge; ved første snapshot kun dem fra de sidste ~4 s.
|
||||
Afsløringens replikker ligger i `reveal.steps[].audio` / `reveal.note_audio` og startes af tidslinjen.
|
||||
|
||||
### Afsløringens tidslinje
|
||||
|
||||
Trin `i` starter ved `started_at + sum(steps_ms[0..i-1])`. Inden for et trin vises først svaret, så hvem der valgte det
|
||||
(~28 % / højst 1,1 s), så stemplet (~55 % / højst 2,3 s). Efter sidste trin kører opsummeringen i `tail_ms`:
|
||||
alle svar (`all`), de ikke-valgte eksploderer, og `deltas` tælles op. `deadline` = slutningen af halen.
|
||||
Mens `started_at` er `null`, venter serveren på speakerens lydfiler (højst ~10 s) og sætter så tiderne.
|
||||
|
||||
### Lyd- og billedfiler
|
||||
|
||||
Templates får `assets` = `{"sfx": {navn: url}, "tease": [url]}` indlejret med
|
||||
`{{ assets|json_script:"wpp-assets" }}`. URL'erne er versionerede (hashede filnavne i drift → caches for altid);
|
||||
klienten slår alle lyde op her og falder tilbage til `/static/party/sfx/<navn>.mp3`. Kategoriens jingle hedder
|
||||
`cat_<slug>` (fx `cat_mad_og_drikke`); mangler den, bruges `category`.
|
||||
|
||||
## Spillets gang (Fibbage-klassisk)
|
||||
|
||||
@@ -108,11 +150,14 @@ Værten får samme snapshot uden `me`, men med `"host": true`.
|
||||
| Runde 2 | 3 | 2000 / 1000 |
|
||||
| Finale | 1 | 3000 / 1500 |
|
||||
|
||||
For hvert spørgsmål: `category` (15 s; skiftende spiller vælger blandt 4; i finalen springes fasen over) → `lie` (60 s, slutter når alle har løjet) → `guess` (30 s, slutter når alle spillere har gættet) → `reveal` (automatisk: antal trin × `step_ms` + 3 s; værten kan `skip`). Efter sidste spørgsmål i en runde: `scores` (10 s). Efter finalen: `final`.
|
||||
For hvert spørgsmål: `category` (15 s; skiftende spiller vælger blandt 4; i finalen springes fasen over) → `intro` (≈5 s; storskærmen præsenterer kategorien med sin jingle, speakeren siger kategorien, og serveren venter til speakeren er færdig) → `lie` (60 s, speakeren læser spørgsmålet op; slutter når alle har løjet) → `guess` (30 s, slutter når alle spillere har gættet) → `reveal` (automatisk: `sum(steps_ms)` + `tail_ms`; værten kan `skip`). Efter sidste spørgsmål i en runde: `scores` (10 s). Efter finalen: `final`.
|
||||
|
||||
Rækkefølge: `category → intro → lie → guess → reveal (→ scores)`; i finalen `intro → lie → guess → reveal → final`.
|
||||
|
||||
- Løgn lig sandheden (eller næsten lig) afvises med `lie_is_truth`. Ens løgne slås sammen, og alle forfattere får point.
|
||||
- Spillere, der ikke løj i tide, får ingen løgn; huset fylder op med lokkesvar, så der er mindst `max(4, spillere+1)` svar.
|
||||
- Man kan ikke vælge sin egen løgn.
|
||||
- Like: +100 til forfatteren pr. like (uden multiplikator).
|
||||
- Hjerter (like) gives kun under `reveal`: +100 til forfatteren pr. hjerte (uden multiplikator); ikke til sandheden og ikke til eget svar.
|
||||
- "Lyv for mig": bruger man forslaget uændret, får man kun halve point for at narre andre (forfatteren står i `assisted`).
|
||||
- Publikum: forfatteren får `round(500 × multiplikator × andel af publikum narret)`.
|
||||
- Spil igen: samme spillere, point nulstilles.
|
||||
|
||||
Reference in New Issue
Block a user