Files
weirsoe-party-protocol/fupogfakta/tests/test_engine.py
T
agw 16054a9b67
CI / test (push) Successful in 28s
Skærmen følger speakeren, opsummering, musik, lydeffekter og drillerier
Faseskift venter, til speakeren er færdig med den aktuelle skærm, og
replikker til en skærm, der er væk, afspilles ikke. Ny kategori-intro med
en lyd pr. kategori, musik pr. fase med bløde overgange og lydeffekter til
alt, der flyver over skærmen. Efter afsløringen eksploderer løgne, ingen
valgte, og pointene tæller op. Hjerter gives kun under afsløringen og ses
live på storskærmen. Lyv for mig giver halve point. Narrede spilleres
telefon kan blive drillet med et fjollet billede og et buh. Alle lyd- og
billedfiler serveres med versionerede navne og caches af browseren.

Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
2026-10-01 21:59:40 +02:00

232 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import random
from django.test import SimpleTestCase
from fupogfakta import engine
from fupogfakta.engine import Game, GameError
def bank(n_cats=5, per_cat=6):
qs = []
for c in range(1, n_cats + 1):
for i in range(per_cat):
qs.append({"id": c * 100 + i, "category_id": c, "category": f"Kategori {c}",
"prompt": f"Spørgsmål {c}-{i} har svaret _____.", "answer": f"sandhed{c}{i}",
"alternatives": [], "decoys": [f"lok{c}{i}a", f"lok{c}{i}b", f"lok{c}{i}c"], "note": "Fordi."})
return qs
class GameTestCase(SimpleTestCase):
def setUp(self):
self.now = 1000.0
self.g = Game("TEST", bank(), join_url="http://x/?code=TEST", rng=random.Random(4))
def join(self, *names):
return [self.g.join(n) for n in names]
def through_category(self):
if self.g.phase == "category":
self.g.choose_category(self.g.chooser_id, self.g.category_options[0]["id"], self.now)
self.assertEqual(self.g.phase, "intro")
self.now += engine.INTRO_SECONDS
self.g.tick(self.now)
self.assertEqual(self.g.phase, "lie")
def truth_id(self):
return next(a.id for a in self.g.answers if a.is_truth)
class LobbyTests(GameTestCase):
def test_vip_is_first_player_and_only_vip_starts(self):
a, b = self.join("Asger", "Sus")
with self.assertRaises(GameError) as e:
self.g.start(b.id, self.now)
self.assertEqual(e.exception.code, "not_allowed")
self.g.start(a.id, self.now)
self.assertEqual(self.g.phase, "category")
def test_too_few_players(self):
self.join("Asger")
with self.assertRaises(GameError) as e:
self.g.start(None, self.now)
self.assertEqual(e.exception.code, "too_few_players")
def test_name_rules(self):
self.join("Asger")
for bad, code in [("asger", "name_taken"), ("", "name_invalid"), ("x" * 13, "name_invalid")]:
with self.assertRaises(GameError) as e:
self.g.join(bad)
self.assertEqual(e.exception.code, code)
def test_ninth_and_late_joiners_become_audience(self):
players = self.join(*[f"P{i}" for i in range(8)])
self.assertTrue(all(p.role == "player" for p in players))
self.assertEqual(self.g.join("Ni").role, "audience")
self.g.start(None, self.now)
self.assertEqual(self.g.join("Sen").role, "audience")
def test_resume_by_token(self):
a, = self.join("Asger")
self.assertIs(self.g.resume(a.token), a)
with self.assertRaises(GameError):
self.g.resume("nope")
class RoundTests(GameTestCase):
def setUp(self):
super().setUp()
self.a, self.b, self.c = self.join("A", "B", "C")
self.g.start(None, self.now)
self.through_category()
def test_only_chooser_may_choose(self):
self.g._begin_question(self.now)
other = next(p for p in self.g.players if p != self.g.chooser_id)
with self.assertRaises(GameError):
self.g.choose_category(other, self.g.category_options[0]["id"], self.now)
def test_lie_equal_or_close_to_truth_is_rejected(self):
truth = self.g.question["answer"]
for attempt in [truth, truth.upper() + "!", " " + truth + " ", "en " + truth, truth[:-1]]:
with self.assertRaises(GameError) as e:
self.g.submit_lie(self.a.id, attempt, self.now)
self.assertEqual(e.exception.code, "lie_is_truth", attempt)
def test_all_lies_moves_to_guess_and_hides_own_lie(self):
self.g.submit_lie(self.a.id, "katte", self.now)
self.g.submit_lie(self.b.id, "hunde", self.now)
self.assertEqual(self.g.phase, "lie")
self.g.submit_lie(self.c.id, "KATTE", self.now) # samme løgn som A
self.assertEqual(self.g.phase, "guess")
texts = [a.text for a in self.g.answers]
self.assertEqual(len(self.g.answers), 4) # katte, hunde, sandhed + husets løgn
shared = next(a for a in self.g.answers if a.text == "katte")
self.assertEqual(sorted(shared.authors), sorted([self.a.id, self.c.id]))
snap = self.g.snapshot(self.now, viewer=self.a)
self.assertNotIn(shared.id, [x["id"] for x in snap["answers"]])
self.assertIn(self.g.question["answer"], texts)
def test_cannot_guess_own_lie_and_scoring(self):
for p, lie in [(self.a, "katte"), (self.b, "hunde"), (self.c, "heste")]:
self.g.submit_lie(p.id, lie, self.now)
by_text = {a.text: a.id for a in self.g.answers}
with self.assertRaises(GameError):
self.g.guess(self.a.id, by_text["katte"], self.now)
self.g.guess(self.a.id, self.truth_id(), self.now)
self.g.guess(self.b.id, by_text["katte"], self.now)
self.g.guess(self.c.id, by_text["katte"], self.now)
self.assertEqual(self.g.phase, "reveal")
# A: rigtigt (1000) + narrede B og C (2 × 500)
self.assertEqual(self.a.score, 2000)
self.assertEqual(self.b.score, 0)
steps = self.g.reveal_steps
self.assertTrue(steps[-1]["is_truth"])
self.assertEqual(steps[0]["text"], "katte")
def test_like_only_in_reveal_gives_points_and_toggles(self):
for p, lie in [(self.a, "katte"), (self.b, "hunde"), (self.c, "heste")]:
self.g.submit_lie(p.id, lie, self.now)
katte = next(a.id for a in self.g.answers if a.text == "katte")
with self.assertRaises(GameError): # ikke i gættefasen
self.g.like(self.b.id, katte)
for p in (self.a, self.b, self.c):
self.g.guess(p.id, self.truth_id(), self.now)
self.assertEqual(self.g.phase, "reveal")
base = self.a.score
self.g.like(self.b.id, katte)
self.assertEqual(self.a.score, base + engine.POINTS_LIKE)
with self.assertRaises(GameError): # ikke sandheden
self.g.like(self.b.id, self.truth_id())
snap = self.g.snapshot(self.now, host=True)
self.assertEqual(next(x for x in snap["reveal"]["all"] if x["id"] == katte)["likes"], 1)
self.g.like(self.b.id, katte)
self.assertEqual(self.a.score, base)
def test_lie_for_me_gives_half_points(self):
suggestion = self.g.lie_for_me(self.a.id)
self.g.submit_lie(self.a.id, suggestion, self.now)
self.g.submit_lie(self.b.id, "hunde", self.now)
self.g.submit_lie(self.c.id, "heste", self.now)
mine = next(x.id for x in self.g.answers if self.a.id in x.authors)
hunde = next(x.id for x in self.g.answers if x.text == "hunde")
self.g.guess(self.b.id, mine, self.now)
self.g.guess(self.c.id, mine, self.now)
self.g.guess(self.a.id, hunde, self.now)
self.assertEqual(self.a.score, engine.POINTS_FOOLED * 2 // 2) # to narret, halve point
self.assertEqual(self.b.score, engine.POINTS_FOOLED) # egen løgn, fulde point
all_ = self.g.snapshot(self.now, host=True)["reveal"]["all"]
self.assertEqual(next(x for x in all_ if x["id"] == mine)["assisted"], [self.a.id])
def test_edited_suggestion_counts_as_own_lie(self):
suggestion = self.g.lie_for_me(self.a.id)
self.g.submit_lie(self.a.id, suggestion + " måske", self.now)
self.assertNotIn(self.a.id, self.g.assisted)
def test_timeouts_advance_and_fill_with_house_lies(self):
self.g.submit_lie(self.a.id, "katte", self.now)
self.assertTrue(self.g.tick(self.now + engine.LIE_SECONDS + 1))
self.assertEqual(self.g.phase, "guess")
self.assertGreaterEqual(len(self.g.answers), 4)
self.assertTrue(any(a.house for a in self.g.answers))
self.g.tick(self.now + 200)
self.assertEqual(self.g.phase, "reveal")
def test_disconnected_player_does_not_block(self):
self.g.set_connected(self.c.id, False, self.now)
self.g.submit_lie(self.a.id, "katte", self.now)
self.g.submit_lie(self.b.id, "hunde", self.now)
self.assertEqual(self.g.phase, "guess")
def test_audience_votes_give_bonus(self):
u = self.g.join("Publikum")
for p, lie in [(self.a, "katte"), (self.b, "hunde"), (self.c, "heste")]:
self.g.submit_lie(p.id, lie, self.now)
katte = next(a.id for a in self.g.answers if a.text == "katte")
self.g.guess(u.id, katte, self.now)
for p in (self.a, self.b, self.c):
self.g.guess(p.id, self.truth_id(), self.now)
self.assertEqual(self.a.score, 1000 + engine.POINTS_AUDIENCE)
class FullGameTests(GameTestCase):
def test_full_game_reaches_final_with_awards(self):
players = self.join("A", "B", "C")
self.g.start(players[0].id, self.now)
seen_phases = []
for _ in range(200):
if self.g.phase == "final":
break
seen_phases.append(self.g.phase)
if self.g.phase == "lie":
for i, p in enumerate(players):
self.g.submit_lie(p.id, f"løgn {p.name} {self.g.q_idx} {self.g.round_idx}", self.now)
elif self.g.phase == "guess":
for p in players:
if p.id not in self.g.guesses:
opts = [a for a in self.g.answers if p.id not in a.authors]
self.g.guess(p.id, opts[0].id, self.now)
else:
self.now += 100
self.g.tick(self.now)
self.assertEqual(self.g.phase, "final")
self.assertEqual(seen_phases.count("lie"), 7)
self.assertEqual(seen_phases.count("intro"), 7)
self.assertEqual(seen_phases.count("scores"), 2)
self.assertEqual(seen_phases.count("category"), 6) # finalen vælger selv
final = self.g.snapshot(self.now, host=True)["final"]
self.assertEqual(len(final["ranking"]), 3)
self.assertTrue(final["awards"])
self.assertEqual(len(self.g.used_questions), 7)
self.g.play_again(None, self.now)
self.assertEqual(self.g.phase, "category")
self.assertEqual(engine.slugify("Mad og drikke"), "mad_og_drikke")
self.assertEqual(engine.slugify("Rummet og naturen"), "rummet_og_naturen")
self.assertTrue(all(p.score == 0 for p in players))
def test_kick_chooser_picks_random_category(self):
a, b, c = self.join("A", "B", "C")
self.g.start(None, self.now)
self.g.kick(self.g.chooser_id, self.now)
self.assertEqual(self.g.phase, "intro")
self.assertEqual(len(self.g.players), 2)