diff --git a/pony-frontend/src/App.css b/pony-frontend/src/App.css index 66c0422..f0d38a8 100644 --- a/pony-frontend/src/App.css +++ b/pony-frontend/src/App.css @@ -1795,3 +1795,66 @@ html.tablet-focus-mode #root { cursor: pointer; margin-bottom: 0.5rem; } + + +/* Stats page — mobile responsive */ +@media (max-width: 768px) { + .stats-page { + padding: 0.75rem; + } + .stats-page h2 { + font-size: 1.4rem; + } + .stats-page h3 { + font-size: 1.1rem; + margin-top: 0.75rem; + } + .stats-overview { + gap: 0.5rem; + } + .stat-item { + padding: 0.4rem 0.75rem; + font-size: 0.9rem; + } + .stats-list li { + padding: 0.4rem 0.5rem; + font-size: 0.85rem; + } + .back-button { + min-height: 44px; + min-width: 44px; + padding: 0.5rem 0.75rem; + font-size: 1rem; + } +} + +@media (max-width: 480px) { + .stats-page { + padding: 0.5rem; + } + .stats-page h2 { + font-size: 1.2rem; + } + .stats-page h3 { + font-size: 1rem; + margin-top: 0.6rem; + } + .stats-overview { + flex-direction: column; + align-items: stretch; + gap: 0.4rem; + } + .stat-item { + text-align: center; + padding: 0.5rem; + } + .stats-list li { + padding: 0.35rem 0.4rem; + font-size: 0.8rem; + } + .back-button { + width: 100%; + text-align: center; + padding: 0.6rem; + } +} diff --git a/web/app/services/persistence.py b/web/app/services/persistence.py index 6db1365..96e688d 100644 --- a/web/app/services/persistence.py +++ b/web/app/services/persistence.py @@ -44,6 +44,7 @@ def _ensure_tables(conn): played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) + conn.execute("CREATE INDEX IF NOT EXISTS idx_game_stats_played_at ON game_stats(played_at)") conn.commit() diff --git a/web/evaluate_game.py b/web/evaluate_game.py index e8e1601..2f3450c 100644 --- a/web/evaluate_game.py +++ b/web/evaluate_game.py @@ -194,6 +194,54 @@ def evaluate_exhaustive(): return all_runs +def compute_per_theme_stats(runs): + """Compute win-rate and stats per theme. Returns a list of dicts.""" + themes = {} + for r in runs: + t = r["tema"] + if t not in themes: + themes[t] = {"wins": 0, "mixed": 0, "defeats": 0, "count": 0, + "succeser": [], "fiaskoer": []} + themes[t]["count"] += 1 + themes[t]["succeser"].append(r["succeser"]) + themes[t]["fiaskoer"].append(r["fiaskoer"]) + if r.get("victory"): + themes[t]["wins"] += 1 + elif r.get("mixed"): + themes[t]["mixed"] += 1 + elif r.get("defeat"): + themes[t]["defeats"] += 1 + + per_theme = [] + for t, d in themes.items(): + per_theme.append({ + "tema": t, + "count": d["count"], + "avg_succeser": round(statistics.mean(d["succeser"]), 2), + "avg_fiaskoer": round(statistics.mean(d["fiaskoer"]), 2), + "win_rate": round(d["wins"] / d["count"] * 100, 1) if d["count"] else 0, + "mixed_rate": round(d["mixed"] / d["count"] * 100, 1) if d["count"] else 0, + "defeat_rate": round(d["defeats"] / d["count"] * 100, 1) if d["count"] else 0, + }) + return per_theme + + +def export_json(runs, output_path): + """Write per-theme win-rate JSON to a file.""" + per_theme = compute_per_theme_stats(runs) + total = len(runs) + victory_count = sum(1 for r in runs if r.get("victory")) + json_output = { + "total": total, + "victory_count": victory_count, + "win_rate": round(victory_count / total * 100, 1) if total else 0, + "per_theme": per_theme, + } + with open(output_path, "w", encoding="utf-8") as f: + json.dump(json_output, f, indent=2, ensure_ascii=False) + print(f"JSON exported to {output_path}") + + def report(runs): total = len(runs) @@ -358,26 +406,26 @@ def report_exhaustive(runs): print(json.dumps(json_output, indent=2)) -def evaluate(): - all_runs = [] - - print(f"Kører {NUM_RUNS} spil-gennemgange...") - for i in range(NUM_RUNS): - try: - all_runs.append(play_one_game()) - except Exception as e: - print(f" Fejl i løb {i}: {e}") - - print(f"Klar! {len(all_runs)} gennemgange færdige.\n") - report(all_runs) - - if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--exhaustive", action="store_true", help="Run all pony×theme combinations") + parser.add_argument("--output", type=str, default=None, help="Path to write per-theme win-rate JSON") args = parser.parse_args() if args.exhaustive: runs = evaluate_exhaustive() report_exhaustive(runs) else: - evaluate() \ No newline at end of file + runs = [] + print(f"Kører {NUM_RUNS} spil-gennemgange...") + for i in range(NUM_RUNS): + try: + runs.append(play_one_game()) + except Exception as e: + print(f" Fejl i løb {i}: {e}") + print(f"Klar! {len(runs)} gennemgange færdige.\n") + report(runs) + if args.output: + if not runs: + print("Warning: no runs collected for JSON export.") + else: + export_json(runs, args.output) \ No newline at end of file