From 8440be6f84152370267cfd1c2f4615378d3e5713 Mon Sep 17 00:00:00 2001 From: Asger Geel Weirsoee Date: Tue, 20 Oct 2026 21:43:24 +0200 Subject: [PATCH] =?UTF-8?q?Skift=20spil=20i=20samme=20rum:=20"V=C3=A6lg=20?= =?UTF-8?q?andet=20spil"=20efter=20podiet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Podiet har nu "Spil igen" og "Vælg andet spil". Det sidste går til menuen med rummets kode (/host/?code=ABCD); menuen viser rummet og hvor mange der venter, og Start sender koden med. Sømmen ligger i rooms.add_room: får et nyt rum en kode, der allerede kører, overtager det nye rum spillerne og forbindelserne (BaseRoom.take_over), og det gamle lukkes (timere og stemmekø). Spillerne joines i det nye spil i den rækkefølge de kom, med samme navn og token; dem over det nye spils max bliver publikum. Hver forbindelses room og member_id peges om, så ingen consumer hænger fast i det gamle rum, og telefonerne får det nye spils lobby med det samme uden at joine igen. En telefon, der genindlæser, kommer ind med sit gamle token. Test (WebSocket): et helt Fup og Fakta-spil til podiet, skift til Tegn og Gæt i samme rum, og et helt Tegn og Gæt-spil på de samme forbindelser; og 10 spillere til Fup og Fakta (8 spiller, resten er publikum). Co-Authored-By: Claude Opus 5.5 --- party/rooms.py | 29 +++++ party/static/party/host.js | 3 +- party/static/party/menu.js | 15 ++- party/static/party/party.css | 4 + party/tegn.py | 1 + party/templates/party/host_new.html | 6 +- party/tests/test_switch.py | 183 ++++++++++++++++++++++++++++ party/views.py | 12 +- 8 files changed, 242 insertions(+), 11 deletions(-) create mode 100644 party/tests/test_switch.py diff --git a/party/rooms.py b/party/rooms.py index c27ebfc..eb6ac07 100644 --- a/party/rooms.py +++ b/party/rooms.py @@ -158,6 +158,28 @@ class BaseRoom: if self._voice_task: self._voice_task.cancel() + def take_over(self, old: "BaseRoom"): + """Skift af spil i samme rum: spillerne kommer med i det nye spils lobby med samme navn og token, så + telefonerne følger med uden at joine igen. Er der flere end det nye spils max, bliver resten publikum + (spillerne først, i den rækkefølge de kom). Forbindelserne flyttes, så ingen peger på det gamle rum.""" + g, ids = self.game, {} + members = sorted(old.game.players.values(), key=lambda p: p.joined_order) + \ + sorted(old.game.audience.values(), key=lambda p: p.joined_order) + for m in members: + try: + p = g.join(m.name) + except engine.GameError: # navnet duer ikke i det nye spil: telefonen må joine igen + log.warning("Rum %s: %s kom ikke med i det nye spil", self.code, m.name) + continue + p.token, p.connected = m.token, m.connected + ids[m.id] = p.id + g.drain_events() # ingen velkomst til hver enkelt: de var her i forvejen + for conn in old.connections: + conn.room = self + if getattr(conn, "member_id", None): + conn.member_id = ids.get(conn.member_id, "-") # "-": findes ikke, så telefonen beder om at joine + self.connections, old.connections = old.connections, set() + def handle_events(self): self.game.drain_events() @@ -732,6 +754,7 @@ def new_code() -> str: def create_room(questions: list[dict], host_username: str, public_url: str, fest: bool = False, code: str | None = None) -> Room: + """code: et kørende rums kode = skift spil i det rum (se add_room).""" cleanup() code = code or new_code() game = Game(code, questions, join_url=f"{public_url}/?code={code}", fest=fest) @@ -742,7 +765,13 @@ def create_room(questions: list[dict], host_username: str, public_url: str, fest def add_room(room: BaseRoom): + """Læg rummet i kodelisten. Kører der allerede et rum med koden, er det et skift af spil: det nye rum + overtager spillerne og forbindelserne, og det gamle lukkes.""" + old = _rooms.get(room.code) _rooms[room.code] = room + if old is not None and old is not room: + room.take_over(old) + old.close() def get_room(code: str) -> Room | None: diff --git a/party/static/party/host.js b/party/static/party/host.js index dab240b..9494f9a 100644 --- a/party/static/party/host.js +++ b/party/static/party/host.js @@ -414,7 +414,8 @@ '
' + (aw ? '

Priser

    ' + aw + '
' : '') + (rest ? '
    ' + rest + '
' : '') + - '
'; + '
' + + 'Vælg andet spil
'; } /* ---------- effekter: et lag over skærmen, som patch ikke rører ---------- */ diff --git a/party/static/party/menu.js b/party/static/party/menu.js index 237f173..5d1e8bf 100644 --- a/party/static/party/menu.js +++ b/party/static/party/menu.js @@ -11,8 +11,10 @@ return '👥 ' + players + ' spillere⏱ ca. ' + esc(g.minutes) + ' min'; } - function menuScreen(games) { - return '

Weirsøe Party Protocol

Vælg et spil

' + + function menuScreen(games, sw) { + var sub = sw ? 'Rum ' + esc(sw.code) + ': ' + (sw.players === 1 ? 'én venter' : sw.players + ' venter') + '. Vælg næste spil' + : 'Vælg et spil'; + return '

Weirsøe Party Protocol

' + sub + '

' + '
' + games.map(function (g, i) { return '
'; } - function introScreen(g, values) { + function introScreen(g, values, sw) { var sets = (g.settings || []).map(function (s) { var cur = values[s.name] != null ? values[s.name] : s.default; var hint = s.options.filter(function (o) { return o.value === cur; })[0]; @@ -37,19 +39,20 @@ '
' + '

' + WPP.brandHtml(g.name) + '

' + esc(g.tagline) + '

' + '

' + facts(g) + '

' + + (sw && sw.players > g.max_players ? '

' + g.max_players + ' kan spille med, resten er publikum.

' : '') + '
    ' + g.steps.map(function (s, i) { return '
  1. ' + esc(s) + '
  2. '; }).join('') + '
' + (sets ? '
' + sets + '
' : '') + '
' + '
'; } - /* view = {screen: 'menu'} eller {screen: 'intro', key, values} */ + /* view = {screen: 'menu'} eller {screen: 'intro', key, values}; view.sw = {code, players} ved skift af spil */ function render(root, view) { var games = WPP.games, g = view.screen === 'intro' && WPP.game(view.key); var sh = WPP.shell(root); sh.screen.className = 'screen mn mn-' + (g ? 'intro' : 'menu'); WPP.patch(sh.screen, '
' + - (g ? introScreen(g, view.values || {}) : menuScreen(games)) + '
'); + (g ? introScreen(g, view.values || {}, view.sw) : menuScreen(games, view.sw)) + ''); } /* pilene: nærmeste valgbare element i den retning (målt fra midten), så både kort i et gitter og knapper @@ -78,6 +81,7 @@ function mount(root) { var form = document.getElementById('mn-form'); + var sw = root.getAttribute('data-code') ? { code: root.getAttribute('data-code'), players: Number(root.getAttribute('data-players')) || 0 } : null; var view = { screen: 'menu' }; function focusFirst(sel) { @@ -95,6 +99,7 @@ function show(next, push) { var from = view; view = next; + view.sw = sw; render(root, view); if (view.screen === 'intro') { if (push) try { history.pushState({ intro: view.key }, '', '#' + view.key); } catch (e) { /* */ } diff --git a/party/static/party/party.css b/party/static/party/party.css index c53808e..5e3f2f9 100644 --- a/party/static/party/party.css +++ b/party/static/party/party.css @@ -500,6 +500,9 @@ button, input { font-family: inherit; } .rest .rk { margin-right: .3em; } .rest b { margin-left: .4em; color: #fff; font-weight: 600; } .fin-side .btn { align-self: flex-start; margin-top: .3em; } +.fin-again { display: flex; gap: .6em; flex-wrap: wrap; align-items: center; } +.fin-again .btn { margin-top: .3em; } +.fin-other { background: var(--card); color: var(--ink); box-shadow: inset 0 0 0 2px var(--card-b); text-decoration: none; font-size: 1.2em; } /* host_new */ .landing { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 24px; text-align: center; padding: 24px 16px; } @@ -1044,3 +1047,4 @@ button, input { font-family: inherit; } @keyframes mn-in { from { opacity: 0; transform: translateY(14px); } } @media (max-width: 640px) { .mn-who { position: static; padding-bottom: 12px; } .mn-intro-top { flex-direction: column; text-align: center; } .mn-intro .mn-tag { text-align: center; } .mn-intro .mn-facts { justify-content: center; } } @media (prefers-reduced-motion: reduce) { .mn-card, .mn-intro, .mn-steps li { animation: none; } } +.mn-note { margin: 0; padding: 10px 18px; border-radius: 14px; background: rgba(255, 210, 63, .14); color: var(--yellow); font-weight: 600; } diff --git a/party/tegn.py b/party/tegn.py index a6807d0..6ec4536 100644 --- a/party/tegn.py +++ b/party/tegn.py @@ -271,6 +271,7 @@ def warm_texts(themes: list[dict] | None = None) -> list[str]: def create_tegn_room(host_username: str, public_url: str, code: str | None = None, show_rules: bool = True) -> TegnRoom: + """code: et kørende rums kode = skift spil i det rum (se rooms.add_room).""" rooms.cleanup() code = code or rooms.new_code() game = Game(code, load_themes(), join_url=f"{public_url}/?code={code}") diff --git a/party/templates/party/host_new.html b/party/templates/party/host_new.html index c65c6b8..1ded7ff 100644 --- a/party/templates/party/host_new.html +++ b/party/templates/party/host_new.html @@ -2,14 +2,14 @@ {% load static %} {% block body_class %}page-menu{% endblock %} {% block content %} - + {# menu.js sender denne formular med spillet og dets indstillinger #} - +