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>
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
{
|
||||
"lobby_waiting": [
|
||||
"Vi er klar. VIP'en må gerne trykke start nu.",
|
||||
"Alle sidder klar. Den med VIP-kronen bestemmer, hvornår vi går i gang.",
|
||||
"Så er vi nok. VIP, din telefon har en meget vigtig knap.",
|
||||
"[sighs] Jeg venter stadig på startknappen. Bare så I ved det.",
|
||||
"Holdet er samlet. Nu mangler vi bare, at VIP'en tager mod til sig."
|
||||
],
|
||||
"lobby_waiting_fest": [
|
||||
"Vi er klar, VIP. Tryk start, bestie.",
|
||||
"Alle venter på VIP'en. Lowkey stressende.",
|
||||
"VIP, startknappen. Glassene bliver ikke koldere."
|
||||
],
|
||||
"round2_intro": [
|
||||
"Runde to. Nu giver alt dobbelt point, så føringen er alt andet end sikker.",
|
||||
"Velkommen til runde to. Pointene fordobles, og det gør jeres mistænksomhed forhåbentlig også.",
|
||||
"[excited] Runde to! Dobbelt point. Nu kan det hele vende.",
|
||||
"Så skruer vi op. Runde to giver dobbelt point.",
|
||||
"Runde to begynder. Dem, der ligger bagerst, har stadig alle chancer."
|
||||
],
|
||||
"round2_intro_fest": [
|
||||
"Runde to, bestie. Narret nu koster to tår.",
|
||||
"[excited] Runde to! Nu bliver nogen cooked.",
|
||||
"Dobbelt point og dobbelt tår. No cap, alt kan ske."
|
||||
],
|
||||
"final_intro": [
|
||||
"Så er det finalen. Her giver alt tredobbelt point, så intet er afgjort.",
|
||||
"[dramatic] Finalen. Et enkelt spørgsmål, tredobbelt point. Alt kan vende nu.",
|
||||
"Velkommen til finalen. Den, der fører, skal passe godt på nu.",
|
||||
"Finalen er her. Dem bagerst får deres sidste og bedste chance.",
|
||||
"Sidste spørgsmål, tredobbelt point. Tag en dyb indånding."
|
||||
],
|
||||
"final_intro_fest": [
|
||||
"[dramatic] Finalen, bestie. Bliver du narret nu, er det en hel shot.",
|
||||
"Det er shot-runden. No cap, nu bliver det seriøst.",
|
||||
"Finalen er her. Tredobbelt point og en shot til dem, der hopper i."
|
||||
],
|
||||
"scores_intro": [
|
||||
"Her er stillingen. Nogen smiler, nogen tæller på fingrene.",
|
||||
"Pointtavlen er klar. Kig selv. Jeg dømmer ikke. Højt.",
|
||||
"Sådan står det. Føringen er spinkel, og bunden er hyggelig.",
|
||||
"Lad os se stillingen. Tallene lyver ikke. Det er jeres opgave.",
|
||||
"[chuckles] Stillingen er på skærmen. Nogle af jer bør se væk.",
|
||||
"Her er pointene. Der er stadig tid til at vende det hele."
|
||||
],
|
||||
"scores_intro_fest": [
|
||||
"Stillingen er her. Men hvem fører på tår? Det er det, der tæller.",
|
||||
"Pointtavlen, bestie. Nogen er lowkey cooked.",
|
||||
"Her står det. Den, der ligger sidst, har nok drukket mest."
|
||||
],
|
||||
"category_choose": [
|
||||
"Så skal der vælges kategori. Telefonen ved, hvis tur det er.",
|
||||
"Tid til at vælge en kategori. Gerne noget, I kan lyve selvsikkert om.",
|
||||
"En af jer vælger nu kategorien. Resten må gerne brokke sig bagefter.",
|
||||
"Kategorien vælges på telefonen. Mavefornemmelsen er fuldt kvalificeret.",
|
||||
"Vælg en kategori. Der er ingen forkerte valg. Kun pinlige.",
|
||||
"[curious] Hvad skal vi høre om? Det afgøres på en telefon lige nu."
|
||||
],
|
||||
"category_choose_fest": [
|
||||
"Vælg en kategori, bestie. No pressure.",
|
||||
"Kategori, tak. Tænk ikke for længe, glasset venter.",
|
||||
"Tid til at vælge. It's giving beslutningsangst."
|
||||
],
|
||||
"category_timeout": [
|
||||
"Tiden løb ud, så jeg valgte selv. Jeg har en god smag.",
|
||||
"For langsomt. Jeg har valgt kategorien for jer.",
|
||||
"[sighs] Ingen valgte, så jeg tog en. Den står på skærmen.",
|
||||
"Uret vandt. Kategorien er valgt, og vi kører videre.",
|
||||
"Ingen beslutning? Så beslutter jeg. Det er det, jeg er her for."
|
||||
],
|
||||
"category_timeout_fest": [
|
||||
"For langsomt, bestie. Jeg valgte.",
|
||||
"Tiden er gået. Jeg tog en kategori, no cap.",
|
||||
"[sighs] Beslutningsangst? Jeg har valgt for dig."
|
||||
],
|
||||
"lie_start": [
|
||||
"Kategorien er valgt. Her kommer spørgsmålet.",
|
||||
"Så er vi klar. Lyt godt efter, nu kommer spørgsmålet.",
|
||||
"Godt valg. Lad os se, hvad I kan finde på.",
|
||||
"Kategorien står på skærmen. Find jeres bedste løgn frem.",
|
||||
"[mischievously] Et glimrende valg. Eller også ikke. Det finder vi ud af."
|
||||
],
|
||||
"lie_start_fest": [
|
||||
"Kategorien er valgt. Let's go, bestie.",
|
||||
"Godt valg. Lowkey spændende.",
|
||||
"Her kommer spørgsmålet. Lock in."
|
||||
],
|
||||
"reveal_lie_fooled": [
|
||||
"Den løgn virkede. Nogen hoppede lige i.",
|
||||
"[chuckles] Narret! Skærmen viser, hvem der blev snydt.",
|
||||
"Det var en løgn, og den gik rent ind.",
|
||||
"Snydt! Forfatteren må gerne smile lidt nu.",
|
||||
"Den var opdigtet. Og den blev købt. Kvitteringen er væk.",
|
||||
"[laughs] Lige i fælden. Pænt skrevet.",
|
||||
"Det var ikke sandheden. Men det lød sådan, og det er hele kunsten.",
|
||||
"Løgn! Og nogen troede på den. Sådan skal det gøres."
|
||||
],
|
||||
"reveal_lie_fooled_fest": [
|
||||
"Løgn! Og nogen hoppede i. Tag en tår, bestie.",
|
||||
"[laughs] Narret! Glasset op, det er reglerne.",
|
||||
"Den var fake, og den blev købt. Drik!",
|
||||
"Caught red-handed. Skål for løgneren.",
|
||||
"Snydt! Lowkey pinligt. Drik."
|
||||
],
|
||||
"reveal_lie_fooled_many": "reveal_lie_fooled",
|
||||
"reveal_lie_shared": "reveal_lie_fooled",
|
||||
"reveal_house_lie": [
|
||||
"Det var husets løgn. Den skrev jeg selv. Jeg er meget stolt.",
|
||||
"[mischievously] Husets løgn! Jeg har stadig evnerne.",
|
||||
"Den løgn kom fra huset. Altså fra mig.",
|
||||
"Ha! Det var min løgn. Tak for tilliden.",
|
||||
"Husets løgn snød nogen. Det varmer et gammelt speakerhjerte.",
|
||||
"Den var min. Jeg håber, I tilgiver mig. Måske."
|
||||
],
|
||||
"reveal_house_lie_fest": [
|
||||
"Husets løgn, bestie. Jeg fik dig. Drik!",
|
||||
"[laughs] Den var min. Tag en tår.",
|
||||
"Huset vinder. Skål."
|
||||
],
|
||||
"reveal_truth_found": [
|
||||
"Det er sandheden! Godt fundet.",
|
||||
"[impressed] Sandheden. Nogen af jer kan altså tænke.",
|
||||
"Det var den rigtige. Point til dem, der fandt den.",
|
||||
"Sandheden er fundet. Det er rart med lidt orden i tingene.",
|
||||
"Rigtigt! Faktum står fast, og pointene er givet.",
|
||||
"Den var sand. Og I fandt den. Jeg er næsten rørt."
|
||||
],
|
||||
"reveal_truth_found_fest": [
|
||||
"Sandheden! Big brain, bestie.",
|
||||
"Rigtigt svar. W til dem, der fandt den.",
|
||||
"[impressed] Det var sandheden. Slay."
|
||||
],
|
||||
"reveal_truth_nobody": [
|
||||
"Det var sandheden, og ingen fandt den. Løgnene var for gode.",
|
||||
"[sighs] Sandheden stod lige der, og ingen ville have den.",
|
||||
"Det var faktisk sandt. Ingen troede på det. Det siger lidt om sandheden.",
|
||||
"Ingen fandt sandheden. Den sidder nu alene i et hjørne.",
|
||||
"Sandt, men ingen gættede det. Virkeligheden er underligere end jeres løgne."
|
||||
],
|
||||
"reveal_truth_nobody_fest": [
|
||||
"Sandheden, og ingen fandt den. Så drikker alle. Det er reglerne.",
|
||||
"[sighs] Ingen fandt den. Alle drikker, bestie.",
|
||||
"Det var sandt. Hele squad'en er cooked. Skål."
|
||||
],
|
||||
"reveal_truth_everyone": [
|
||||
"Alle fandt sandheden! Løgnerne må prøve igen.",
|
||||
"[impressed] Hele holdet fandt den. Der var ikke meget at hente for løgnerne.",
|
||||
"Alle som én. Sandheden var åbenbart for nem.",
|
||||
"Fuldt hus på sandheden. Jeg er imponeret og lidt skuffet.",
|
||||
"Alle fandt den. Det var vist ikke et godt spørgsmål for løgnere."
|
||||
],
|
||||
"reveal_truth_everyone_fest": [
|
||||
"Alle fandt sandheden. Big brain hele squad'en.",
|
||||
"Alle som én! Ingen tår der. Kedeligt, bestie.",
|
||||
"[impressed] Fuldt hus. Løgnerne er cooked."
|
||||
],
|
||||
"summary_favorite": [
|
||||
"Publikums favorit står på skærmen. Hjerterne har talt.",
|
||||
"Flest hjerter gik til et svar, der fortjente det.",
|
||||
"[chuckles] Hjerterne er delt ud. Se lige, hvem der fik flest.",
|
||||
"Det svar fik flest hjerter. Kunst, siger jeg bare.",
|
||||
"Hjerterne er talt. Favoritten står på skærmen."
|
||||
],
|
||||
"summary_favorite_fest": [
|
||||
"Flest hjerter, bestie. Det svar slayede.",
|
||||
"Favoritten er fundet. It's giving ikonisk.",
|
||||
"Hjerterne har talt. Lowkey fortjent."
|
||||
],
|
||||
"winner": [
|
||||
"Vi har en vinder! Navnet står på skærmen, og æren er fortjent.",
|
||||
"[cheering] Tillykke til vinderen! Det skal sofaen nok høre om igen.",
|
||||
"Sejren er afgjort. Takketalen må gerne indeholde noget sandt.",
|
||||
"Og vinderen er fundet. Pointene taler for sig selv.",
|
||||
"Tillykke! Vinderen må nu se ned på os andre. Med måde.",
|
||||
"Spillet er slut, og vinderen står øverst. Pokalen er usynlig, men ægte."
|
||||
],
|
||||
"winner_fest": [
|
||||
"Vi har en vinder! Men den rigtige vinder er den, der drak mest.",
|
||||
"[cheering] Tillykke, bestie. Skål for sejren.",
|
||||
"Vinderen er fundet. Main character energy."
|
||||
],
|
||||
"award_biggest_liar": [
|
||||
"Prisen for største løgner. Tjek lige jeres lommer.",
|
||||
"Største løgner! Den her person narrede jer igen og igen.",
|
||||
"[impressed] Aftenens største løgner. Fantasien har fået faste kunder.",
|
||||
"Prisen for flest narrede går til en, I aldrig skal stole på igen.",
|
||||
"Største løgner. Prisen overrækkes med et mistænksomt håndtryk."
|
||||
],
|
||||
"award_biggest_liar_fest": [
|
||||
"Største løgner, bestie. Red flag, men respekt.",
|
||||
"[laughs] Prisen for flest narrede. It's giving manipulation.",
|
||||
"Største løgner. No cap, det var imponerende."
|
||||
],
|
||||
"award_truth_seeker": [
|
||||
"Sandhedsvidnet! Den her person fandt sandheden igen og igen.",
|
||||
"Prisen for at finde sandheden. Nogen skal jo holde styr på virkeligheden.",
|
||||
"[impressed] Aftenens sandhedsvidne. Ingen løgn slap forbi.",
|
||||
"Prisen for flest rigtige går til en skarp hjerne.",
|
||||
"Sandhedsvidnet. Det er næsten mistænkeligt, hvor godt det gik."
|
||||
],
|
||||
"award_truth_seeker_fest": [
|
||||
"Sandhedsvidnet, bestie. Big brain.",
|
||||
"Flest rigtige. Lowkey et geni.",
|
||||
"[impressed] Ingen løgn slap forbi. Slay."
|
||||
],
|
||||
"award_most_liked": [
|
||||
"Folkets favorit! Flest hjerter i aften.",
|
||||
"Prisen for flest hjerter. Publikum har talt.",
|
||||
"[cheering] Folkets favorit. Hjerterne regnede ned.",
|
||||
"Flest hjerter gik til den her person. Charme virker åbenbart.",
|
||||
"Folkets favorit. Det er ikke point, men det er kærlighed."
|
||||
],
|
||||
"award_most_liked_fest": [
|
||||
"Folkets favorit, bestie. Mest liked. Slay.",
|
||||
"Flest hjerter. Main character energy.",
|
||||
"[cheering] Folkets favorit. It's giving ikon."
|
||||
],
|
||||
"award_thirstiest_fest": [
|
||||
"Prisen for flest tår. Vand nu, bestie.",
|
||||
"[laughs] Aftenens tørstigste. Leveren sender en hilsen.",
|
||||
"Flest tår i aften. No cap, respekt og bekymring."
|
||||
],
|
||||
"lie_progress": [
|
||||
"En løgn er afleveret. Den ligger nu i bunken.",
|
||||
"Der er en, der er færdig. Resten, kom så.",
|
||||
"[mischievously] Nogen har skrevet noget. Jeg siger ikke hvad.",
|
||||
"Der kom en løgn. Jeg tager den ikke personligt.",
|
||||
"En er klar. Der er stadig tid til jer andre.",
|
||||
"Endnu en løgn er landet. Den er stadig lun."
|
||||
],
|
||||
"lie_progress_fest": [
|
||||
"En løgn er inde. Lowkey lumsk.",
|
||||
"Nogen er færdig. Aura farmet.",
|
||||
"[whispers] Der kom en løgn. It's giving."
|
||||
],
|
||||
"lie_progress_fast": "lie_progress",
|
||||
"lie_progress_retry": "lie_progress"
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"lobby_join": [
|
||||
"Så kom der en til. Velkommen, sæt dig bare.",
|
||||
"En ny spiller! Sofaen rykker lidt sammen.",
|
||||
"Velkommen til. Telefonen virker, kan jeg se. Det er en god start.",
|
||||
"Der var en mere. Jo flere, jo sjovere.",
|
||||
"[excited] Endnu en er landet!",
|
||||
"Velkommen. Find en plads og se uskyldig ud.",
|
||||
"Hej med dig. Du kom lige til tiden. Det gør næsten ingen.",
|
||||
"Så er vi en mere. Det var nu hyggeligt.",
|
||||
"Velkommen ombord. Redningsvesten ligger under sofapuden.",
|
||||
"Der kom en til. Jeg kan allerede mærke stemningen stige.",
|
||||
"[chuckles] Nå, der var du. Vi har ventet på dig. Lidt.",
|
||||
"Velkommen. Navnet står på skærmen, så jeg slipper for at udtale det.",
|
||||
"En mere i flokken. Dejligt.",
|
||||
"Så blev vi flere. Klogere bliver vi nok ikke.",
|
||||
"Velkommen til. Husk, alt hvad du gør, kan bruges imod dig.",
|
||||
"Godt at se dig. Eller, godt at se dit navn.",
|
||||
"Der er en ny ved bordet. Opfør jer pænt. I hvert fald i starten.",
|
||||
"[mischievously] Velkommen. Jeg har store forventninger. De plejer ikke at holde.",
|
||||
"En til! Nu begynder det at ligne en fest.",
|
||||
"Velkommen. Hold godt fast i telefonen, det bliver vildt.",
|
||||
"Så kom der forstærkning.",
|
||||
"Hej og velkommen. Du ser ud til at kunne tåle lidt drilleri."
|
||||
],
|
||||
"lobby_join_fest": [
|
||||
"Der kom en til. Main character energy, helt sikkert.",
|
||||
"[gasps] Ny spiller! Glasset er klar, ikke?",
|
||||
"Velkommen, bestie. Det her ender med en tår.",
|
||||
"En mere til festen. Squad'en vokser. Slay.",
|
||||
"Så er vi flere. Lowkey det bedste, der er sket i aften.",
|
||||
"Velkommen. No cap, du kommer til at drikke.",
|
||||
"[excited] Endnu en er joinet! It's giving fest.",
|
||||
"Der var en mere. Vibe check bestået."
|
||||
],
|
||||
"game_start_2": [
|
||||
"Vi er to. Det bliver en ren tvekamp.",
|
||||
"Kun to i aften. Så kan ingen gemme sig.",
|
||||
"Et hold på to. Hyggeligt og helt uden nåde."
|
||||
],
|
||||
"game_start_3": [
|
||||
"Vi er tre. Lille hold, stor ambition.",
|
||||
"Så kører vi med tre spillere. Overskueligt. Indtil videre.",
|
||||
"Et hold på tre. Det er lige nok til en alliance og et forræderi."
|
||||
],
|
||||
"game_start_4": [
|
||||
"Vi er fire. Det er en rigtig spilleaften.",
|
||||
"Så starter vi med fire spillere. Perfekt til et sofabord.",
|
||||
"Holdet er på fire. Ingen kan gemme sig bag de andre."
|
||||
],
|
||||
"game_start_5": [
|
||||
"Vi er fem. Det bliver tæt.",
|
||||
"Med fem spillere kan alt ske.",
|
||||
"Holdet tæller fem. Jeg har allerede en favorit. Jeg siger ikke hvem."
|
||||
],
|
||||
"game_start_6": [
|
||||
"Vi er seks. Nu er det en rigtig fest.",
|
||||
"Med seks spillere bliver det kaos. Den gode slags.",
|
||||
"Så er vi seks. Sofaen er officielt fuld."
|
||||
],
|
||||
"game_start_7": [
|
||||
"Vi er syv. Et heldigt tal. For nogen af jer.",
|
||||
"Med syv spillere bliver det svært at holde styr på, hvem der gjorde hvad.",
|
||||
"Så er vi syv. Jeg har sat ekstra stole frem."
|
||||
],
|
||||
"game_start_8": [
|
||||
"Vi er otte. Nu bliver det larmende.",
|
||||
"Med otte spillere er der ingen steder at gemme sig. Eller alle steder.",
|
||||
"Så kører vi med otte. Det bliver en lang aften. Heldigvis."
|
||||
],
|
||||
"game_start_9": [
|
||||
"Vi er ni. Det er næsten et fodboldhold.",
|
||||
"Med ni spillere bliver det vildt.",
|
||||
"Så er vi ni. Jeg håber, I har ladet telefonerne op."
|
||||
],
|
||||
"game_start_10": [
|
||||
"Vi er ti! Fuldt hus.",
|
||||
"Med ti spillere er der fest i hele stuen.",
|
||||
"Så er vi ti. Det er rekord. I hvert fald i aften."
|
||||
],
|
||||
"game_start_2_fest": [
|
||||
"Vi er kun to. Det bliver en drikkeduel, bestie.",
|
||||
"Kun to til festen. Lowkey intimt. Skål."
|
||||
],
|
||||
"game_start_3_fest": [
|
||||
"Vi er tre. Lille squad, stor tørst.",
|
||||
"Så er vi tre. No cap, nogen drikker meget i aften."
|
||||
],
|
||||
"game_start_4_fest": [
|
||||
"Vi er fire. Det er fire glas, der skal tømmes.",
|
||||
"Squad'en er på fire. Vibe check bestået."
|
||||
],
|
||||
"game_start_5_fest": [
|
||||
"Vi er fem. Glassene er klar, undskyldningerne er ikke.",
|
||||
"Med fem spillere bliver det kaos. Det gode kaos."
|
||||
],
|
||||
"game_start_6_fest": [
|
||||
"Vi er seks. Nu bliver det en rigtig fest, bestie.",
|
||||
"Med seks spillere er der nok vidner til alt."
|
||||
],
|
||||
"game_start_7_fest": [
|
||||
"Vi er syv. Den her aften kommer på stories.",
|
||||
"Så er vi syv. Lowkey for mange vidner."
|
||||
],
|
||||
"game_start_8_fest": [
|
||||
"Vi er otte! Fuldt hus, fulde glas.",
|
||||
"Med otte spillere bliver nogen cooked. Det er bare matematik."
|
||||
]
|
||||
}
|
||||
+65
-12
@@ -5,8 +5,14 @@ import random
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
LINES = json.loads((Path(__file__).resolve().parents[1] / "fupogfakta" / "narrator" / "lines.json")
|
||||
.read_text(encoding="utf-8"))
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
LINES = json.loads((ROOT / "fupogfakta" / "narrator" / "lines.json").read_text(encoding="utf-8"))
|
||||
# Generiske replikker: samme begivenheder uden navne og spillertekst, så de kan forvarmes. Speakeren siger dem,
|
||||
# når den rigtige replik ikke kan laves (ingen live-stemme). Nøglen er begivenheden; "game_start_<antal>" for
|
||||
# antal spillere; en tekst i stedet for en liste peger på en anden begivenheds replikker. Fælles for alle spil
|
||||
# (party/generic_lines.json) plus spillets egne.
|
||||
GENERIC_COMMON = json.loads((ROOT / "party" / "generic_lines.json").read_text(encoding="utf-8"))
|
||||
GENERIC = json.loads((ROOT / "fupogfakta" / "narrator" / "generic.json").read_text(encoding="utf-8"))
|
||||
|
||||
ONES = ["nul", "én", "to", "tre", "fire", "fem", "seks", "syv", "otte", "ni", "ti", "elleve", "tolv",
|
||||
"tretten", "fjorten", "femten", "seksten", "sytten", "atten", "nitten"]
|
||||
@@ -90,10 +96,30 @@ TEASES_FEST = ["Bro er cooked!", "[laughs] L, tag en tår!", "Ohio-moment! Skål
|
||||
"Bussin' løgn, cooked gæt!", "[mischievously] Tak for tåren!", "No cap, du blev fooled!", "Main character? Nah, NPC!"]
|
||||
|
||||
|
||||
class Line(str):
|
||||
"""En replik, der kender sin generiske afløser: generic(accept) giver en udgave uden navne og spillertekst
|
||||
(kun blandt dem, accept godtager, fx dem der ligger i lydcachen), eller None. Rummet spørger, når replikken
|
||||
selv ikke kan laves."""
|
||||
|
||||
def generic(self, accept=None) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def with_generic(text: str | None, generic) -> Line | None:
|
||||
"""Giv en tekst (fx hjernens) en generisk afløser: generic(accept) -> str | None."""
|
||||
if text is None:
|
||||
return None
|
||||
line = Line(text)
|
||||
line.generic = generic
|
||||
return line
|
||||
|
||||
|
||||
class Narrator:
|
||||
def __init__(self, rng: random.Random | None = None, fest: bool = False, lines: dict | None = None):
|
||||
def __init__(self, rng: random.Random | None = None, fest: bool = False, lines: dict | None = None,
|
||||
generic: dict | None = None):
|
||||
self.rng = rng or random.Random()
|
||||
self.lines = LINES if lines is None else lines # et andet spil har sin egen lines.json
|
||||
self.lines = LINES if lines is None else lines # et andet spil har sin egen lines.json ...
|
||||
self.generic_lines = {**GENERIC_COMMON, **(GENERIC if lines is None else generic or {})} # ... og generic.json
|
||||
self.fest = fest # fest: replikken "<begivenhed>_fest" bruges, når den findes
|
||||
self._recent: dict[str, list[int]] = {}
|
||||
|
||||
@@ -105,22 +131,49 @@ class Narrator:
|
||||
filled = {k: (number_words(v) if isinstance(v, int) else safe(v)) for k, v in values.items()}
|
||||
return capitalize(re.sub(r"\{(\w+)\}", lambda m: filled.get(m.group(1), ""), variant))
|
||||
|
||||
def line(self, event: str, **values) -> str | None:
|
||||
event = self._event(event)
|
||||
variants = self.lines.get(event)
|
||||
if not variants:
|
||||
return None
|
||||
recent = self._recent.setdefault(event, [])
|
||||
fresh = [i for i in range(len(variants)) if i not in recent] or list(range(len(variants)))
|
||||
def _pick(self, key: str, variants: list[str], allowed: list[int] | None = None) -> int:
|
||||
allowed = list(range(len(variants))) if allowed is None else allowed
|
||||
recent = self._recent.setdefault(key, [])
|
||||
fresh = [i for i in allowed if i not in recent] or allowed
|
||||
i = self.rng.choice(fresh)
|
||||
recent.append(i)
|
||||
del recent[:-max(1, len(variants) // 2)] # undgå gentagelser i den seneste halvdel
|
||||
return self.fill(variants[i], values)
|
||||
return i
|
||||
|
||||
def line(self, event: str, **values) -> Line | None:
|
||||
key = self._event(event)
|
||||
variants = self.lines.get(key)
|
||||
if not variants:
|
||||
return None
|
||||
return with_generic(self.fill(variants[self._pick(key, variants)], values),
|
||||
lambda accept=None: self.generic(event, accept, **values))
|
||||
|
||||
def variants(self, event: str, **values) -> list[str]:
|
||||
"""Alle udgaver af replikken, som line() ville sige dem (til forvarmning af lyden)."""
|
||||
return [self.fill(v, values) for v in self.lines.get(self._event(event), [])]
|
||||
|
||||
# ── generiske replikker ──────────────────────────────────────────────────
|
||||
def _generic_key(self, event: str, values: dict) -> str | None:
|
||||
if event == "game_start" and isinstance(values.get("antal_spillere"), int):
|
||||
event = f"game_start_{values['antal_spillere']}"
|
||||
v = self.generic_lines.get(event)
|
||||
if isinstance(v, str): # henvisning til en anden begivenheds replikker
|
||||
event = v
|
||||
if self.fest and isinstance(self.generic_lines.get(event + "_fest"), list):
|
||||
return event + "_fest"
|
||||
return event if isinstance(self.generic_lines.get(event), list) else None
|
||||
|
||||
def generic(self, event: str, accept=None, **values) -> str | None:
|
||||
"""En generisk udgave af begivenhedens replik (samme rotation som line()); accept(tekst) begrænser valget."""
|
||||
key = self._generic_key(event, values)
|
||||
variants = self.generic_lines.get(key, []) if key else []
|
||||
allowed = [i for i, v in enumerate(variants) if accept is None or accept(v)]
|
||||
return variants[self._pick("generic:" + key, variants, allowed)] if allowed else None
|
||||
|
||||
def generic_variants(self, event: str, **values) -> list[str]:
|
||||
key = self._generic_key(event, values)
|
||||
return list(self.generic_lines.get(key, [])) if key else []
|
||||
|
||||
# ── afsløringen ──────────────────────────────────────────────────────────
|
||||
def reveal_step(self, step: dict, names: dict[str, str], n_players: int, comment: str | None = None) -> str | None:
|
||||
picked = [names[p] for p in step["guessed_by"] if p in names]
|
||||
|
||||
+4
-3
@@ -23,8 +23,9 @@ from .narrator import Narrator, untag
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
LINES = json.loads((Path(__file__).resolve().parents[1] / "tegnoggaet" / "narrator" / "lines.json")
|
||||
.read_text(encoding="utf-8"))
|
||||
NARRATOR_DIR = Path(__file__).resolve().parents[1] / "tegnoggaet" / "narrator"
|
||||
LINES = json.loads((NARRATOR_DIR / "lines.json").read_text(encoding="utf-8"))
|
||||
GENERIC = json.loads((NARRATOR_DIR / "generic.json").read_text(encoding="utf-8")) # se party.narrator
|
||||
BEAT_PAD_MS = 700 # luft efter speakerens replik i et trin af afsløringen
|
||||
HURRY_BEFORE = 10 # "ti sekunder tilbage"
|
||||
# Replikker uden navne eller spillertekst, som rummet siger alene: dem kan tts_warm lave på forhånd.
|
||||
@@ -41,7 +42,7 @@ class TegnRoom(rooms.BaseRoom):
|
||||
|
||||
def __init__(self, code: str, game: Game, host_username: str):
|
||||
super().__init__(code, game, host_username)
|
||||
self.narrator = Narrator(lines=LINES)
|
||||
self.narrator = Narrator(lines=LINES, generic=GENERIC)
|
||||
self._last_join_line = 0.0
|
||||
self._lobby_waiting_said = False
|
||||
self._beat_timers: list[asyncio.TimerHandle] = []
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
"""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
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"lobby_waiting": [
|
||||
"Vi er klar. VIP'en må gerne trykke start.",
|
||||
"Alle har fundet blyanten. Nu mangler vi bare startknappen.",
|
||||
"[sighs] Pennene tørrer ud. VIP, tryk start.",
|
||||
"Holdet er samlet. VIP'en bestemmer, hvornår vi tegner.",
|
||||
"Så er vi nok. Startknappen sidder på VIP'ens telefon."
|
||||
],
|
||||
"theme_choose": [
|
||||
"VIP'en vælger et tema på telefonen.",
|
||||
"Hvad skal vi tegne? Det bestemmer VIP'en.",
|
||||
"Tid til at vælge tema. Vælg med omhu. Eller vælg noget sjovt.",
|
||||
"[curious] Hvilket tema bliver det? Det afgøres på VIP'ens telefon.",
|
||||
"Et tema, tak. Resten af jer kan begynde at varme fingrene op."
|
||||
],
|
||||
"chain_intro_first": [
|
||||
"Vi starter med den første kæde.",
|
||||
"Første kæde. Lad os se, hvad der skete med ordet.",
|
||||
"[curious] Her er den første kæde. Hvor mon den endte?",
|
||||
"Den første kæde ruller ind. Hold godt fast.",
|
||||
"Lad os begynde med den første kæde. Den startede så uskyldigt."
|
||||
],
|
||||
"chain_intro": [
|
||||
"Her er næste kæde. Navnet på skærmen startede den.",
|
||||
"Lad os se den her kæde. Den startede med et helt uskyldigt ord.",
|
||||
"[curious] Næste kæde. Hvad mon der skete her?",
|
||||
"Så er det en ny kæde. Hold fast.",
|
||||
"Næste kæde. Kig på det første ord og gæt, hvor det ender.",
|
||||
"En ny kæde ruller ind. Hvad gik galt denne gang?"
|
||||
],
|
||||
"chain_survived": [
|
||||
"[gasps] Den kom hele vejen rundt!",
|
||||
"Utroligt. Ordet overlevede hele turen.",
|
||||
"[impressed] Perfekt kæde! Godt tegnet og godt gættet.",
|
||||
"Hele vejen rundt uden et eneste uheld. Det sker næsten aldrig.",
|
||||
"Den holdt! Bonus til den, der startede kæden."
|
||||
],
|
||||
"chain_changed": [
|
||||
"[laughs] Se lige, hvad det første ord blev til.",
|
||||
"Det startede ét sted og endte et helt andet sted.",
|
||||
"[chuckles] Fra det ene til det andet. Det er kunst.",
|
||||
"Ordet tog en omvej. En lang en.",
|
||||
"Det første ord og det sidste gæt har ikke meget tilfælles.",
|
||||
"Klassisk. Et sted på vejen gik det helt galt."
|
||||
],
|
||||
"award_best_drawing": [
|
||||
"Prisen for bedste tegning! Flest hjerter til en tegning.",
|
||||
"Årets kunstner er fundet. Navnet står på skærmen.",
|
||||
"[impressed] Bedste tegning. Den var næsten genkendelig.",
|
||||
"Flest hjerter til en tegning. Museet har ringet.",
|
||||
"Bedste tegning går til en, der bør tegne noget mere."
|
||||
],
|
||||
"award_best_guess": [
|
||||
"Bedste gæt! Flest hjerter til et gæt.",
|
||||
"Prisen for bedste gæt. Fantasi slår facit.",
|
||||
"[laughs] Det sjoveste gæt i aften fik flest hjerter.",
|
||||
"Bedste gæt. Nogle gange er det forkerte svar det rigtige.",
|
||||
"Flest hjerter til et gæt. Hjernen bag det står på skærmen."
|
||||
],
|
||||
"award_survived": [
|
||||
"Kæden der overlevede! Bonus til den, der startede den.",
|
||||
"En kæde kom hele vejen rundt. Det giver bonus.",
|
||||
"[impressed] Ordet overlevede hele turen. Bonus!",
|
||||
"Den kæde holdt. Bonuspoint til ejeren.",
|
||||
"Prisen for kæden, der overlevede. Sjældent og smukt."
|
||||
],
|
||||
"award_transformed": [
|
||||
"Største forvandling! Den kæde tog den længste omvej.",
|
||||
"Prisen for største forvandling. Ordet blev til noget helt andet.",
|
||||
"[laughs] Ingen kæde ændrede sig mere end den her.",
|
||||
"Største forvandling. Fra noget til noget helt andet.",
|
||||
"Den kæde forvandlede sig mest. Det er næsten magi."
|
||||
],
|
||||
"winner": [
|
||||
"Vi har en vinder! Aftenens kunstner står øverst.",
|
||||
"[excited] Tillykke til vinderen!",
|
||||
"Spillet er slut, og vinderen står øverst på skærmen.",
|
||||
"Tillykke! Flest point og fortjent ære.",
|
||||
"Sejren er afgjort. Blyanten går videre til næste gang."
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user