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>
115 lines
6.2 KiB
Python
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
|