import hashlib import random from datetime import date, datetime, time, timedelta from . import db from .models import Movie, Player, Session, Universe # Statusklassen fuer die Zellen GREEN = "green" YELLOW = "yellow" RED = "red" def get_max_attempts(): from flask import current_app return current_app.config.get("MAX_ATTEMPTS", 10) def _game_date(today=None): """Ermittelt den 'Spieltag' basierend auf der globalen Reset-Uhrzeit (UTC).""" from flask import current_app reset_hour = current_app.config.get("DAILY_RESET_UTC", 9) now = today or datetime.utcnow() game_date = now.date() # Vor der Resetzeit gilt noch der Vortag if now.time() < time(reset_hour % 24, 0, 0): game_date = game_date - timedelta(days=1) return game_date def daily_target(game_date=None): """Bestimmt deterministisch den taeglichen Ziel-Film (fuer alle gleich).""" game_date = game_date or _game_date() movies = Movie.query.order_by(Movie.id).all() if not movies: return None idx = int(hashlib.sha256(str(game_date).encode()).hexdigest(), 16) % len(movies) return movies[idx] def random_target(): movies = Movie.query.order_by(Movie.id).all() if not movies: return None return random.choice(movies) def get_or_create_player(nickname): player = Player.query.filter_by(nickname=nickname).first() if player is None: player = Player(nickname=nickname) db.session.add(player) db.session.commit() return player def _series(normalized_title): """Erstellt eine Normalisierung fuer Titel-Vergleiche (nur Buchstaben, klein).""" return "".join(ch for ch in normalized_title.lower() if ch.isalnum()) def match_platforms(guess_movie, target_movie): guess = set(p.name for p in guess_movie.platforms) target = set(p.name for p in target_movie.platforms) if guess == target: return GREEN # gelb: mindestens eine gemeinsame Platform if guess & target: return YELLOW return RED def match_genres(guess_movie, target_movie): guess = set(g.name for g in guess_movie.genres) target = set(g.name for g in target_movie.genres) if guess == target: return GREEN if guess & target: return YELLOW return RED def match_year(guess_year, target_year): if guess_year == target_year: return GREEN, "equal" if guess_year < target_year: return YELLOW, "up" # Ziel liegt hoeher (spaeter) return YELLOW, "down" # Ziel liegt tiefer (frueher) def match_fsk(guess_fsk, target_fsk): if guess_fsk == target_fsk: return GREEN if guess_fsk is None or target_fsk is None: return YELLOW if guess_fsk == target_fsk else RED return RED def match_director(guess_director, target_director): if not guess_director or not target_director: return GREEN if guess_director == target_director else RED if _series(guess_director) == _series(target_director): return GREEN return RED def match_studio(guess_studio, target_studio): if (guess_studio or "").lower() == (target_studio or "").lower(): return GREEN return RED def match_budget(guess_budget, target_budget): if guess_budget is None or target_budget is None: return RED if guess_budget != target_budget else GREEN if guess_budget == target_budget: return GREEN, "equal" if guess_budget < target_budget: return YELLOW, "up" return YELLOW, "down" def match_universe(guess_universe, target_universe): # Fuer beide None => green (kein Universum) if (guess_universe or "") == (target_universe or ""): return GREEN if not guess_universe or not target_universe: return RED return RED def compare(guess_movie, target_movie): """Vergleicht einen Tipp mit dem Ziel-Film und liefert die 9 Zellen.""" ys, ydir = match_year(guess_movie.year, target_movie.year) bs, bdir = match_budget(guess_movie.budget, target_movie.budget) return { "title": guess_movie.title, "platforms": {"status": match_platforms(guess_movie, target_movie), "value": sorted(p.name for p in guess_movie.platforms)}, "genres": {"status": match_genres(guess_movie, target_movie), "value": sorted(g.name for g in guess_movie.genres)}, "year": {"status": ys, "value": guess_movie.year, "direction": ydir, "diff": target_movie.year - guess_movie.year}, "fsk": {"status": match_fsk(guess_movie.fsk, target_movie.fsk), "value": guess_movie.fsk}, "studio": {"status": match_studio(guess_movie.studio.name if guess_movie.studio else None, target_movie.studio.name if target_movie.studio else None), "value": guess_movie.studio.name if guess_movie.studio else None}, "director": {"status": match_director(guess_movie.director, target_movie.director), "value": guess_movie.director or None}, "budget": {"status": bs, "value": guess_movie.budget, "direction": bdir, "diff": (target_movie.budget - guess_movie.budget) if guess_movie.budget is not None and target_movie.budget is not None else None}, "universe": {"status": match_universe(guess_movie.universe.name if guess_movie.universe else None, target_movie.universe.name if target_movie.universe else None), "value": guess_movie.universe.name if guess_movie.universe else None}, } def is_correct(compared): """Prueft ob alle Zellen gruen sind (== Ziel gefunden).""" return all(cell["status"] == GREEN for cell in compared.values() if cell is not compared["title"]) def finish_session(session, won): """Markiert eine Runde als gewonnen/verloren und aktualisiert den Str eak.""" session.status = "won" if won else "lost" session.won = won db.session.commit() def _streak_for(player, mode, won): """Berechnet den aktuellen Str eak (Sieg bzw. Niederlage) fuer Spieler+Modus.""" sessions = ( Session.query .filter_by(player_id=player.id, mode=mode) .order_by(Session.id.desc()) .all() ) count = 0 for s in sessions: if s.won is None: continue if s.won == won: count += 1 else: break return count def get_streaks(player_id): """Liefert getrennte Sieg-/Niederlagen-Str eaks fuer Tages- und Unbegrenzt-Modus.""" player = Player.query.get(player_id) if player is None: return None return { "daily": { "win": _streak_for(player, "daily", True), "loss": _streak_for(player, "daily", False), }, "unlimited": { "win": _streak_for(player, "unlimited", True), "loss": _streak_for(player, "unlimited", False), }, } def existing_daily_session(player_id, game_date): """Gibt eine vorhandene, abgeschlossene Tages-Runde zurueck (falls vorhanden).""" return Session.query.filter_by( player_id=player_id, mode="daily", date=game_date ).order_by(Session.id.desc()).first()