diff --git a/backend/app/main.py b/backend/app/main.py index bfec216..4ad6a9b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -260,7 +260,7 @@ def drehen(): """Einer dreht, alle sehen dasselbe Ergebnis.""" laeuft = ( letzter_dreh["ergebnis"] - and time.monotonic() - letzter_dreh["start"] < suggestions.DREH_DAUER + and time.monotonic() - letzter_dreh["start"] < letzter_dreh["ergebnis"]["dauer"] ) if laeuft: return {"started": False, "reason": "dreht bereits"} @@ -283,7 +283,7 @@ def letzter(): return {"running": False, "result": None} vergangen = time.monotonic() - letzter_dreh["start"] return { - "running": vergangen < suggestions.DREH_DAUER, + "running": vergangen < ergebnis["dauer"], # Wer später dazukommt, überspringt den bereits gelaufenen Teil "elapsed": round(vergangen, 2), "result": ergebnis, diff --git a/backend/app/suggestions.py b/backend/app/suggestions.py index c5f6cac..c19a9bb 100644 --- a/backend/app/suggestions.py +++ b/backend/app/suggestions.py @@ -1,4 +1,4 @@ -"""Filmvorschläge fürs Rad. +"""Filmvorschläge für die Ziehung. Jede Sitzung darf einen Film vorschlagen. Die Sitzung erkennt ein Cookie — ohne Anmeldung geht es nicht genauer, und für einen Filmabend im Freundeskreis @@ -37,7 +37,19 @@ COOKIE = "filmabend_sid" # So lange läuft die Animation im Browser. Der Server merkt sich das # Ergebnis mit, damit eine Seite, die mitten im Dreh geladen wird, es # trotzdem bekommt. -DREH_DAUER = 4.2 +DREH_DAUER = 11.2 + +# Die Ziehung läuft wie das Öffnen einer Lootbox: ein Streifen aus +# Postern rast vorbei und bremst auf dem Gewinner aus. Der Streifen ist +# so lang, und an dieser Stelle steht der Treffer. +STREIFEN_LAENGE = 64 +STREIFEN_ZIEL = 56 + +# Manchmal bricht nach dem Halt die Halterung: die Bahn rutscht ein paar +# Poster zurück, und ein anderer gewinnt. So lange dauert das im Browser +# zusätzlich (Ruhe plus Rückfahrt — die Werte stehen auch in WheelTab.vue). +BRUCH_DAUER = 3.7 +BRUCH_ANTEIL = 0.5 def init(): @@ -112,11 +124,19 @@ def ersetzen(session_id, movie, name=None): return anlegen(session_id, movie, name) +def _versatz(): + """Wo genau im Poster die Marke stehen bleibt — nie exakt mittig. + Jedes zweite Mal ganz knapp an der Kante: fast der andere.""" + if random.random() < 0.5: + return round(random.choice((-1, 1)) * random.uniform(0.34, 0.46), 3) + return round(random.uniform(-0.3, 0.3), 3) + + def ziehen(): """Server lost aus, damit alle Seiten dasselbe Ergebnis sehen. - Der Zielwinkel gehört dazu: sonst dreht jede Seite woanders hin und - der Zeiger stünde am Ende auf einem anderen Segment. + Der Streifen gehört dazu: sonst zeigt jede Seite andere Poster + vorbeirauschen, und der Treffer stünde am Ende woanders. """ eintraege = alle() if not eintraege: @@ -130,26 +150,38 @@ def ziehen(): } frisch = [i for i, e in enumerate(eintraege) if e["movie_id"] not in gesehen] treffer = random.choice(frisch) if frisch else random.randrange(len(eintraege)) - schritt = 360 / len(eintraege) - runden = 5 + random.randrange(3) - # Der Zeiger steht oben, also muss die Mitte des Zielsegments dorthin. - # Ein einzelner Vorschlag ist ein Vollkreis mit Cover — der soll nach - # dem Dreh aufrecht stehen, also bei 0° enden statt bei 180°. - if len(eintraege) == 1: - winkel = runden * 360 - else: - winkel = runden * 360 + (360 - (treffer * schritt + schritt / 2)) + + # Blockweise gemischt: jeder Vorschlag kommt gleich oft vorbei + streifen = [] + while len(streifen) < STREIFEN_LAENGE: + block = list(range(len(eintraege))) + random.shuffle(block) + streifen.extend(block) + streifen = streifen[:STREIFEN_LAENGE] + + # Wo die Bahn hält — beim Bruch zweimal, und erst der zweite Halt zählt + stopps = [{"position": STREIFEN_ZIEL, "versatz": _versatz()}] + ziel = STREIFEN_ZIEL + if random.random() < BRUCH_ANTEIL: + ziel = STREIFEN_ZIEL - random.randint(1, 4) + stopps.append({"position": ziel, "versatz": _versatz()}) + # Der erste Halt zeigt einen anderen — sonst wäre der Bruch egal + andere = [i for i in range(len(eintraege)) if i != treffer] + streifen[STREIFEN_ZIEL] = random.choice(andere) if andere else treffer + streifen[ziel] = treffer + + def karte(e): + return {k: v for k, v in e.items() if k not in ("session_id", "user_id")} return { "index": treffer, - "winkel": winkel, - "dauer": DREH_DAUER, - "gewinner": { - k: v - for k, v in eintraege[treffer].items() - if k not in ("session_id", "user_id") - }, - "anzahl": len(eintraege), + "streifen": streifen, + "stopps": stopps, + "dauer": DREH_DAUER + (BRUCH_DAUER if len(stopps) > 1 else 0), + # Der Streifen zeigt diesen Stand, auch wenn danach jemand einen + # Vorschlag dazutut + "karten": [karte(e) for e in eintraege], + "gewinner": karte(eintraege[treffer]), } diff --git a/frontend/src/components/WheelTab.vue b/frontend/src/components/WheelTab.vue index 135df1a..c7357f0 100644 --- a/frontend/src/components/WheelTab.vue +++ b/frontend/src/components/WheelTab.vue @@ -1,5 +1,5 @@