CI / test (push) Successful in 1m4s
- Runde 2 og finalen: speakeren læser svarene op ét ad gangen (med hjernen en overbevisende kommentar pr. svar), i samme takt på storskærm og telefoner - Emoji-reaktioner fra telefonerne, der må spammes; de flyver op på storskærmen og tælles på svaret i fokus - Opsummeringen følger speakeren: "lad os rydde op", eksplosioner, så pointene - Speakeren kommenterer, hvordan løgnene skrider frem, uden at røbe indhold, og driller dem, der falder for svage løgne - Reglerne forklares med kort og stemme før rummets første spil - "Lyv for mig" låser forslaget i feltet (halve point), og feltet bliver ikke længere kæmpestort - Svar vises med versaler, og spillernes løgne gemmes med små bogstaver, så store forbogstaver ikke afslører dem - Løgne og navne renses: kun bogstaver, tal og lidt tegnsætning - Valgfrit workspace-id til Anthropic-brugernøgler Claude-Session: https://claude.ai/code/session_01JmhJG6JnhXfzNiKv8f2vHu
177 lines
6.2 KiB
Python
177 lines
6.2 KiB
Python
"""WebSocket-forbindelser for storskærm (vært) og telefoner (spillere/publikum)."""
|
|
|
|
import logging
|
|
import time
|
|
|
|
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
|
|
|
from fupogfakta.engine import GameError
|
|
|
|
from . import stt
|
|
from .auth import scope_identity
|
|
from .rooms import get_room
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class _RoomConsumer(AsyncJsonWebsocketConsumer):
|
|
room = None
|
|
|
|
async def connect(self):
|
|
self.room = get_room(self.scope["url_route"]["kwargs"]["code"])
|
|
if not self.authorized():
|
|
await self.close(code=4403)
|
|
return
|
|
await self.accept()
|
|
if not self.room:
|
|
await self.send_error("unknown_room", "Det spil findes ikke. Tjek koden.")
|
|
await self.close(code=4404)
|
|
return
|
|
self.room.connections.add(self)
|
|
self.room.touch()
|
|
await self.on_open()
|
|
|
|
def authorized(self) -> bool:
|
|
return True
|
|
|
|
async def on_open(self):
|
|
pass
|
|
|
|
async def disconnect(self, code):
|
|
if self.room:
|
|
self.room.connections.discard(self)
|
|
await self.on_close()
|
|
|
|
async def on_close(self):
|
|
pass
|
|
|
|
async def send_error(self, code, message):
|
|
await self.send_json({"type": "error", "code": code, "message": message})
|
|
|
|
async def receive_json(self, content, **kwargs):
|
|
if not self.room or not isinstance(content, dict):
|
|
return
|
|
action = content.get("action")
|
|
if action == "ping":
|
|
await self.send_json({"type": "pong"})
|
|
return
|
|
version = self.room.game.version
|
|
try:
|
|
await self.handle(action, content, time.time())
|
|
except GameError as e:
|
|
await self.send_error(e.code, e.message)
|
|
except (TypeError, ValueError, KeyError):
|
|
await self.send_error("bad_request", "Ugyldig besked.")
|
|
if self.room.game.version != version:
|
|
self.room.changed()
|
|
else:
|
|
self.room.handle_events() # fx "det var sandheden", der afvises men kommenteres
|
|
|
|
async def handle(self, action, content, now):
|
|
raise NotImplementedError
|
|
|
|
async def send_state(self):
|
|
raise NotImplementedError
|
|
|
|
|
|
class HostConsumer(_RoomConsumer):
|
|
is_host = True
|
|
|
|
def authorized(self):
|
|
return scope_identity(self.scope) is not None
|
|
|
|
async def on_open(self):
|
|
self.room.host_opened()
|
|
await self.send_state()
|
|
|
|
async def send_state(self):
|
|
state = self.room.game.snapshot(time.time(), host=True)
|
|
state["narration"] = self.room.narration
|
|
state["phase_key"] = self.room.game.phase_key()
|
|
state["mic"] = {"on": self.room.mic_on(), "laughs": self.room.mic_laughs, "available": stt.enabled()}
|
|
await self.send_json({"type": "state", "state": state})
|
|
|
|
async def handle(self, action, content, now):
|
|
g = self.room.game
|
|
if action in ("start", "skip", "play_again"):
|
|
self.room.cut_speech() # værtens knap vinder over speakeren
|
|
if action == "start":
|
|
g.start(None, now)
|
|
elif action == "skip":
|
|
g.skip(now)
|
|
elif action == "play_again":
|
|
g.play_again(None, now)
|
|
elif action == "kick":
|
|
g.kick(str(content["player_id"]), now)
|
|
else:
|
|
await self.send_error("bad_request", "Ukendt handling.")
|
|
|
|
|
|
class PlayConsumer(_RoomConsumer):
|
|
member_id = None
|
|
|
|
async def on_close(self):
|
|
if self.member_id:
|
|
# Er spilleren stadig forbundet fra en anden fane/telefon, er de ikke væk.
|
|
still = any(getattr(c, "member_id", None) == self.member_id for c in self.room.connections)
|
|
if not still:
|
|
version = self.room.game.version
|
|
self.room.game.set_connected(self.member_id, False, time.time())
|
|
if self.room.game.version != version:
|
|
self.room.changed()
|
|
|
|
def _member(self):
|
|
g = self.room.game
|
|
return g.players.get(self.member_id) or g.audience.get(self.member_id)
|
|
|
|
async def send_state(self):
|
|
if not self.member_id:
|
|
return
|
|
member = self._member()
|
|
if not member: # smidt ud af værten
|
|
self.member_id = None
|
|
await self.send_error("unknown_token", "Du er ikke længere med i spillet.")
|
|
return
|
|
await self.send_json({"type": "state", "state": self.room.game.snapshot(time.time(), viewer=member)})
|
|
|
|
async def _attach(self, member, now):
|
|
self.member_id = member.id
|
|
self.room.game.set_connected(member.id, True, now)
|
|
await self.send_json({"type": "joined", "token": member.token, "player_id": member.id, "role": member.role})
|
|
await self.send_state()
|
|
|
|
async def handle(self, action, content, now):
|
|
g = self.room.game
|
|
if action == "join":
|
|
await self._attach(g.join(str(content.get("name", ""))), now)
|
|
return
|
|
if action == "resume":
|
|
await self._attach(g.resume(str(content.get("token", ""))), now)
|
|
return
|
|
if not self.member_id or not self._member():
|
|
raise GameError("unknown_token", "Join spillet først.")
|
|
mid = self.member_id
|
|
if action in ("start", "play_again") and mid == g.vip_id():
|
|
self.room.cut_speech()
|
|
if action == "start":
|
|
g.start(mid, now)
|
|
elif action == "choose_category":
|
|
g.choose_category(mid, int(content["category_id"]), now)
|
|
elif action == "submit_lie":
|
|
self.room.prepare_judging()
|
|
g.submit_lie(mid, str(content.get("text", "")), now)
|
|
elif action == "lie_for_me":
|
|
await self.send_json({"type": "suggestion", "text": g.lie_for_me(mid)})
|
|
elif action == "guess":
|
|
g.guess(mid, str(content["answer_id"]), now)
|
|
elif action == "like":
|
|
g.like(mid, str(content["answer_id"]))
|
|
elif action == "react":
|
|
reaction = g.react(mid, str(content.get("emoji", "")), now)
|
|
if reaction:
|
|
await self.room.forward_reaction(reaction)
|
|
elif action == "play_again":
|
|
g.play_again(mid, now)
|
|
else:
|
|
await self.send_error("bad_request", "Ukendt handling.")
|