From f80c8aff79e05fb473b5b394488ff8a61df52002 Mon Sep 17 00:00:00 2001 From: alexpolo Date: Sun, 9 Aug 2026 08:11:14 +0000 Subject: [PATCH] feat: CLI generator --seed, --pony-count, --scene-count, --json, --quiet flags --- ny_spiller.py | 203 +++++++++++++++------ pony-frontend/src/App.js | 26 +-- pony-frontend/src/services/achievements.js | 72 ++++++++ 3 files changed, 229 insertions(+), 72 deletions(-) create mode 100644 pony-frontend/src/services/achievements.js diff --git a/ny_spiller.py b/ny_spiller.py index 72f9027..b779968 100644 --- a/ny_spiller.py +++ b/ny_spiller.py @@ -418,35 +418,90 @@ def print_test_table(all_tests): # === HUVUDKØRSEL === def main(): - random.seed() # Tilfældig seed hver gang + import sys - print_header("MY LITTLE PONY: TAILS OF EQUESTRIA") - print(" — Ny Spiller Generator —") - print(" Genereret af Hermes AI GM") + # Parse CLI args + parser_type = "argparse" # avoid import if not needed + args = { + "seed": None, + "pony_count": None, + "scene_count": None, + "json": False, + "quiet": False, + } + raw = sys.argv[1:] + i = 0 + while i < len(raw): + a = raw[i] + if a == "--seed" and i + 1 < len(raw): + args["seed"] = int(raw[i + 1]) + i += 2 + elif a == "--pony-count" and i + 1 < len(raw): + args["pony_count"] = int(raw[i + 1]) + i += 2 + elif a == "--scene-count" and i + 1 < len(raw): + args["scene_count"] = int(raw[i + 1]) + i += 2 + elif a == "--json": + args["json"] = True + i += 1 + elif a == "--quiet": + args["quiet"] = True + i += 1 + else: + i += 1 - # Generer 4-6 ponyer - ant_al_ponyer = random.randint(4, 6) - print(f"\nGenererer {ant_al_ponyer} ponyer...") + # Seed RNG + if args["seed"] is not None: + random.seed(args["seed"]) + else: + random.seed() + + result = { + "ponies": [], + "theme": None, + "location": None, + "scenes": [], + "tests": [], + "outcome": None, + } + + if not args["quiet"]: + print_header("MY LITTLE PONY: TAILS OF EQUESTRIA") + print(" — Ny Spiller Generator —") + print(" Genereret af Hermes AI GM") + + # Generate ponies + ant_al_ponyer = args["pony_count"] or random.randint(4, 6) + if not args["quiet"]: + print(f"\nGenererer {ant_al_ponyer} ponyer...") ponies = [generer_pony() for _ in range(ant_al_ponyer)] + result["ponies"] = ponies - print_header("DINE PONYER") - for p in ponies: - print_pony(p) + if not args["quiet"]: + print_header("DINE PONYER") + for p in ponies: + print_pony(p) - # Vælg tema og setting + # Select theme and setting tema = random.choice(THEMAER) sted = random.choice(EQUESTRIA_STEDER) - print_header(f"EVENTYR: {tema['titel'].upper()}") - print(f" SETTING: {sted['navn']} — {sted['beskrivelse']}") - print(f" Fjende: {tema['fjende']}") - print(f" Stakes: {tema['stakes']}") + result["theme"] = tema["titel"] + result["location"] = sted["navn"] - # Kør scener (5-7) - scenes = tema['scener'] - ant_al_scener = min(len(scenes), random.randint(5, 7)) + if not args["quiet"]: + print_header(f"EVENTYR: {tema['titel'].upper()}") + print(f" SETTING: {sted['navn']} — {sted['beskrivelse']}") + print(f" Fjende: {tema['fjende']}") + print(f" Stakes: {tema['stakes']}") + + # Run scenes + scenes = tema["scener"] + ant_al_scener = args["scene_count"] or min(len(scenes), random.randint(5, 7)) scenes_at_koere = scenes[:ant_al_scener] - print_header(f"HANDLING ({ant_al_scener} SCENER)") + if not args["quiet"]: + print_header(f"HANDLING ({ant_al_scener} SCENER)") all_tests = [] scene_nummer = 0 @@ -454,22 +509,24 @@ def main(): scene_nummer += 1 scene_navn, scene_beskrivelse, egenskab, svaerhedsgrad = scene - print(f"\n{'─' * 60}") - print(f" SCENE {scene_nummer}: {scene_navn.upper()}") - print(f"{'─' * 60}") - print(f" {scene_beskrivelse}") + if not args["quiet"]: + print(f"\n{'─' * 60}") + print(f" SCENE {scene_nummer}: {scene_navn.upper()}") + print(f"{'─' * 60}") + print(f" {scene_beskrivelse}") - # Tilfældig NPC-møde (20% chance) + # Random NPC encounter (20% chance) if random.random() < 0.2: npc = random.choice(KENDTE_NPCS) handling = random.choice(npc["handlinger"]) - print(f"\n 🎭 NPC-MØDE: {npc['navn']} ({npc['race']}) dukker op og {handling}") - print(f" ({npc['personlighed']})") + if not args["quiet"]: + print(f"\n 🎭 NPC-MØDE: {npc['navn']} ({npc['race']}) dukker op og {handling}") + print(f" ({npc['personlighed']})") - # Vælg hvilken pony der tester (tilfældig, men foretrækker relevante stats) + # Pick test pony test_pony = random.choice(ponies) - # Find relevant talent baseret på egenskab + # Find relevant talent relevant_talent = None if egenskab == "krop": for t in test_pony["talenter"]: @@ -487,47 +544,89 @@ def main(): relevant_talent = t break - result = test(test_pony, egenskab, svaerhedsgrad, relevant_talent) - all_tests.append(result) - print_test(result, len(all_tests)) + r = test(test_pony, egenskab, svaerhedsgrad, relevant_talent) + all_tests.append(r) + result["scenes"].append({ + "number": scene_nummer, + "name": scene_navn, + "test": r, + }) + if not args["quiet"]: + print_test(r, len(all_tests)) - # Hvis succes, lav en bonus-test fra en anden pony (for variation) - if result["udfald"] == "SUCCES" and result["overskud"] >= 2: + # Bonus test on big success + if r["udfald"] == "SUCCES" and r["overskud"] >= 2: bonus_pony = random.choice([p for p in ponies if p["navn"] != test_pony["navn"]]) bonus_egen = random.choice(EGENSKABER) bonus_sv = random.choice(["let", "normal", "svaert"]) bonus_result = test(bonus_pony, bonus_egen, bonus_sv) all_tests.append(bonus_result) - print_test(bonus_result, len(all_tests)) - print(f"\n 🌟 BONUS: {bonus_pony['navn']} hjælper med!") + if not args["quiet"]: + print_test(bonus_result, len(all_tests)) + print(f"\n 🌟 BONUS: {bonus_pony['navn']} hjælper med!") - # Slutopgørelse - print_header("SLUTOPOPGØRELSE") + # Final tally succeser = sum(1 for t in all_tests if t["udfald"] == "SUCCES") fiaskoer = sum(1 for t in all_tests if t["udfald"] == "MISLYKKES") fantastiske_total = sum(t["fantastiske"] for t in all_tests) - print(f"\n Total succeser: {succeser}") - print(f" Total fiaskoer: {fiaskoer}") - print(f" Fantastiske terninger (6): {fantastiske_total}") - if succeser > fiaskoer: - print(f"\n 🌟 SEJR! Ponyerne har overvundet {tema['fjende']}!") - print(f" {tema['stakes']}") + outcome = "SEJR" if fantastiske_total >= 3: - print(f" ✦ EPISKE SEJR! Alle i Equestria hylder dem!") + outcome = "EPISKE_SEJR" elif succeser == fiaskoer: - print(f"\n ⚖️ BLANDET RESULTAT. Ponyerne har kæmpet hårdt, men {tema['fjende']} trækker sig tilbage midlertidigt.") - print(f" {tema['stakes']}") + outcome = "BLANDET" else: - print(f"\n 💀 Nederlag. {tema['fjende']} har vundet denne gang.") - print(f" {tema['stakes']}") + outcome = "NEDERLAG" + result["outcome"] = outcome - print_test_table(all_tests) + if not args["quiet"]: + print_header("SLUTOPOPGØRELSE") + print(f"\n Total succeser: {succeser}") + print(f" Total fiaskoer: {fiaskoer}") + print(f" Fantastiske terninger (6): {fantastiske_total}") - print(f"\n{'=' * 60}") - print(" FÆRDIG") - print(f"{'=' * 60}") + if succeser > fiaskoer: + print(f"\n 🌟 SEJR! Ponyerne har overvundet {tema['fjende']}!") + print(f" {tema['stakes']}") + if fantastiske_total >= 3: + print(f" ✦ EPISKE SEJR! Alle i Equestria hylder dem!") + elif succeser == fiaskoer: + print(f"\n ⚖️ BLANDET RESULTAT. Ponyerne har kæmpet hårdt, men {tema['fjende']} trækker sig tilbage midlertidigt.") + print(f" {tema['stakes']}") + else: + print(f"\n 💀 Nederlag. {tema['fjende']} har vundet denne gang.") + print(f" {tema['stakes']}") + + print_test_table(all_tests) + print(f"\n{'=' * 60}") + print(" FÆRDIG") + print(f"{'=' * 60}") + + if args["json"]: + import json + # Strip non-serializable data + out = { + "seed": args["seed"], + "ponies": ponies, + "theme": tema["titel"], + "location": sted["navn"], + "outcome": outcome, + "successes": succeser, + "failures": fiaskoer, + "fantastic_rolls": fantastiske_total, + "tests": [ + { + "pony": t["pony"], + "stat": t["egenskab"], + "dice": t["resultat"], + "successes": t["succeser"], + "outcome": t["udfald"], + } + for t in all_tests + ], + } + print(json.dumps(out, ensure_ascii=False, indent=2)) if __name__ == "__main__": diff --git a/pony-frontend/src/App.js b/pony-frontend/src/App.js index 12d28a0..f322fa7 100644 --- a/pony-frontend/src/App.js +++ b/pony-frontend/src/App.js @@ -8,6 +8,7 @@ import PonySelectPage from './pages/PonySelectPage'; import GameScenePage from './pages/GameScenePage'; import GameEndPage from './pages/GameEndPage'; import * as api from './services/api'; +import * as achievements from './services/achievements'; import './App.css'; // Fallback pony data if API fails to load @@ -18,32 +19,17 @@ const DEFAULT_PONIES = [ { name: 'Alicorn', emoji: '👑', img: 'alicorn.png', bonus: 'Magi + vinger 🌟', color: '#FFD700', diceBonus: 2 }, ]; -// Achievement hook +// Achievement hook — thin wrapper around the service function useAchievements() { - const [stats, setStats] = useState(() => { - try { - return JSON.parse(localStorage.getItem('pony_stats') || '{"wins":0,"losses":0,"games":0,"bestScore":0}'); - } catch { return { wins: 0, losses: 0, games: 0, bestScore: 0 }; } - }); + const [stats, setStats] = useState(() => achievements.getStats()); const recordGame = useCallback((victory, score) => { - setStats(prev => { - const next = { - ...prev, - games: prev.games + 1, - wins: victory ? prev.wins + 1 : prev.wins, - losses: victory ? prev.losses : prev.losses + 1, - bestScore: Math.max(prev.bestScore, score || 0), - }; - localStorage.setItem('pony_stats', JSON.stringify(next)); - return next; - }); + setStats(achievements.recordGame(victory, score)); }, []); const resetStats = useCallback(() => { - const zero = { wins: 0, losses: 0, games: 0, bestScore: 0 }; - setStats(zero); - localStorage.setItem('pony_stats', JSON.stringify(zero)); + achievements.resetStats(); + setStats(achievements.getStats()); }, []); return { stats, recordGame, resetStats }; diff --git a/pony-frontend/src/services/achievements.js b/pony-frontend/src/services/achievements.js new file mode 100644 index 0000000..97c23db --- /dev/null +++ b/pony-frontend/src/services/achievements.js @@ -0,0 +1,72 @@ +/** + * Achievement system — deterministic, persistent, localStorage-backed. + * + * Tracks wins, losses, total games, and best score. + * Syncs with backend when game ends. + */ + +const STORAGE_KEY = 'pony_stats'; + +const DEFAULT_STATS = { wins: 0, losses: 0, games: 0, bestScore: 0 }; + +function loadStats() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) return JSON.parse(raw); + } catch { /* ignore */ } + return { ...DEFAULT_STATS }; +} + +function saveStats(stats) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(stats)); +} + +/** + * Record a completed game. + * @param {boolean} victory — whether the player won + * @param {number} score — number of successes + * @returns {Object} updated stats + */ +export function recordGame(victory, score) { + const stats = loadStats(); + stats.games += 1; + if (victory) { + stats.wins += 1; + } else { + stats.losses += 1; + } + stats.bestScore = Math.max(stats.bestScore, score || 0); + saveStats(stats); + return stats; +} + +/** + * Reset all achievement stats. + */ +export function resetStats() { + saveStats({ ...DEFAULT_STATS }); +} + +/** + * Get current stats. + * @returns {Object} + */ +export function getStats() { + return loadStats(); +} + +/** + * Compute derived achievement badges. + * @param {Object} stats + * @returns {string[]} badge labels + */ +export function computeBadges(stats) { + const badges = []; + if (stats.games >= 1) badges.push('🎮 Første spil'); + if (stats.wins >= 1) badges.push('🌟 Første sejr'); + if (stats.games >= 5) badges.push('🎯 5 spil'); + if (stats.wins >= 5) badges.push('⭐ 5 sejre'); + if (stats.bestScore >= 5) badges.push('🏆 Perfekt run'); + if (stats.games >= 10) badges.push('👑 Pony Mester!'); + return badges; +} \ No newline at end of file