Fest-pakken handler nu om ungdomskultur: "Fest: slang" (årets ord, slangens oprindelse, dansk ungdomssprog) og "Fest: internettet" (memes, creators, spil), ved siden af "Fest: nu". "Fest: retro" er taget ud. Hjernen får sin egen fest-instruks (zoomer-speaker, tår må nævnes), så afsløringens replikker og kommentarer også taler zoomer. Telefonens drillerier har en fest-pulje, og de sidste faste replikker har fået fest-udgaver. Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
This commit is contained in:
+1264
-599
File diff suppressed because it is too large
Load Diff
@@ -140,6 +140,13 @@
|
||||
"[suspicious] Der var en, der skrev det rigtige svar. Det tæller ikke.",
|
||||
"Sandheden er ikke en løgn. Prøv igen."
|
||||
],
|
||||
"lie_was_truth_fest": [
|
||||
"Bro skrev sandheden. Det er ikke rizz, det er snyd. Prøv igen.",
|
||||
"[sarcastic] Wow, ærlighed. Super mid. Skriv en løgn, bestie.",
|
||||
"Nogen ramte sandheden. Lowkey sus. Gem den til gættefasen.",
|
||||
"Det var faktisk sandt, no cap. Vi skal bruge en løgn, ikke en TED talk.",
|
||||
"[gasps] Sandheden sneg sig ind. Delulu er fint her, skriv noget falsk."
|
||||
],
|
||||
"guess_start": [
|
||||
"Én af dem er sand. Held og lykke. I får brug for det.",
|
||||
"Vælg det sande svar. Selvtillid er desværre en dårlig kildehenvisning.",
|
||||
@@ -345,6 +352,16 @@
|
||||
"{svar}. Tænk lige over den.",
|
||||
"Og {svar}. Hvorfor ikke?"
|
||||
],
|
||||
"present_answer_fest": [
|
||||
"{svar}.",
|
||||
"Måske {svar}.",
|
||||
"Eller hvad med {svar}?",
|
||||
"{svar}. Lowkey plausibelt.",
|
||||
"{svar}? Hmm, it's giving.",
|
||||
"Så er der {svar}.",
|
||||
"[whispers] {svar}.",
|
||||
"{svar}. Tænk lige over den, bestie."
|
||||
],
|
||||
"summary_cleanup": [
|
||||
"De her løgne troede ingen på. Lad os lige rydde op.",
|
||||
"[sighs] Ingen faldt for de her. Væk med dem.",
|
||||
@@ -386,6 +403,13 @@
|
||||
"{svar} fik flest reaktioner. Selv jeg måtte trække på smilebåndet.",
|
||||
"Stuen elskede {svar}. Så er det sagt."
|
||||
],
|
||||
"summary_favorite_fest": [
|
||||
"Publikums favorit var {svar}. I ate den op.",
|
||||
"[laughs] {svar} fik hele festen til at gå amok. W.",
|
||||
"Chatten var vild med {svar}. Main character energy.",
|
||||
"{svar} fik flest reaktioner. Aura plus tusind.",
|
||||
"I elskede {svar}. Slay, no cap."
|
||||
],
|
||||
"lie_progress_fast": [
|
||||
"{navn} er allerede færdig. Mistænkeligt hurtigt.",
|
||||
"[suspicious] {navn} har allerede løjet. Det gik lidt for let.",
|
||||
@@ -398,6 +422,13 @@
|
||||
"{navn} er færdig. Resten af jer kan godt mærke presset nu.",
|
||||
"[impressed] {navn} har allerede løjet. Imponerende. Og lidt bekymrende."
|
||||
],
|
||||
"lie_progress_fast_fest": [
|
||||
"{navn} er allerede færdig. Speedrun, any procent.",
|
||||
"[suspicious] {navn} har løjet på rekordtid. Lowkey sus.",
|
||||
"{navn} er klar. Sigma grindset.",
|
||||
"[chuckles] {navn} er færdig allerede. Bro tænker ikke, bro gør bare.",
|
||||
"Første løgn er inde, tak til {navn}. Resten af jer er cooked."
|
||||
],
|
||||
"lie_progress_retry": [
|
||||
"{navn} er færdig. Andet forsøg sad i skabet.",
|
||||
"{navn} fik det til sidst. Øvelse gør mester.",
|
||||
@@ -408,6 +439,12 @@
|
||||
"[sighs] {navn} er endelig færdig. Jeg var lige ved at blive bekymret.",
|
||||
"{navn} fandt en ny løgn. Den gamle var for ærlig."
|
||||
],
|
||||
"lie_progress_retry_fest": [
|
||||
"{navn} er færdig. Andet forsøg, and we ball.",
|
||||
"[chuckles] {navn} prøvede igen, og nu sidder den. Character development.",
|
||||
"{navn} kom i mål efter lidt bøvl. Det tæller stadig, bestie.",
|
||||
"{navn} fandt en ny løgn. Den gamle var alt for ærlig."
|
||||
],
|
||||
"lie_progress": [
|
||||
"{navn} er færdig. Lad os håbe, den er god.",
|
||||
"{navn} har løjet. Jeg tager det ikke personligt.",
|
||||
@@ -420,6 +457,14 @@
|
||||
"{navn} er inde. Nu mangler vi bare resten af familien.",
|
||||
"[whispers] {navn} er færdig. Og jeg tror, den er lumsk."
|
||||
],
|
||||
"lie_progress_fest": [
|
||||
"{navn} er færdig. Lad os håbe, den slayer.",
|
||||
"{navn} har løjet. Aura farmet.",
|
||||
"[mischievously] {navn} har skrevet noget. Jeg siger intet, men it's giving.",
|
||||
"{navn} er inde. Selvsikker. Det er et red flag.",
|
||||
"[whispers] {navn} er færdig. Og den er lowkey lumsk.",
|
||||
"En løgn mere i posen. W, {navn}."
|
||||
],
|
||||
"lobby_welcome_fest": [
|
||||
"[excited] Yo yo yo, velkommen til festen! Telefonen frem, glasset fyldt, hjernen må gerne blive hjemme.",
|
||||
"Hvad så, besties? Det her er Fup og Fakta, festudgaven. Jeres lever har ikke sagt ja, men vi kører alligevel.",
|
||||
|
||||
+38
-8
@@ -36,6 +36,32 @@ Regler for alt, du skriver:
|
||||
"Hele sofaen tog fejl. Sikke en sjælden form for enighed." "Ti sekunder. Det behøver ikke være godt. Bare forkert."
|
||||
- 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.
|
||||
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.
|
||||
|
||||
Din stil: en hypet, memet zoomer-streamer fra København med brainrot-energi. Du taler dansk blandet med
|
||||
zoomer- og internetslang: no cap, for real, lowkey, highkey, it's giving, slay, ate, rizz, aura, cooked,
|
||||
delulu, NPC, main character energy, Ohio, sus, bestie, based, mid, W og L, skibidi, sigma, "hold da op, bro".
|
||||
Brug slangen naturligt, et eller to udtryk pr. replik, ikke en ordsalat. Du roaster kærligt og hyper folk op.
|
||||
|
||||
Regler for alt, du skriver:
|
||||
- Mundret, skrevet til at blive LÆST HØJT af en stemme. Korte sætninger.
|
||||
- Aldrig grimt, aldrig om udseende, køn, krop eller lignende. Intet groft eller seksuelt. Du må gerne nævne
|
||||
tår, shots og at nogen skal drikke, men opfordr aldrig til at drikke sig fuld, og hyld gerne vand.
|
||||
- Ingen tankestreger (hverken — eller - som indskud). Brug komma eller punktum.
|
||||
- Skriv tal med bogstaver ("tre tår", ikke "3 tår").
|
||||
- Du må give stemmen en lyd-tag i firkantede klammer, fx "[laughs] Bro er cooked." Kun disse: [laughs]
|
||||
[chuckles] [giggles] [sighs] [whispers] [excited] [mischievously] [sarcastic] [suspicious] [impressed]
|
||||
[gasps] [dramatic] [cheering] [curious] [snorts]. Højst én pr. replik. Tags vises ikke på skærmen.
|
||||
- Kald spillerne ved navn. Gæt aldrig køn ud fra et navn: ingen "han", "hun", "ham" eller "hende" om en spiller.
|
||||
- Én pointe pr. replik. Konkret frem for abstrakt.
|
||||
- Gode eksempler på tonen: "Sus faldt for den. Lowkey cooked, tag en tår, bestie."
|
||||
"Ingen fandt sandheden. Kollektiv Ohio-moment. Alle drikker." "Mads skrev den løgn. Aura plus tusind, no cap."
|
||||
- Brug gerne aftenens hukommelse til en pointe, der bygger videre på noget, der er sket tidligere."""
|
||||
|
||||
|
||||
def _schema(props: dict, required: list[str] | None = None) -> dict:
|
||||
return {"type": "object", "properties": props, "required": required or list(props), "additionalProperties": False}
|
||||
@@ -50,12 +76,14 @@ REVIEW = _schema({"lines": {"type": "array", "items": _schema({"player_id": {"ty
|
||||
|
||||
|
||||
class GameBrain:
|
||||
def __init__(self, provider: Provider):
|
||||
def __init__(self, provider: Provider, fest: bool = False):
|
||||
self.provider = provider
|
||||
self.fest = fest
|
||||
self.system = SYSTEM_FEST if fest else SYSTEM
|
||||
|
||||
async def _ask(self, task: str, payload: dict, schema: dict, *, max_tokens: int, timeout: float):
|
||||
user = f"{task}\n\nData (JSON):\n{json.dumps(payload, ensure_ascii=False)}"
|
||||
return await self.provider.complete_json(SYSTEM, user, schema, max_tokens=max_tokens, timeout=timeout)
|
||||
return await self.provider.complete_json(self.system, user, schema, max_tokens=max_tokens, timeout=timeout)
|
||||
|
||||
async def present_lines(self, question: dict, answers: list[dict]) -> dict[str, str]:
|
||||
"""{answer_id: overbevisende kommentar} til oplæsningen. answers: [{id, text}] uden at sige hvilket der er sandt."""
|
||||
@@ -104,9 +132,11 @@ class GameBrain:
|
||||
return await self._ask(
|
||||
"En spiller har afleveret en løgn. Afgør to ting. is_truth: er løgnen reelt sandheden (samme betydning, "
|
||||
"synonym, bøjning, stavefejl eller omskrivning af sandheden eller en af dens stavemåder)? En anden, "
|
||||
"forkert ting er IKKE sandheden. inappropriate: er løgnen upassende for et familiespil med børn "
|
||||
"(grov, seksuel, nedsættende, voldelig)? Fjollet og frækt-sjovt er fint. message: hvis en af dem er "
|
||||
"sand, en kort, venlig besked til spilleren på dansk om at skrive en ny løgn; ellers tom streng.",
|
||||
"forkert ting er IKKE sandheden. inappropriate: er løgnen upassende for "
|
||||
+ ("en fest blandt unge voksne (hadsk, nedsættende, grov seksuel, voldelig)? Fræk og plat er fint. "
|
||||
if self.fest else "et familiespil med børn (grov, seksuel, nedsættende, voldelig)? Fjollet og frækt-sjovt er fint. ")
|
||||
+ "message: hvis en af dem er sand, en kort, venlig besked til spilleren på dansk om at skrive en ny "
|
||||
"løgn; ellers tom streng.",
|
||||
{"spørgsmål": question.get("prompt"), "sandheden": question.get("answer"),
|
||||
"andre_stavemåder": question.get("alternatives", []), "løgn": lie},
|
||||
JUDGE, max_tokens=200, timeout=settings.BRAIN_TIMEOUT_FAST)
|
||||
@@ -137,8 +167,8 @@ class GameBrain:
|
||||
return [(x["player_id"], x["line"].strip()) for x in res.get("lines", []) if x.get("player_id") in ids and x.get("line")]
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_brain() -> GameBrain | None:
|
||||
@lru_cache(maxsize=2)
|
||||
def get_brain(fest: bool = False) -> GameBrain | None:
|
||||
provider = build_provider(settings.BRAIN_PROVIDER, settings.BRAIN_MODEL, settings.BRAIN_API_KEY,
|
||||
settings.BRAIN_BASE_URL, settings.BRAIN_EFFORT, settings.BRAIN_WORKSPACE)
|
||||
return GameBrain(provider) if provider else None
|
||||
return GameBrain(provider, fest=fest) if provider else None
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.conf import settings
|
||||
from fupogfakta.engine import RULES, RULES_FEST
|
||||
from fupogfakta.models import Category, Question
|
||||
from party import tts
|
||||
from party.narrator import LINES, TEASES, Narrator, speakable_prompt
|
||||
from party.narrator import LINES, TEASES, TEASES_FEST, Narrator, speakable_prompt
|
||||
|
||||
|
||||
def _expand_categories(lines: list[str], categories: list[str]) -> list[str]:
|
||||
@@ -44,7 +44,7 @@ class Command(BaseCommand):
|
||||
# fest-speakeren siger "<begivenhed>_fest", og den almindelige replik, hvor der ikke er en fest-udgave
|
||||
keys = [k for k in LINES if k.endswith("_fest") or k + "_fest" not in LINES]
|
||||
fest_static = [line for k in keys for line in LINES[k] if not re.search(r"\{\w+\}", line)]
|
||||
fest_static += TEASES + [r["say"] for r in RULES_FEST]
|
||||
fest_static += TEASES_FEST + [r["say"] for r in RULES_FEST]
|
||||
ok = sum(1 for line in fest_static if tts.synth_sync(line, fest))
|
||||
self.stdout.write(self.style.SUCCESS(f"{ok} af {len(fest_static)} faste fest-replikker ligger i cachen."))
|
||||
if budget <= 0:
|
||||
|
||||
@@ -81,6 +81,13 @@ TEASES = ["Buh!", "Hov hov, der røg du i!", "Avs, avs, avs!", "Snydt!", "Hihi,
|
||||
"Ups, der røg pointene.", "[mischievously] Tak for pointene!", "Godtroende!", "Sikke en naiv sjæl!",
|
||||
"Narret!", "Fanget med fingrene i løgnen!", "[gasps] Den troede du på?", "Lidt for tillidsfuld!",
|
||||
"Hjem og læs lektier!", "[sighs] Åh nej, ikke dig også.", "Den løgn sad lige i skabet!", "Bum! Fanget!"]
|
||||
# Fest-spillets drillerier: samme idé, bare på zoomer, og der skal drikkes.
|
||||
TEASES_FEST = ["Bro er cooked!", "[laughs] L, tag en tår!", "Ohio-moment! Skål!", "NPC-behavior!", "Aura minus tusind!",
|
||||
"Du blev scammet, bestie!", "[gasps] Du troede på den? Delulu!", "Skibidi-snydt!", "Lowkey pinligt. Drik!",
|
||||
"Det var ikke rizz, det var en løgn!", "[sarcastic] Big brain move. Not.", "Emotional damage! Skål!",
|
||||
"Fanum tax på din tår!", "[chuckles] Mid gæt, fuld tår!", "Rip bozo. Drik!", "Ratio! Tag en tår!",
|
||||
"Caught in 4K!", "[whispers] Sus. Meget sus.", "Den var for nem, bestie!", "It's giving godtroende!",
|
||||
"Bussin' løgn, cooked gæt!", "[mischievously] Tak for tåren!", "No cap, du blev fooled!", "Main character? Nah, NPC!"]
|
||||
|
||||
|
||||
class Narrator:
|
||||
|
||||
+3
-3
@@ -23,7 +23,7 @@ from fupogfakta.engine import Game
|
||||
from . import stt, tts
|
||||
from .assets import tease_images
|
||||
from .brain import get_brain
|
||||
from .narrator import TEASES, Narrator, untag
|
||||
from .narrator import TEASES, TEASES_FEST, Narrator, untag
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -77,7 +77,7 @@ class Room:
|
||||
self._phase_key = game.phase_key()
|
||||
self._pending: dict[str, list[float]] = {} # fasenøgle -> starttider for replikker under generering
|
||||
# Hjernen og hukommelsen
|
||||
self.brain = get_brain()
|
||||
self.brain = get_brain(fest=game.fest)
|
||||
self.memory: list[str] = []
|
||||
self._tease_texts: dict[str, str] = {}
|
||||
self.fooled_pairs: dict[tuple[str, str], int] = {} # (narret, forfatter) -> antal gange i aften
|
||||
@@ -642,7 +642,7 @@ class Room:
|
||||
g = self.game
|
||||
if g.phase != "reveal" or (g.round_idx, g.q_idx) != ident:
|
||||
return
|
||||
text = self._tease_texts.get(pid) or self.narrator.rng.choice(TEASES)
|
||||
text = self._tease_texts.get(pid) or self.narrator.rng.choice(TEASES_FEST if self.game.fest else TEASES)
|
||||
asyncio.get_running_loop().create_task(self._send_tease(pid, text))
|
||||
|
||||
async def _send_tease(self, pid: str, text: str):
|
||||
|
||||
@@ -210,3 +210,40 @@ class PitchTests(SimpleTestCase):
|
||||
pitch = {engine.normalize(k): v for k, v in q["pitch"].items()}
|
||||
covered = {a.id for a in answers if engine.normalize(a.text) in pitch}
|
||||
self.assertEqual(covered, {"a1", "a2"}) # spillerens løgn (a3) er den eneste, hjernen skal skrive til
|
||||
|
||||
|
||||
class SystemRecorder:
|
||||
name = "fake"
|
||||
|
||||
def __init__(self):
|
||||
self.systems, self.users = [], []
|
||||
|
||||
async def complete_json(self, system, user, schema, **kw):
|
||||
self.systems.append(system)
|
||||
self.users.append(user)
|
||||
return {}
|
||||
|
||||
|
||||
class FestBrainTests(SimpleTestCase):
|
||||
def test_fest_brain_speaks_zoomer(self):
|
||||
from party.brain.game import SYSTEM, SYSTEM_FEST
|
||||
fake = SystemRecorder()
|
||||
asyncio.run(GameBrain(fake, fest=True).reveal_lines({"prompt": "x"}, [], [], []))
|
||||
asyncio.run(GameBrain(fake).reveal_lines({"prompt": "x"}, [], [], []))
|
||||
self.assertEqual(fake.systems, [SYSTEM_FEST, SYSTEM])
|
||||
self.assertIn("no cap", SYSTEM_FEST)
|
||||
|
||||
def test_fest_judge_is_not_a_family_game(self):
|
||||
fake = SystemRecorder()
|
||||
asyncio.run(GameBrain(fake, fest=True).judge_lie({"prompt": "x", "answer": "y"}, "z"))
|
||||
self.assertNotIn("børn", fake.users[0])
|
||||
|
||||
def test_fest_room_gets_fest_brain_and_teases(self):
|
||||
from fupogfakta.engine import Game
|
||||
from party.narrator import TEASES, TEASES_FEST
|
||||
with mock.patch.object(rooms, "get_brain", return_value=None) as gb:
|
||||
rooms.create_room([], "agw", "http://testserver", fest=True)
|
||||
gb.assert_called_with(fest=True)
|
||||
self.assertGreaterEqual(len(TEASES_FEST), 10)
|
||||
self.assertFalse(set(TEASES_FEST) & set(TEASES))
|
||||
self.assertTrue(Game("ABCD", [], fest=True).fest)
|
||||
|
||||
Reference in New Issue
Block a user