From 2ceb5290f81568d8b150a365417beb85725311d9 Mon Sep 17 00:00:00 2001 From: Asger Geel Weirsoee Date: Tue, 20 Oct 2026 21:59:49 +0200 Subject: [PATCH] =?UTF-8?q?Temaer:=20f=C3=A6lles=20temabank=20i=20party/da?= =?UTF-8?q?ta/themes.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tegn og Gæts temaer flyttes til ét sted (party/data/themes.json med loaderen party/themes.py), så Spionen kan bruge de samme i stedet for en kopi. Navne og ord er uændrede, så Tegn og Gæt og dets forvarmede {tema}-replikker opfører sig som før. Spionen skal bruge ord, det er nemt at give stikord til: spy_themes() giver de temaer, der har mindst 15 konkrete navneord i ét ord. "spion": false udelader et tema (film og serier: titler), og "spion_skip" enkelte ord (si, and, frikvarter, lektier, hygge). Det giver 13 temaer med 15 til 18 ord. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/PROTOCOL.md | 2 +- party/data/themes.json | 355 +++++++++++++++++++++++++++++++ party/tegn.py | 2 +- party/tests/test_themes.py | 27 +++ party/tests/test_tts_fallback.py | 2 +- party/tests/test_tts_warm.py | 2 +- party/themes.py | 33 +++ tegnoggaet/data/themes.json | 45 ---- tegnoggaet/tests/test_engine.py | 2 +- tegnoggaet/themes.py | 10 - 11 files changed, 421 insertions(+), 61 deletions(-) create mode 100644 party/data/themes.json create mode 100644 party/tests/test_themes.py create mode 100644 party/themes.py delete mode 100644 tegnoggaet/data/themes.json delete mode 100644 tegnoggaet/themes.py diff --git a/README.md b/README.md index b7c6e16..14927b2 100644 --- a/README.md +++ b/README.md @@ -13,7 +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 | +| `tegnoggaet/` | Spilmotor for Tegn og Gæt og speakerens replikker (temaerne er fælles: `party/data/themes.json`) | | `party/` | Rum, WebSocket-consumers, sider, Authentik-header-login, templates og JS/CSS; spilregistret i `games.py` | | `partyhub/` | Django-projekt (settings, ASGI) | | `deploy/` | systemd-unit, miljøfil-skabelon og `deploy.sh` | diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 1f70350..a70191b 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -312,5 +312,5 @@ 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` +"Største forvandling" (mindst lighed mellem første ord og sidste gæt). Temaerne står i `party/data/themes.json` og speakerens replikker i `tegnoggaet/narrator/lines.json`. diff --git a/party/data/themes.json b/party/data/themes.json new file mode 100644 index 0000000..755d0ee --- /dev/null +++ b/party/data/themes.json @@ -0,0 +1,355 @@ +[ + { + "id": "dyr", + "name": "Dyr", + "icon": "🐘", + "words": [ + "kat", + "hund", + "elefant", + "giraf", + "pingvin", + "krokodille", + "blæksprutte", + "flodhest", + "pindsvin", + "edderkop", + "sommerfugl", + "kænguru", + "hval", + "ugle", + "snegl", + "zebra", + "papegøje", + "isbjørn" + ] + }, + { + "id": "film", + "name": "Film og serier", + "icon": "🎬", + "words": [ + "løvernes konge", + "frost", + "star wars", + "harry potter", + "titanic", + "jurassic park", + "find nemo", + "shrek", + "toy story", + "ringenes herre", + "olsen banden", + "spiderman", + "batman", + "pippi langstrømpe", + "minions", + "mary poppins", + "ghostbusters" + ], + "spion": false + }, + { + "id": "koekken", + "name": "Ting i køkkenet", + "icon": "🍳", + "words": [ + "gaffel", + "pande", + "køleskab", + "brødrister", + "piskeris", + "kaffemaskine", + "grydeske", + "ovn", + "mikroovn", + "kagerulle", + "si", + "osteskærer", + "opvaskemaskine", + "tekande", + "skærebræt", + "kasserolle", + "køkkenrulle" + ], + "spion_skip": [ + "si" + ] + }, + { + "id": "jul", + "name": "Jul", + "icon": "🎄", + "words": [ + "juletræ", + "julemand", + "nisse", + "rensdyr", + "snemand", + "gave", + "risalamande", + "pebernødder", + "stjerne", + "kravlenisse", + "slæde", + "julehjerte", + "lys", + "and", + "juletræsfod", + "mandelgave", + "kalenderlys", + "sne" + ], + "spion_skip": [ + "and" + ] + }, + { + "id": "sport", + "name": "Sport", + "icon": "⚽", + "words": [ + "fodbold", + "håndbold", + "cykelløb", + "svømning", + "tennis", + "golf", + "skihop", + "boksning", + "badminton", + "basketball", + "bueskydning", + "surfing", + "ishockey", + "kunstskøjteløb", + "maraton", + "vægtløftning", + "ridning" + ] + }, + { + "id": "mad", + "name": "Mad", + "icon": "🍕", + "words": [ + "pizza", + "pandekager", + "spaghetti", + "hotdog", + "frikadeller", + "sushi", + "burger", + "is", + "banan", + "vandmelon", + "æbleskiver", + "smørrebrød", + "popcorn", + "rugbrød", + "flæskesteg", + "kanelsnegl", + "tacos", + "lagkage" + ] + }, + { + "id": "strand", + "name": "På stranden", + "icon": "🏖️", + "words": [ + "sandslot", + "parasol", + "badebold", + "krabbe", + "solcreme", + "fyrtårn", + "bølge", + "måge", + "badehåndklæde", + "vandmand", + "luftmadras", + "solbriller", + "spand og skovl", + "muslingeskal", + "isbod", + "badebukser", + "redningsvest" + ] + }, + { + "id": "job", + "name": "Job", + "icon": "👩‍🚒", + "words": [ + "brandmand", + "læge", + "politibetjent", + "kok", + "pilot", + "frisør", + "tandlæge", + "landmand", + "maler", + "astronaut", + "postbud", + "skuespiller", + "lærer", + "dyrlæge", + "tømrer", + "dommer", + "klovn" + ] + }, + { + "id": "skole", + "name": "I skolen", + "icon": "🏫", + "words": [ + "tavle", + "lommeregner", + "skoletaske", + "lineal", + "blyant", + "frikvarter", + "madpakke", + "globus", + "lektier", + "viskelæder", + "gymnastiksal", + "skolebus", + "mikroskop", + "saks", + "atlas", + "klokke", + "penalhus" + ], + "spion_skip": [ + "frikvarter", + "lektier" + ] + }, + { + "id": "eventyr", + "name": "Eventyr", + "icon": "🏰", + "words": [ + "drage", + "prinsesse", + "slot", + "trold", + "heks", + "enhjørning", + "havfrue", + "trylledrik", + "ridder", + "frø", + "glasko", + "den grimme ælling", + "tinsoldat", + "kejserens nye klæder", + "fe", + "skattekiste", + "tryllestav", + "ulv" + ] + }, + { + "id": "transport", + "name": "Transport", + "icon": "🚂", + "words": [ + "tog", + "cykel", + "helikopter", + "ubåd", + "traktor", + "rumraket", + "sejlbåd", + "brandbil", + "løbehjul", + "luftballon", + "skraldebil", + "færge", + "motorcykel", + "flyvemaskine", + "rulleskøjter", + "taxa", + "kran" + ] + }, + { + "id": "musik", + "name": "Musik", + "icon": "🎸", + "words": [ + "guitar", + "trommer", + "klaver", + "trompet", + "violin", + "mikrofon", + "harmonika", + "dirigent", + "kor", + "koncert", + "høretelefoner", + "saxofon", + "blokfløjte", + "rockstjerne", + "luftguitar", + "kontrabas", + "tamburin" + ] + }, + { + "id": "danmark", + "name": "Danmark", + "icon": "🇩🇰", + "words": [ + "den lille havfrue", + "dannebrog", + "vikingeskib", + "lego", + "rundetårn", + "tivoli", + "cykelsti", + "pølsevogn", + "kronborg", + "dronning", + "hygge", + "klitter", + "vindmølle", + "kongekrone", + "fastelavn", + "sankthans", + "kolonihave" + ], + "spion_skip": [ + "hygge" + ] + }, + { + "id": "rummet", + "name": "Rummet", + "icon": "🚀", + "words": [ + "astronaut", + "planet", + "måne", + "rumvæsen", + "stjerneskud", + "satellit", + "sort hul", + "saturn", + "teleskop", + "rumstation", + "ufo", + "komet", + "månelanding", + "solsystem", + "mars", + "rumdragt" + ] + } +] diff --git a/party/tegn.py b/party/tegn.py index 91bfb29..581c454 100644 --- a/party/tegn.py +++ b/party/tegn.py @@ -16,7 +16,7 @@ from pathlib import Path from fupogfakta.engine import GameError from tegnoggaet.engine import EMPTY_GUESS, MAX_PLAYERS, MIN_PLAYERS, RULES, RULES_STEP_MS, Game -from tegnoggaet.themes import load_themes +from party.themes import load_themes from . import rooms, tts from .narrator import Narrator, generic_texts, one_of, untag diff --git a/party/tests/test_themes.py b/party/tests/test_themes.py new file mode 100644 index 0000000..fba3376 --- /dev/null +++ b/party/tests/test_themes.py @@ -0,0 +1,27 @@ +"""Den fælles temabank (party/data/themes.json): Tegn og Gæt som før, og nok gode ord til Spionen.""" + +from django.test import SimpleTestCase + +from party.themes import SPY_MIN_WORDS, load_themes, spy_themes, spy_words + + +class ThemeBankTests(SimpleTestCase): + def test_tegn_og_gaet_gets_the_same_themes(self): + themes = load_themes() + self.assertEqual(len(themes), 14) + self.assertEqual(len({t["id"] for t in themes}), 14) + for t in themes: + self.assertTrue(t["name"] and t["icon"] and len(t["words"]) >= 16, t["id"]) + + def test_spionen_has_enough_concrete_words(self): + themes = spy_themes() + self.assertGreaterEqual(len(themes), 10) + for t in themes: + self.assertGreaterEqual(len(t["words"]), SPY_MIN_WORDS, t["id"]) + self.assertTrue(all(" " not in w for w in t["words"]), t["id"]) + self.assertNotIn("film", {t["id"] for t in themes}) # titler er dårlige at give stikord til + + def test_skip_list(self): + self.assertNotIn("si", spy_words({"words": ["si", "ovn"], "spion_skip": ["si"]})) + self.assertEqual(spy_words({"words": ["sort hul", "måne"]}), ["måne"]) + self.assertEqual(spy_words({"words": ["x"], "spion": False}), []) diff --git a/party/tests/test_tts_fallback.py b/party/tests/test_tts_fallback.py index 295bf26..9a26301 100644 --- a/party/tests/test_tts_fallback.py +++ b/party/tests/test_tts_fallback.py @@ -17,7 +17,7 @@ from party.narrator import Narrator, instead_of, one_of from party.rooms import Room from party.tegn import TegnRoom from tegnoggaet.engine import Game as TegnGame -from tegnoggaet.themes import load_themes +from party.themes import load_themes QUESTIONS = [{"id": i, "category_id": i % 3, "category": f"Kat {i % 3}", "prompt": f"Hvad _____ {i}?", "answer": f"svar{i}", "decoys": ["x", "y", "z"]} for i in range(9)] diff --git a/party/tests/test_tts_warm.py b/party/tests/test_tts_warm.py index 304eab8..a2cd86c 100644 --- a/party/tests/test_tts_warm.py +++ b/party/tests/test_tts_warm.py @@ -17,7 +17,7 @@ from tegnoggaet.engine import RULES as TEGN_RULES from party.narrator import LINES as FOF_LINES, TEASES, TEASES_FEST, Narrator from party.tegn import COUNT_LINES, FIXED_LINES, LINES, THEME_LINES, TegnRoom, warm_texts from tegnoggaet.engine import Game -from tegnoggaet.themes import load_themes +from party.themes import load_themes STROKE = {"color": "#1d1b2e", "width": 0.01, "points": [[0.1, 0.1], [0.5, 0.5]]} NAMES = ("Qxa", "Zyb", "Wvc") # spillernavne og -ord, der ikke findes i replikkerne diff --git a/party/themes.py b/party/themes.py new file mode 100644 index 0000000..c303e45 --- /dev/null +++ b/party/themes.py @@ -0,0 +1,33 @@ +"""Temabanken, fælles for spillene: party/data/themes.json. + +Tegn og Gæt bruger alle ordene (eksempel-ordene til "Giv mig et forslag"). Spionen skal bruge ord, det er nemt at give +stikord til: konkrete navneord i ét ord. Et tema med "spion": false er ikke med i Spionen, og "spion_skip" udelader +enkelte ord. +""" + +import json +from pathlib import Path + +THEMES_FILE = Path(__file__).resolve().parent / "data" / "themes.json" +SPY_MIN_WORDS = 15 # et tema skal have mindst så mange gode ord for at være med i Spionen + + +def load_themes() -> list[dict]: + return json.loads(THEMES_FILE.read_text(encoding="utf-8")) + + +def spy_words(theme: dict) -> list[str]: + if theme.get("spion") is False: + return [] + skip = set(theme.get("spion_skip", [])) + return [w for w in theme.get("words", []) if " " not in w and w not in skip] + + +def spy_themes() -> list[dict]: + """Temaerne til Spionen, med words = de gode stikordsord.""" + out = [] + for t in load_themes(): + words = spy_words(t) + if len(words) >= SPY_MIN_WORDS: + out.append({"id": t["id"], "name": t["name"], "icon": t.get("icon", ""), "words": words}) + return out diff --git a/tegnoggaet/data/themes.json b/tegnoggaet/data/themes.json deleted file mode 100644 index ed36017..0000000 --- a/tegnoggaet/data/themes.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - {"id": "dyr", "name": "Dyr", "icon": "🐘", "words": [ - "kat", "hund", "elefant", "giraf", "pingvin", "krokodille", "blæksprutte", "flodhest", "pindsvin", "edderkop", - "sommerfugl", "kænguru", "hval", "ugle", "snegl", "zebra", "papegøje", "isbjørn"]}, - {"id": "film", "name": "Film og serier", "icon": "🎬", "words": [ - "løvernes konge", "frost", "star wars", "harry potter", "titanic", "jurassic park", "find nemo", "shrek", - "toy story", "ringenes herre", "olsen banden", "spiderman", "batman", "pippi langstrømpe", "minions", - "mary poppins", "ghostbusters"]}, - {"id": "koekken", "name": "Ting i køkkenet", "icon": "🍳", "words": [ - "gaffel", "pande", "køleskab", "brødrister", "piskeris", "kaffemaskine", "grydeske", "ovn", "mikroovn", - "kagerulle", "si", "osteskærer", "opvaskemaskine", "tekande", "skærebræt", "kasserolle", "køkkenrulle"]}, - {"id": "jul", "name": "Jul", "icon": "🎄", "words": [ - "juletræ", "julemand", "nisse", "rensdyr", "snemand", "gave", "risalamande", "pebernødder", "stjerne", - "kravlenisse", "slæde", "julehjerte", "lys", "and", "juletræsfod", "mandelgave", "kalenderlys", "sne"]}, - {"id": "sport", "name": "Sport", "icon": "⚽", "words": [ - "fodbold", "håndbold", "cykelløb", "svømning", "tennis", "golf", "skihop", "boksning", "badminton", - "basketball", "bueskydning", "surfing", "ishockey", "kunstskøjteløb", "maraton", "vægtløftning", "ridning"]}, - {"id": "mad", "name": "Mad", "icon": "🍕", "words": [ - "pizza", "pandekager", "spaghetti", "hotdog", "frikadeller", "sushi", "burger", "is", "banan", "vandmelon", - "æbleskiver", "smørrebrød", "popcorn", "rugbrød", "flæskesteg", "kanelsnegl", "tacos", "lagkage"]}, - {"id": "strand", "name": "På stranden", "icon": "🏖️", "words": [ - "sandslot", "parasol", "badebold", "krabbe", "solcreme", "fyrtårn", "bølge", "måge", "badehåndklæde", - "vandmand", "luftmadras", "solbriller", "spand og skovl", "muslingeskal", "isbod", "badebukser", "redningsvest"]}, - {"id": "job", "name": "Job", "icon": "👩‍🚒", "words": [ - "brandmand", "læge", "politibetjent", "kok", "pilot", "frisør", "tandlæge", "landmand", "maler", "astronaut", - "postbud", "skuespiller", "lærer", "dyrlæge", "tømrer", "dommer", "klovn"]}, - {"id": "skole", "name": "I skolen", "icon": "🏫", "words": [ - "tavle", "lommeregner", "skoletaske", "lineal", "blyant", "frikvarter", "madpakke", "globus", "lektier", - "viskelæder", "gymnastiksal", "skolebus", "mikroskop", "saks", "atlas", "klokke", "penalhus"]}, - {"id": "eventyr", "name": "Eventyr", "icon": "🏰", "words": [ - "drage", "prinsesse", "slot", "trold", "heks", "enhjørning", "havfrue", "trylledrik", "ridder", "frø", - "glasko", "den grimme ælling", "tinsoldat", "kejserens nye klæder", "fe", "skattekiste", "tryllestav", "ulv"]}, - {"id": "transport", "name": "Transport", "icon": "🚂", "words": [ - "tog", "cykel", "helikopter", "ubåd", "traktor", "rumraket", "sejlbåd", "brandbil", "løbehjul", "luftballon", - "skraldebil", "færge", "motorcykel", "flyvemaskine", "rulleskøjter", "taxa", "kran"]}, - {"id": "musik", "name": "Musik", "icon": "🎸", "words": [ - "guitar", "trommer", "klaver", "trompet", "violin", "mikrofon", "harmonika", "dirigent", "kor", "koncert", - "høretelefoner", "saxofon", "blokfløjte", "rockstjerne", "luftguitar", "kontrabas", "tamburin"]}, - {"id": "danmark", "name": "Danmark", "icon": "🇩🇰", "words": [ - "den lille havfrue", "dannebrog", "vikingeskib", "lego", "rundetårn", "tivoli", "cykelsti", "pølsevogn", - "kronborg", "dronning", "hygge", "klitter", "vindmølle", "kongekrone", "fastelavn", "sankthans", "kolonihave"]}, - {"id": "rummet", "name": "Rummet", "icon": "🚀", "words": [ - "astronaut", "planet", "måne", "rumvæsen", "stjerneskud", "satellit", "sort hul", "saturn", "teleskop", - "rumstation", "ufo", "komet", "månelanding", "solsystem", "mars", "rumdragt"]} -] diff --git a/tegnoggaet/tests/test_engine.py b/tegnoggaet/tests/test_engine.py index 7fce1ec..ba73b98 100644 --- a/tegnoggaet/tests/test_engine.py +++ b/tegnoggaet/tests/test_engine.py @@ -5,7 +5,7 @@ from django.test import SimpleTestCase from fupogfakta.engine import GameError from tegnoggaet import engine from tegnoggaet.engine import Game, clean_drawing -from tegnoggaet.themes import load_themes +from party.themes import load_themes THEMES = [{"id": "dyr", "name": "Dyr", "words": ["kat", "hund", "ko", "gris"]}, {"id": "mad", "name": "Mad", "words": ["pizza", "is"]}] diff --git a/tegnoggaet/themes.py b/tegnoggaet/themes.py deleted file mode 100644 index 02a7a38..0000000 --- a/tegnoggaet/themes.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Temaerne til Tegn og Gæt ligger i data/themes.json (eksempel-ordene bruges til "Giv mig et forslag").""" - -import json -from pathlib import Path - -THEMES_FILE = Path(__file__).resolve().parent / "data" / "themes.json" - - -def load_themes() -> list[dict]: - return json.loads(THEMES_FILE.read_text(encoding="utf-8"))