Files
weirsoe-party-protocol/party/tests/test_generic_lines.py
T
agwandClaude Opus 5.5 ecf94bcbf0 Speaker: generiske replikker uden navne, til forvarmning
Replikker med navne og spillertekst kan ikke laves på forhånd, så uden
live-stemme var de øjeblikke tavse. Nu har hver af dem en generisk udgave,
der giver mening uden navnet (navnet står på skærmen):

- fælles (party/generic_lines.json): 22 velkomster til en ny spiller og
  tre pr. antal spillere fra to til ti (tallet som ord, aldrig først), med
  fest-udgaver til Fup og Fakta's fest-pakke.
- Fup og Fakta (fupogfakta/narrator/generic.json): VIP venter, runde to,
  finalen, stillingen, kategori, narret, husets løgn, sandheden fundet,
  af ingen og af alle, publikums favorit, vinder, priserne og hvem der er
  færdig; fem eller flere hver, og tre i fest-tonen.
- Tegn og Gæt (tegnoggaet/narrator/generic.json): VIP venter, tema, første
  og næste kæde, overlevede, forvandlet, priserne og vinderen.

Narrator.line() giver nu en Line, der kender sin generiske afløser
(generic(accept) vælger kun blandt dem, accept godtager, fx dem i cachen).
Filerne er nøglet på begivenheden, så et nyt spil kun skal skrive sin egen
generic.json. Test: antal, ingen tankestreger, intet tal først i en sætning,
ingen dubletter, kendte lyd-tags, og at alle navnebegivenheder i begge spil
har en generisk udgave (i fest-tonen, hvor der er en fest-replik).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:59:00 +02:00

115 lines
6.2 KiB
Python

"""De generiske replikker (uden navne og spillertekst): antal, sprog og at alle navnebegivenheder har dem."""
import random
import re
from django.test import SimpleTestCase
from fupogfakta import engine as fof_engine
from party import narrator as fof
from party.narrator import Narrator
from party import tegn
from tegnoggaet import engine as tegn_engine
NUMBERS = ("to tre fire fem seks syv otte ni ti elleve tolv tretten fjorten femten seksten sytten atten nitten "
"tyve tredive fyrre halvtreds hundrede tusind").split()
# Pladsholdere med et endeligt antal værdier: de replikker forvarmes, som de er (temaer, kategorier, antal)
FINITE = {"tema", "kategori", "antal_spillere"}
# Begivenheder, der ikke har en god generisk udgave: tavse, når de ikke er forvarmet (svaret ER replikken)
SILENT = {"present_answer"}
FEST_ONLY = {"award_thirstiest"} # tår findes kun i fest-spil
GAMES = {"fof": (fof.LINES, {**fof.GENERIC_COMMON, **fof.GENERIC}, fof_engine.MIN_PLAYERS, fof_engine.MAX_PLAYERS),
"tegn": (tegn.LINES, {**fof.GENERIC_COMMON, **tegn.GENERIC}, tegn_engine.MIN_PLAYERS, tegn_engine.MAX_PLAYERS)}
def all_generic() -> list[tuple[str, str]]:
files = [fof.GENERIC_COMMON, fof.GENERIC, tegn.GENERIC]
return [(k, line) for f in files for k, v in f.items() if isinstance(v, list) for line in v]
def sentences(line: str) -> list[str]:
text = re.sub(r"\[[^\]]*\]\s*", "", line)
return [s for s in re.split(r"(?<=[.!?])\s+", text) if s]
class GenericLinesTests(SimpleTestCase):
def test_language(self):
lines = all_generic()
self.assertGreater(len(lines), 250)
for key, line in lines:
with self.subTest(key=key, line=line):
self.assertNotRegex(line, r"[—–]| - ") # ingen tankestreger
self.assertNotRegex(line, r"[{}<>\d]") # ingen pladsholdere eller cifre
for s in sentences(line):
self.assertNotIn(s.split()[0].strip(",!?.").lower(), NUMBERS, "tal først i en sætning")
for tag in re.findall(r"\[([^\]]*)\]", line):
self.assertIn(tag, fof.AUDIO_TAGS)
self.assertRegex(line, r"[.!?]$")
def test_no_duplicates(self):
texts = [line for _, line in all_generic()]
dupes = sorted({t for t in texts if texts.count(t) > 1})
self.assertEqual(dupes, [])
specific = {v for lines in (fof.LINES, tegn.LINES) for vs in lines.values() for v in vs}
self.assertEqual(sorted(set(texts) & specific), [], "en generisk replik er en kopi af en almindelig")
def test_counts(self):
common = fof.GENERIC_COMMON
self.assertTrue(20 <= len(common["lobby_join"]) <= 25)
self.assertGreaterEqual(len(common["lobby_join_fest"]), 5)
for n in range(fof_engine.MIN_PLAYERS, tegn_engine.MAX_PLAYERS + 1):
self.assertIn(len(common[f"game_start_{n}"]), (3, 4), n)
for n in range(fof_engine.MIN_PLAYERS, fof_engine.MAX_PLAYERS + 1): # fest findes kun i Fup og Fakta
self.assertGreaterEqual(len(common[f"game_start_{n}_fest"]), 2, n)
for name, (_, generic, _, _) in GAMES.items():
for key, v in generic.items():
if isinstance(v, str) or key.startswith(("lobby_join", "game_start_")):
continue
with self.subTest(game=name, key=key):
self.assertGreaterEqual(len(v), 3 if key.endswith("_fest") else 5)
def test_every_name_event_has_a_generic(self):
"""Alle replikker med navne eller spillertekst har en generisk udgave, i fest-tonen hvor der er en fest-replik."""
for name, (lines, generic, lo, hi) in GAMES.items():
n = Narrator(lines=lines, generic=generic, rng=random.Random(1))
nf = Narrator(lines=lines, generic=generic, fest=True, rng=random.Random(1))
for key, variants in lines.items():
base = key.removesuffix("_fest")
holders = {p for v in variants for p in re.findall(r"\{(\w+)\}", v)}
if not holders - FINITE and base != "game_start" or base in SILENT:
continue
with self.subTest(game=name, event=key):
if base == "game_start":
counts = range(lo, (fof_engine.MAX_PLAYERS if key.endswith("_fest") else hi) + 1)
for c in counts:
self.assertTrue((nf if key.endswith("_fest") else n).generic(base, antal_spillere=c))
continue
self.assertTrue((nf if base in FEST_ONLY else n).generic(base), "mangler generisk replik")
if key.endswith("_fest"):
self.assertTrue(nf._generic_key(base, {}).endswith("_fest"), "mangler fest-udgave")
def test_aliases_and_keys_are_real_events(self):
for name, (lines, generic, _, _) in GAMES.items():
for key, v in generic.items():
with self.subTest(game=name, key=key):
base = re.sub(r"_\d+$", "", key.removesuffix("_fest"))
self.assertTrue(base in lines or base in ("chain_intro_first",), base)
if isinstance(v, str):
self.assertIsInstance(generic.get(v), list)
def test_generic_follows_the_line(self):
n = Narrator(rng=random.Random(3))
line = n.line("lobby_join", navn="Bo")
self.assertIn("Bo", line)
self.assertIn(line.generic(), fof.GENERIC_COMMON["lobby_join"])
only = fof.GENERIC_COMMON["lobby_join"][4]
self.assertEqual(line.generic(lambda t: t == only), only) # kun det, accept godtager
self.assertIsNone(line.generic(lambda t: False))
self.assertIn(n.generic("game_start", antal_spillere=5), fof.GENERIC_COMMON["game_start_5"])
self.assertIn(n.generic("reveal_lie_shared"), fof.GENERIC["reveal_lie_fooled"]) # henvisning
f = Narrator(fest=True, rng=random.Random(3))
self.assertIn(f.generic("lie_progress_fast"), fof.GENERIC["lie_progress_fest"])
self.assertIn(f.generic("award_thirstiest"), fof.GENERIC["award_thirstiest_fest"])
self.assertIsNone(n.generic("lie_hurry")) # ingen navne: ingen generisk