Spionen: rettelser fra review og QA, og bots
Replikker:
- To spioner siges i flertal ("Spionerne var ..."), også i den generiske
udgave; fanges den ene, og den anden slipper, er det en halv sejr
("Den ene spion er fanget, men den anden slap væk"), ikke "Agenterne
vinder". Storskærmens overskrift følger med.
- Er spionen gået i løbet af runden, siges "Spionen forlod missionen", og
navnet står stadig i afsløringen (motoren husker spionernes navne fra
rundens start; storskærmen viser dem uden farve).
- Fire små replikker skrevet om (vedkommende, "Måske er det ansigtet",
skarpeste øje = flest rigtige stemmer, vinderen gentog ikke den faste).
Ord: Spionen bruger ikke ord under tre bogstaver ("is", "fe"); de står inde
i for mange andre ord (isvaffel), til at stikordene kan holdes fri af dem.
Mad har 17 ord, Eventyr 15.
Test: speaker-testen kører uden rigtige ventetider (37 s til 4 s for hele
filen); skift-testen venter på det nye spils lobby i stedet for at gætte
på tiden.
deploy/bots.py --game spion: tilfældige stikord (prøver et andet, hvis det
afvises), tilfældige stemmer og gæt; værten forkorter snakken. En stemme,
der kommer et øjeblik efter, at fasen sluttede, er ikke en fejl.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+72
-5
@@ -11,6 +11,10 @@ faser blev nået og slutstillingen blev vist.
|
||||
Tegn og Gæt (mindst 3 spillere):
|
||||
python deploy/bots.py --base http://127.0.0.1:8000 --game tegn --players 5
|
||||
Bots tegner tilfældige streger og gætter tilfældige ord.
|
||||
|
||||
Spionen (mindst 4 spillere):
|
||||
python deploy/bots.py --base http://127.0.0.1:8000 --game spion --players 10
|
||||
Bots giver tilfældige stikord, stemmer tilfældigt, og en anklaget spion gætter tilfældigt. Værten forkorter snakken.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -60,6 +64,8 @@ async def host(ws_base, code, seen: list, done: asyncio.Event):
|
||||
st = msg["state"]
|
||||
if not seen or seen[-1] != st["phase"]:
|
||||
seen.append(st["phase"])
|
||||
if st.get("game") == "spion" and st["phase"] == "talk": # værten forkorter snakken (bots snakker ikke)
|
||||
await ws.send(json.dumps({"action": "skip"}))
|
||||
extra = st.get("question", {}).get("prompt", "") if st["phase"] == "lie" else ""
|
||||
print(f"[vært] {st['phase']:8} {extra}")
|
||||
if st["phase"] == "final":
|
||||
@@ -74,7 +80,8 @@ async def host(ws_base, code, seen: list, done: asyncio.Event):
|
||||
|
||||
async def player(ws_base, code, name, n_players, done: asyncio.Event, rng: random.Random, stop_at_final=False, game="fof"):
|
||||
try:
|
||||
await (_tegn_player if game == "tegn" else _player)(ws_base, code, name, n_players, done, rng, stop_at_final)
|
||||
play = {"tegn": _tegn_player, "spion": _spion_player}.get(game, _player)
|
||||
await play(ws_base, code, name, n_players, done, rng, stop_at_final)
|
||||
except websockets.ConnectionClosed:
|
||||
print(f"[{name}] forbindelsen blev lukket (findes rummet?)")
|
||||
done.set()
|
||||
@@ -211,6 +218,64 @@ async def _tegn_player(ws_base, code, name, n_players, done, rng, stop_at_final)
|
||||
await ws.send(json.dumps({"action": "react", "emoji": rng.choice(st.get("reactions") or ["😂"])}))
|
||||
|
||||
|
||||
SPION_CLUES = ["rund", "lille", "stor", "blød", "hård", "rød", "grøn", "kold", "varm", "hurtig", "langsom", "farlig",
|
||||
"sjov", "gammel", "ny", "tung", "let", "lang", "kort", "våd", "tør", "glat", "skarp", "larm", "stille",
|
||||
"dyr", "billig", "udenfor", "indenfor", "sommer", "vinter", "morgen", "aften", "hjem", "skole"]
|
||||
|
||||
|
||||
async def _spion_player(ws_base, code, name, n_players, done, rng, stop_at_final):
|
||||
async with websockets.connect(f"{ws_base}/ws/play/{code}/") as ws:
|
||||
await ws.send(json.dumps({"action": "join", "name": name}))
|
||||
acted, state = set(), None
|
||||
while not done.is_set():
|
||||
try:
|
||||
raw = await asyncio.wait_for(ws.recv(), timeout=1)
|
||||
except asyncio.TimeoutError:
|
||||
continue
|
||||
msg = json.loads(raw)
|
||||
if msg["type"] == "error":
|
||||
if msg["code"] in ("clue_is_word", "clue_is_theme", "clue_taken", "clue_invalid") and state:
|
||||
# afvist stikord: prøv et andet (det er en del af spillet, ikke en fejl)
|
||||
await ws.send(json.dumps({"action": "submit_clue", "text": rng.choice(SPION_CLUES)}))
|
||||
continue
|
||||
if msg["code"] == "wrong_phase":
|
||||
continue # stemte et øjeblik efter, at alle andre havde stemt: helt normalt
|
||||
print(f"[{name}] fejl: {msg['code']} {msg['message']}")
|
||||
if msg["code"] == "unknown_room":
|
||||
done.set()
|
||||
return
|
||||
continue
|
||||
if msg["type"] != "state":
|
||||
continue
|
||||
state = st = msg["state"]
|
||||
me = st["me"]
|
||||
if st["phase"] == "final" and stop_at_final:
|
||||
done.set()
|
||||
return
|
||||
key = (st["phase"], (st.get("round") or {}).get("number"))
|
||||
pause = lambda: asyncio.sleep(rng.uniform(0.05, 0.4)) # noqa: E731
|
||||
if st["phase"] == "lobby" and me["vip"] and len(st["players"]) >= n_players and key not in acted:
|
||||
acted.add(key)
|
||||
await pause()
|
||||
await ws.send(json.dumps({"action": "start"}))
|
||||
elif st["phase"] == "clue" and me.get("card") and not me.get("clue") and key not in acted:
|
||||
acted.add(key)
|
||||
await pause()
|
||||
await ws.send(json.dumps({"action": "submit_clue", "text": rng.choice(SPION_CLUES)}))
|
||||
elif st["phase"] == "vote" and not me.get("vote") and key not in acted:
|
||||
options = [p["id"] for p in st["players"] if p["in_round"] and p["id"] != me["id"]]
|
||||
if options:
|
||||
acted.add(key)
|
||||
await pause()
|
||||
await ws.send(json.dumps({"action": "vote", "player_id": rng.choice(options)}))
|
||||
elif st["phase"] == "guess" and me.get("can_guess") and key not in acted:
|
||||
acted.add(key)
|
||||
await pause()
|
||||
await ws.send(json.dumps({"action": "spy_guess", "text": rng.choice(SPION_CLUES)}))
|
||||
if st["phase"] in ("talk", "result") and rng.random() < 0.2:
|
||||
await ws.send(json.dumps({"action": "react", "emoji": rng.choice(st.get("reactions") or ["😂"])}))
|
||||
|
||||
|
||||
async def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--base", default="http://127.0.0.1:8000")
|
||||
@@ -219,7 +284,8 @@ async def main():
|
||||
ap.add_argument("--seed", type=int, default=None)
|
||||
ap.add_argument("--pack", default="alle", help="spørgsmålspakke (alle, familie, nørd, arbejde, fest)")
|
||||
ap.add_argument("--code", help="join et eksisterende rum i stedet for at oprette et (du er selv vært/VIP)")
|
||||
ap.add_argument("--game", default="fof", choices=["fof", "tegn"], help="spillet (fof = Fup og Fakta, tegn = Tegn og Gæt)")
|
||||
ap.add_argument("--game", default="fof", choices=["fof", "tegn", "spion"],
|
||||
help="spillet (fof = Fup og Fakta, tegn = Tegn og Gæt, spion = Spionen)")
|
||||
args = ap.parse_args()
|
||||
rng = random.Random(args.seed)
|
||||
if args.code:
|
||||
@@ -236,7 +302,7 @@ async def main():
|
||||
await asyncio.sleep(0.1)
|
||||
# Publikum joiner, når spillet er i gang (i lobbyen ville de blive spillere).
|
||||
for _ in range(200):
|
||||
if "category" in seen or "theme" in seen:
|
||||
if "category" in seen or "theme" in seen or "clue" in seen:
|
||||
break
|
||||
await asyncio.sleep(0.1)
|
||||
for i in range(args.audience):
|
||||
@@ -248,8 +314,9 @@ async def main():
|
||||
return 1
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
needed = ({"lobby", "rules", "theme", "word", "draw", "guess", "reveal", "final"} if args.game == "tegn" else
|
||||
{"lobby", "rules", "category", "lie", "present", "guess", "reveal", "scores", "final"})
|
||||
needed = {"tegn": {"lobby", "rules", "theme", "word", "draw", "guess", "reveal", "final"},
|
||||
"spion": {"lobby", "rules", "clue", "clues", "talk", "vote", "verdict", "result", "final"},
|
||||
}.get(args.game, {"lobby", "rules", "category", "lie", "present", "guess", "reveal", "scores", "final"})
|
||||
missing = needed - set(seen)
|
||||
if missing:
|
||||
print(f"Faser der aldrig blev nået: {missing}", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user