#!/usr/bin/env python3 """ Evaluere MLP Pony-spillet som en 4-årig. Simulerer spil-gennemgange og producerer rapporter med: - gennemsnitlig score per tema - succesrate per scene - sværeste/letteste scener - pony-type balance - generel balance-vurdering Brug: cd /home/alex/pony/web python3 evaluate_game.py # 100 tilfældige spil python3 evaluate_game.py --exhaustive # alle 32 pony×tema kombinationer """ import argparse import json import os import random import statistics import sys sys.path.insert(0, os.path.dirname(__file__)) from app.data.themes import THEMAER from app.game.pony import PONITYPER, PONYNAMNE from app.game.dice import roll_d6, DIFFICULTY_PENALTY from app.game.state import create_game from app.services.game_service import roll_scene, resolve_scene_interaction, format_scene_data NUM_RUNS = 100 RNG = random.Random(42) # reproducibelt def pick_pony(): return RNG.randint(0, len(PONITYPER) - 1) def pick_theme(): return RNG.randint(0, len(THEMAER) - 1) def play_one_game(): """Play a full game from start to finish. Return a report dict.""" pony_idx = pick_pony() tema_idx = pick_theme() game = create_game(pony_idx, tema_idx) pony_type = PONITYPER[pony_idx] tema = THEMAER[tema_idx] scenes = tema.get("scener", []) results = [] while not game.get("færdig"): scn_idx = game["scene"] if scn_idx >= len(scenes): game["færdig"] = True break scn = scenes[scn_idx] interaction = scn.get("interaction", {"type": "dice"}) if interaction["type"] == "dice": game = roll_scene(game) else: options = interaction.get("options", []) if not options: game = roll_scene(game) else: target = interaction.get("target") if interaction["type"] in ("color", "number", "memory") and target: target_opt = next((o for o in options if o.get("id") == target), None) selection = target_opt["id"] if target_opt else RNG.choice([o["id"] for o in options]) elif interaction["type"] == "choice": selection = RNG.choice([o["id"] for o in options]) else: selection = RNG.choice([o["id"] for o in options]) game, _ = resolve_scene_interaction(game, selection) if game["historie"]: results.append(game["historie"][-1]) fmt = format_scene_data(game) return { "pony_type": pony_type["navn"], "pony_idx": pony_idx, "tema": tema["titel"], "tema_idx": tema_idx, "stats": { "krop": game["pony"]["krop"], "sind": game["pony"]["sind"], "charme": game["pony"]["charme"], "talent": game["pony"]["talent"], }, "succeser": game["succeser"], "fiaskoer": game["fiaskoer"], "total_scenes": len(scenes), "victory": fmt.get("victory"), "mixed": fmt.get("mixed"), "defeat": fmt.get("defeat"), "results": results, } def play_game_deterministic(pony_idx, tema_idx): """Play a full game with deterministic correct choices. Return report dict.""" game = create_game(pony_idx, tema_idx) tema = THEMAER[tema_idx] scenes = tema.get("scener", []) results = [] while not game.get("færdig"): scn_idx = game["scene"] if scn_idx >= len(scenes): game["færdig"] = True break scn = scenes[scn_idx] interaction = scn.get("interaction", {"type": "dice"}) if interaction["type"] == "dice": game = roll_scene(game) else: options = interaction.get("options", []) if not options: game = roll_scene(game) continue # For color/number/memory: pick the correct target target = interaction.get("target") if target and interaction["type"] in ("color", "number", "memory"): target_opt = next((o for o in options if o.get("id") == target), None) selection = target_opt["id"] if target_opt else options[0]["id"] elif interaction["type"] == "choice": selection = options[0]["id"] # any choice works else: selection = options[0]["id"] game, _ = resolve_scene_interaction(game, selection) if game["historie"]: results.append(game["historie"][-1]) fmt = format_scene_data(game) return { "pony_type": PONITYPER[pony_idx]["navn"], "pony_idx": pony_idx, "tema": tema["titel"], "tema_id": tema["id"], "tema_idx": tema_idx, "stats": { "krop": game["pony"]["krop"], "sind": game["pony"]["sind"], "charme": game["pony"]["charme"], "talent": game["pony"]["talent"], }, "succeser": game["succeser"], "fiaskoer": game["fiaskoer"], "total_scenes": len(scenes), "victory": fmt.get("victory"), "mixed": fmt.get("mixed"), "defeat": fmt.get("defeat"), "results": results, "scene_results": [ { "scene": i, "action": scn["aktion"], "stat": scn["stat"], "difficulty": scn["svaer"], "success": res.get("succes", False) if res else None, } for i, (scn, res) in enumerate(zip(scenes, results)) ], } def evaluate_exhaustive(): """Run every pony x theme combination once.""" all_runs = [] total = len(PONITYPER) * len(THEMAER) print(f"Kører {len(PONITYPER)} x {len(THEMAER)} = {total} kombinationer...") for pony_idx in range(len(PONITYPER)): for tema_idx in range(len(THEMAER)): try: run = play_game_deterministic(pony_idx, tema_idx) all_runs.append(run) except Exception as e: print(f" Fejl: pony {pony_idx} x tema {tema_idx}: {e}") return all_runs def report(runs): total = len(runs) avg_succeser = statistics.mean([r["succeser"] for r in runs]) avg_fiaskoer = statistics.mean([r["fiaskoer"] for r in runs]) victory_count = sum(1 for r in runs if r.get("victory")) mixed_count = sum(1 for r in runs if r.get("mixed")) defeat_count = sum(1 for r in runs if r.get("defeat")) print("=" * 60) print(" EVALUERINGSRAPPORT — My Little Pony: Tails of Equestria") print("=" * 60) print(f"\nSamlet: {total} spil") print(f" Gennemsnitlige succeser: {avg_succeser:.1f} / {runs[0]['total_scenes']}") print(f" Gennemsnitlige fiaskoer: {avg_fiaskoer:.1f}") print(f" Sejr: {victory_count} ({victory_count/total*100:.0f}%)") print(f" Blandet: {mixed_count} ({mixed_count/total*100:.0f}%)") print(f" Nederlag: {defeat_count} ({defeat_count/total*100:.0f}%)") # --- Per tema --- print("\n--- Per Tema ---") themes = {} for r in runs: t = r["tema"] if t not in themes: themes[t] = {"succeser": [], "fiaskoer": [], "victories": 0, "count": 0} themes[t]["succeser"].append(r["succeser"]) themes[t]["fiaskoer"].append(r["fiaskoer"]) themes[t]["count"] += 1 if r.get("victory"): themes[t]["victories"] += 1 for t, d in themes.items(): avg_s = statistics.mean(d["succeser"]) v_rate = d["victories"] / d["count"] * 100 print(f" {t}:") print(f" Spil: {d['count']}, Gns. succeser: {avg_s:.1f}, Sejr-rate: {v_rate:.0f}%") # --- Per pony type --- print("\n--- Per Pony Type ---") ponies = {} for r in runs: p = r["pony_type"] if p not in ponies: ponies[p] = {"succeser": [], "victories": 0, "count": 0} ponies[p]["succeser"].append(r["succeser"]) ponies[p]["count"] += 1 if r.get("victory"): ponies[p]["victories"] += 1 for p, d in ponies.items(): avg_s = statistics.mean(d["succeser"]) v_rate = d["victories"] / d["count"] * 100 print(f" {p}: {d['count']} spil, Gns. succeser: {avg_s:.1f}, Sejr-rate: {v_rate:.0f}%") # --- Per scene --- print("\n--- Scene Analyse (alle temaer samlet) ---") scene_data = {} for r in runs: tema = r["tema"] for i, res in enumerate(r["results"]): key = f"{tema} - Scene {i+1}" if key not in scene_data: scene_data[key] = {"successes": 0, "total": 0} scene_data[key]["total"] += 1 if res["succes"]: scene_data[key]["successes"] += 1 sorted_scenes = sorted(scene_data.items(), key=lambda x: x[1]["successes"] / x[1]["total"]) print("\n Sværeste scener (laveste succesrate):") for key, d in sorted_scenes[:5]: rate = d["successes"] / d["total"] * 100 print(f" {key}: {rate:.0f}% ({d['successes']}/{d['total']})") print("\n Letteste scener (højeste succesrate):") for key, d in sorted_scenes[-5:][::-1]: rate = d["successes"] / d["total"] * 100 print(f" {key}: {rate:.0f}% ({d['successes']}/{d['total']})") # --- Balance assessment --- print("\n--- Balance Vurdering ---") overall_rate = avg_succeser / runs[0]["total_scenes"] if overall_rate >= 0.8: print(" Spillet er FOR LET — de fleste passer næsten alle scener") elif overall_rate >= 0.6: print(" God balance — børn vil føle succes men også møde udfordring") elif overall_rate >= 0.4: print(" Spillet er MELLEMSVÆRT — kan være frustrerende for 4-årige") else: print(" Spillet er FOR SVÆRT — 4-årige vil blive frustrerede") if victory_count / total > 0.7: print(" Sejr-rate over 70% — børnespil skal have ~50-60% sejr") elif victory_count / total < 0.3: print(" Sejr-rate under 30% — for svært for målgruppen") else: print(f" Sejr-rate {victory_count/total*100:.0f}% — inden for rimeligt område") # --- Stats balance --- print("\n--- Stat Balance ---") stat_rolls = {} for r in runs: for res in r["results"]: stat = res.get("stat", "?") if stat not in stat_rolls: stat_rolls[stat] = [] stat_rolls[stat].append(1 if res["succes"] else 0) for stat, vals in sorted(stat_rolls.items()): rate = statistics.mean(vals) * 100 count = len(vals) emoji = {"krop": "💪", "sind": "🧠", "charme": "💕"}.get(stat, "") print(f" {emoji} {stat}: {rate:.0f}% succes over {count} kast") # --- Difficulty distribution --- print("\n--- Sværhedsfordeling ---") diff_counts = {"let": 0, "normal": 0, "svaert": 0} for tema in THEMAER: for scn in tema.get("scener", []): diff_counts[scn.get("svaer", "normal")] += 1 total_scenes = sum(diff_counts.values()) for diff, count in diff_counts.items(): pct = count / total_scenes * 100 label = {"let": "Nem", "normal": "Normal", "svaert": "Svær"}.get(diff, diff) print(f" {label}: {count} scener ({pct:.0f}%)") print("\n" + "=" * 60) def report_exhaustive(runs): """Report for exhaustive mode — includes per-scene breakdown and JSON output.""" report(runs) # Per-scene success rates across all pony types print("\n--- Per-Scene Success Rates (exhaustive) ---") for scene_num in range(5): scene_data = [] for r in runs: for sr in r.get("scene_results", []): if sr["scene"] == scene_num: scene_data.append(sr) if scene_data: success_rate = sum(1 for s in scene_data if s["success"]) / len(scene_data) * 100 print(f" Scene {scene_num+1}: {success_rate:.0f}% succes ({len(scene_data)} kast)") # Identify problem areas print("\n--- Balance-Problemer ---") for r in runs: if r["fiaskoer"] >= 4: print(f" FOR SVÆRT: {r['pony_type']} x {r['tema']} — {r['fiaskoer']} fiaskoer") if r["succeser"] == r["total_scenes"] and r["fiaskoer"] == 0: print(f" FOR LET: {r['pony_type']} x {r['tema']} — perfekt run") # Output JSON for automated analysis json_output = { "total": len(runs), "victory_count": sum(1 for r in runs if r.get("victory")), "win_rate": round(sum(1 for r in runs if r.get("victory")) / len(runs) * 100, 1) if runs else 0, "runs": runs, } print(f"\n--- JSON Output ({len(json.dumps(json_output))} chars) ---") 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") args = parser.parse_args() if args.exhaustive: runs = evaluate_exhaustive() report_exhaustive(runs) else: evaluate()