From c3f9a5c8b41ee1ebb2dc74bb25b952a7126c1c5e Mon Sep 17 00:00:00 2001 From: Asger Geel Weirsoee Date: Sun, 18 Oct 2026 13:17:26 +0200 Subject: [PATCH] =?UTF-8?q?Fest-Z:=20eget=20emoji-s=C3=A6t=20og=20nyt=20na?= =?UTF-8?q?vn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fest-spillet hedder nu Fest-Z (kategorierne "Fest-Z: …"), og telefonerne får zoomer-emojis (💀 😭 🧢 🗿 💅 🍻) i stedet for familie-sættet. Serveren sender sættet i tilstanden, så klienten altid viser det, spillet tager imod. Reglerne og speakeren taler om slang og memes frem for pop-sladder. Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu --- deploy/bots.py | 2 +- docs/PROTOCOL.md | 4 +- fupogfakta/data/questions_d_fest.json | 132 +++++++++++++------------- fupogfakta/engine.py | 16 +++- fupogfakta/narrator/lines.json | 6 +- fupogfakta/tests/test_engine.py | 10 ++ party/brain/game.py | 2 +- party/static/party/common.js | 4 +- party/static/party/dev-fixtures.js | 1 + party/static/party/host.js | 1 + party/static/party/play.js | 1 + party/templates/party/host_new.html | 4 +- 12 files changed, 102 insertions(+), 81 deletions(-) diff --git a/deploy/bots.py b/deploy/bots.py index 9b72c18..bf3c3fa 100644 --- a/deploy/bots.py +++ b/deploy/bots.py @@ -103,7 +103,7 @@ async def _player(ws_base, code, name, n_players, done, rng, stop_at_final): await asyncio.sleep(rng.uniform(0.05, 0.4)) if st["phase"] in ("present", "guess", "reveal") and rng.random() < 0.25: for _ in range(rng.randint(1, 8)): # emojis må spammes; serveren drosler - await ws.send(json.dumps({"action": "react", "emoji": rng.choice(["😂", "😱", "👏", "🤯", "🙈", "🔥"])})) + await ws.send(json.dumps({"action": "react", "emoji": rng.choice(st.get("reactions") or ["😂", "😱", "👏", "🤯", "🙈", "🔥"])})) if st["phase"] == "lobby" and me["vip"] and len(st["players"]) >= n_players and key not in acted: acted.add(key) await ws.send(json.dumps({"action": "start"})) diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 71c2fe8..e7f9eb1 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -32,7 +32,7 @@ Alle beskeder er JSON `{"action": "...", ...}`. | `lie_for_me` | | fase `lie`; svar: `suggestion` | | `guess` | `answer_id` | fase `guess`; spillere og publikum, én gang | | `like` | `answer_id` | kun fase `reveal`; slår til/fra; ikke eget svar og ikke sandheden (`answer_invalid`) | -| `react` | `emoji` (en af `😂 😱 👏 🤯 🙈 🔥`) | fase `present`, `guess`, `reveal`; må spammes (serveren dropper stille over 6/s pr. person). Intet svar; storskærmen får `react`. Tælles på det svar, der er i fokus på storskærmen. | +| `react` | `emoji` (en af tilstandens `reactions`: `😂 😱 👏 🤯 🙈 🔥`, i fest-spil `💀 😭 🧢 🗿 💅 🍻`) | fase `present`, `guess`, `reveal`; må spammes (serveren dropper stille over 6/s pr. person). Intet svar; storskærmen får `react`. Tælles på det svar, der er i fokus på storskærmen. | | `play_again` | | VIP i fasen `final` | | `ping` | | svar `pong` | @@ -185,7 +185,7 @@ Rækkefølge: `category → intro → lie → guess → reveal (→ scores)`; i - Publikum: forfatteren får `round(500 × multiplikator × andel af publikum narret)`. - Spil igen: samme spillere, point nulstilles. -## Fest-spil (pakken "fest", 18+) +## Fest-Z (pakken "fest", 18+) Oprettes med `pack=fest` på `/host/new`. Snapshot har altid `fest` (bool). I et fest-spil desuden: diff --git a/fupogfakta/data/questions_d_fest.json b/fupogfakta/data/questions_d_fest.json index cde6b59..7fca772 100644 --- a/fupogfakta/data/questions_d_fest.json +++ b/fupogfakta/data/questions_d_fest.json @@ -1,6 +1,6 @@ [ { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -26,7 +26,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -52,7 +52,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -79,7 +79,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -107,7 +107,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -135,7 +135,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -161,7 +161,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -187,7 +187,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -213,7 +213,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -240,7 +240,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -268,7 +268,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -295,7 +295,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -321,7 +321,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -348,7 +348,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -374,7 +374,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -400,7 +400,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -427,7 +427,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -453,7 +453,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -480,7 +480,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -507,7 +507,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -534,7 +534,7 @@ } }, { - "category": "Fest: nu", + "category": "Fest-Z: nu", "packs": [ "fest" ], @@ -561,7 +561,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -590,7 +590,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -618,7 +618,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -646,7 +646,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -673,7 +673,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -700,7 +700,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -726,7 +726,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -754,7 +754,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -781,7 +781,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -809,7 +809,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -837,7 +837,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -863,7 +863,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -889,7 +889,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -918,7 +918,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -947,7 +947,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -975,7 +975,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1004,7 +1004,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1033,7 +1033,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1061,7 +1061,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1088,7 +1088,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1116,7 +1116,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1144,7 +1144,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1171,7 +1171,7 @@ } }, { - "category": "Fest: slang", + "category": "Fest-Z: slang", "packs": [ "fest" ], @@ -1198,7 +1198,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1225,7 +1225,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1253,7 +1253,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1282,7 +1282,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1311,7 +1311,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1338,7 +1338,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1368,7 +1368,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1397,7 +1397,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1424,7 +1424,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1452,7 +1452,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1481,7 +1481,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1511,7 +1511,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1539,7 +1539,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1567,7 +1567,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1596,7 +1596,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1625,7 +1625,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1654,7 +1654,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1683,7 +1683,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1711,7 +1711,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1738,7 +1738,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1766,7 +1766,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], @@ -1795,7 +1795,7 @@ } }, { - "category": "Fest: internettet", + "category": "Fest-Z: internettet", "packs": [ "fest" ], diff --git a/fupogfakta/engine.py b/fupogfakta/engine.py index e2c2e60..c9055d5 100644 --- a/fupogfakta/engine.py +++ b/fupogfakta/engine.py @@ -34,6 +34,7 @@ REVEAL_STEP_MS = 4000 PRESENT_STEP_MS = 2600 # pr. svar under oplæsningen, når der ikke er stemme PRESENT_INTRO_MS = 1500 REACTIONS = ("😂", "😱", "👏", "🤯", "🙈", "🔥") +REACTIONS_FEST = ("💀", "😭", "🧢", "🗿", "💅", "🍻") # fest: dødt af grin, cap, moai-blik, slay, skål REACT_PER_SECOND = 6 # man må gerne spamme, men ikke uendeligt REVEAL_TAIL_MS = 8000 # opsummeringen efter sidste trin: løgne der eksploderer, point der tæller op @@ -69,15 +70,15 @@ RULES_STEP_MS = 5000 # Fest (18+): samme spil, men det handler om tår. Narret = drik; ingen fandt sandheden = alle drikker. # Antal tårer følger rundens gange (×1/×2/×3), og finalen kaldes en shot. RULES_FEST = [ - {"icon": "🤥", "title": "Lyv som en influencer", "body": "Et sandt faktum om dansk pop mangler et ord. Skriv en løgn, der lyder sand.", - "say": "Yo, bestie, velkommen til festen. I får noget ægte dansk pop-sladder, men der mangler et ord. " + {"icon": "🤥", "title": "Lyv som en influencer", "body": "Et sandt faktum om slang, memes eller dansk pop mangler et ord. Skriv en løgn, der lyder sand.", + "say": "Yo, bestie, velkommen til festen. I får ægte facts om slang, memes og dansk pop, men der mangler et ord. " "Jeres job? At lyve så hårdt, at de andre hopper lige i det. Ingen cap."}, {"icon": "🥃", "title": "Narret? Drik!", "body": "Falder du for en løgn, tager du en tår. Point er sekundært.", "say": "Og nu kommer det vigtige. Falder du for en løgn, så tager du en tår. Det er hele pointen. Point er lowkey ligegyldige."}, {"icon": "🍻", "title": "Ingen fandt sandheden?", "body": "Så drikker alle. Sådan er reglerne.", "say": "Finder ingen af jer sandheden, så drikker alle sammen. Det er bare sådan, det er."}, - {"icon": "🔥", "title": "Det eskalerer", "body": "Runde 2 er to tårer. Finalen er en shot.", - "say": "Runde to er to tårer, og finalen er en hel shot. Drik med omtanke, og drik vand imellem. Lad os gå!"}, + {"icon": "🔥", "title": "Det eskalerer", "body": "Runde 2 er to tår. Finalen er en shot.", + "say": "Runde to er to tår, og finalen er en hel shot. Drik med omtanke, og drik vand imellem. Lad os gå!"}, ] @@ -344,6 +345,10 @@ class Game: return self._begin_question(now) + @property + def reaction_set(self) -> tuple[str, ...]: + return REACTIONS_FEST if self.fest else REACTIONS + @property def rules(self) -> list[dict]: return RULES_FEST if self.fest else RULES @@ -607,7 +612,7 @@ class Game: def react(self, member_id: str, emoji: str, now: float) -> dict | None: """En emoji fra en telefon. Returnerer reaktionen (til storskærmen) eller None, hvis den droppes.""" - if self.phase not in ("present", "guess", "reveal") or emoji not in REACTIONS: + if self.phase not in ("present", "guess", "reveal") or emoji not in self.reaction_set: return None member = self._member(member_id) if not member: @@ -876,6 +881,7 @@ class Game: ], "audience_count": len(self.audience), "fest": self.fest, + "reactions": list(self.reaction_set), } if self.phase not in ("lobby", "final"): name, mult, count, _read = self.round diff --git a/fupogfakta/narrator/lines.json b/fupogfakta/narrator/lines.json index 5e73650..6c33009 100644 --- a/fupogfakta/narrator/lines.json +++ b/fupogfakta/narrator/lines.json @@ -482,7 +482,7 @@ "[sighs] {vip}, festen venter på dig. Start nu, bestie." ], "game_start_fest": [ - "{antal_spillere} spillere, en masse sladder og alt for mange tår. Lad os gå!", + "{antal_spillere} spillere, en masse brainrot og alt for mange tår. Lad os gå!", "[excited] {antal_spillere} spillere er klar. Det her bliver ikke familievenligt. Okay, lidt alligevel." ], "round1_intro_fest": [ @@ -508,7 +508,7 @@ "lie_start_fest": [ "{kategori}. Skriv en løgn så god, at Se og Hør ville trykke den.", "Nu er det {kategori}. Lyv som en influencer med en rabatkode.", - "{kategori}, bestie. Find på noget, der lyder som ægte sladder." + "{kategori}, bestie. Find på noget, der lyder ægte, no cap." ], "lie_hurry_fest": [ "Tiden løber, besties. Skriv noget, hvad som helst!", @@ -587,7 +587,7 @@ "Igen igen! Fyld op, besties." ], "present_intro_fest": [ - "Okay, her er svarene. Lyt godt efter, én af dem er ægte sladder.", + "Okay, her er svarene. Lyt godt efter, én af dem er den ægte vare.", "Jeg læser dem op. Den der lyder mest unhinged er måske sandheden." ], "summary_cleanup_fest": [ diff --git a/fupogfakta/tests/test_engine.py b/fupogfakta/tests/test_engine.py index a441d37..2c4e66b 100644 --- a/fupogfakta/tests/test_engine.py +++ b/fupogfakta/tests/test_engine.py @@ -361,3 +361,13 @@ class FestTests(GameTestCase): normal = Game("N", bank()) self.assertIs(normal.rules, engine.RULES) self.assertFalse(normal.snapshot(self.now)["fest"]) + + def test_fest_has_its_own_emojis(self): + for p, lie in [(self.a, "katte"), (self.b, "hunde"), (self.c, "heste")]: + self.g.submit_lie(p.id, lie, self.now) + self.assertEqual(self.g.phase, "guess") + self.assertEqual(self.g.snapshot(self.now)["reactions"], list(engine.REACTIONS_FEST)) + self.assertIsNotNone(self.g.react(self.a.id, "💀", self.now)) + self.assertIsNone(self.g.react(self.a.id, "😂", self.now + 2)) # familie-sættet gælder ikke her + normal = Game("N", bank()) + self.assertEqual(normal.snapshot(self.now)["reactions"], list(engine.REACTIONS)) diff --git a/party/brain/game.py b/party/brain/game.py index 1339871..5b80278 100644 --- a/party/brain/game.py +++ b/party/brain/game.py @@ -37,7 +37,7 @@ Regler for alt, du skriver: - Brug gerne aftenens hukommelse til en pointe, der bygger videre på noget, der er sket tidligere.""" # Fest-spillet (18+): samme spil, men speakeren er en hypet zoomer, og det handler om tår frem for point. -SYSTEM_FEST = """Du er speakeren i "Fup og Fakta: Fest", et dansk partyspil for unge voksne (18+) i stil med Fibbage. +SYSTEM_FEST = """Du er speakeren i "Fup og Fakta: Fest-Z", et dansk zoomer-partyspil for unge voksne (18+) i stil med Fibbage. Spillet: et kuriøst, sandt faktum vises med et hul. Alle skriver en troværdig løgn. Løgnene blandes med sandheden, og man skal gætte sandheden. Falder man for en løgn, tager man en tår (to i runde to, en shot i finalen). Finder ingen sandheden, drikker alle. Tårene betyder mere end pointene. diff --git a/party/static/party/common.js b/party/static/party/common.js index c6a1557..014c105 100644 --- a/party/static/party/common.js +++ b/party/static/party/common.js @@ -341,7 +341,9 @@ try { return String(s || '').normalize('NFKC').replace(re, ''); } catch (e) { return String(s || ''); } }; /* emojis, telefonerne kan spamme under oplæsning, gæt og afsløring (samme liste som serveren) */ - WPP.REACTIONS = ['😂', '😱', '👏', '🤯', '🙈', '🔥']; + WPP.REACTIONS_BASE = WPP.REACTIONS = ['😂', '😱', '👏', '🤯', '🙈', '🔥']; + /* fest-spil har sit eget sæt; serveren sender listen i tilstanden */ + WPP.useReactions = function (state) { WPP.REACTIONS = (state && state.reactions) || WPP.REACTIONS_BASE; }; WPP.reducedMotion = function () { try { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; } catch (e) { return false; } }; diff --git a/party/static/party/dev-fixtures.js b/party/static/party/dev-fixtures.js index 0a1a48a..366853c 100644 --- a/party/static/party/dev-fixtures.js +++ b/party/static/party/dev-fixtures.js @@ -271,6 +271,7 @@ var sips = { p1: 1, p2: 3, p3: 6, p4: 4, p5: 2, p6: 5 }; [host].concat(phones.map(function (ph) { return ph.state; })).forEach(function (st) { st.fest = true; + st.reactions = ['💀', '😭', '🧢', '🗿', '💅', '🍻']; (st.players || []).forEach(function (p) { p.sips = sips[p.id] || 0; }); if (st.reveal) { st.reveal.steps.forEach(function (s) { diff --git a/party/static/party/host.js b/party/static/party/host.js index 0748da7..568d4ff 100644 --- a/party/static/party/host.js +++ b/party/static/party/host.js @@ -784,6 +784,7 @@ /* ---------- offentlig render ---------- */ function renderHost(root, state, ctx) { + WPP.useReactions(state); ctx = ctx || {}; state = state || {}; var u = ui(root); diff --git a/party/static/party/play.js b/party/static/party/play.js index 965bff9..86e79be 100644 --- a/party/static/party/play.js +++ b/party/static/party/play.js @@ -373,6 +373,7 @@ /* ---------- offentlige render-funktioner ---------- */ function renderPlay(root, state, ctx) { + WPP.useReactions(state); ctx = ctx || {}; root._ctx = ctx; bind(root); diff --git a/party/templates/party/host_new.html b/party/templates/party/host_new.html index 6460407..7ec6c6c 100644 --- a/party/templates/party/host_new.html +++ b/party/templates/party/host_new.html @@ -14,8 +14,8 @@ Flere valg…

Arbejdsspørgsmål: e-faktura, IT-sikkerhed og kategorier med pakken "arbejde".

- -

Dansk pop-sladder med zoomer-speaker. Narret = tag en tår, finalen er en shot.

+ +

Slang, memes og dansk pop med zoomer-speaker. Narret = tag en tår, finalen er en shot.