diff --git a/Shayan1/Prod/main.py b/Shayan1/Prod/main.py new file mode 100644 index 0000000..5c5170c --- /dev/null +++ b/Shayan1/Prod/main.py @@ -0,0 +1,270 @@ +import argparse +import itertools +import json +import random +import sys + +from nash import solver, visualize +from nash.game import Game +from nash.simulation import Simulation, EvolutionarySimulation + +# ANSI color codes — no external dependencies needed +RED = "\033[91m" +GREEN = "\033[92m" +YELLOW = "\033[93m" +CYAN = "\033[96m" +BOLD = "\033[1m" +DIM = "\033[2m" +RESET = "\033[0m" + + +def build_arg_parser(): + parser = argparse.ArgumentParser( + description="Solve and simulate Nash equilibria for a normal-form game." + ) + source = parser.add_mutually_exclusive_group(required=True) + source.add_argument("--game", help="Path to a game JSON file") + source.add_argument( + "--describe", + help="Plain-English scenario description; translated into a game JSON via AI", + ) + parser.add_argument("--rounds", type=int, default=50) + parser.add_argument("--memory", choices=["on", "off"], default="on") + parser.add_argument( + "--out", default="strategy_usage.png", help="Path to save the usage chart" + ) + parser.add_argument( + "--save-game", + default="games/generated.json", + help="Where to save an AI-translated game (only used with --describe)", + ) + parser.add_argument("--seed", type=int, default=None) + parser.add_argument( + "--player", + choices=["best_response", "regret_matching", "softmax", "q_learning", "llm"], + default="best_response", + help="Player decision model for simulation (default: best_response)", + ) + parser.add_argument( + "--inspect", + action="store_true", + help="Print game details, payoff matrix, and dominant strategies, then exit", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Print per-round debug data during simulation", + ) + parser.add_argument( + "--sim-type", + choices=["agents", "evolutionary"], + default="agents", + help="Type of simulation: 'agents' (default) or 'evolutionary'", + ) + parser.add_argument( + "--reduce", + action="store_true", + help="Iteratively eliminate strictly dominated strategies before simulation or inspection", + ) + return parser + + +def load_game(args): + try: + if args.describe: + from nash import translate + + print(f"Translating description via AI: {args.describe!r}") + data = translate.describe_to_json(args.describe) + translate.save_json(data, args.save_game) + print(f"Generated game JSON (saved to {args.save_game}):") + print(json.dumps(data, indent=2)) + return Game(data["players"], data["strategies"], data["payoffs"]) + return Game.load_from_json(args.game) + except ValueError as e: + print(f"{RED}{BOLD}Error:{RESET} {RED}{e}{RESET}", file=sys.stderr) + sys.exit(1) + +def print_inspection(game): + """Print a formatted game inspection: strategies, payoff matrix, and dominant strategies.""" + header = f"{BOLD}{CYAN}Game Inspection{RESET}" + rule = f"{DIM}{'─' * 60}{RESET}" + print(f"\n{rule}\n {header}\n{rule}") + + # --- Players & strategies --- + print(f"\n {BOLD}Players & Strategies{RESET}") + for i, name in enumerate(game.players): + strats = ", ".join(game.strategies[i]) + print(f" {CYAN}{name}{RESET} → [{strats}]") + + # --- Payoff matrix --- + print(f"\n {BOLD}Payoff Matrix{RESET}") + header_parts = [f"{CYAN}{p}{RESET}" for p in game.players] + print(f" {' │ '.join(header_parts)}") + + for profile in itertools.product(*(range(n) for n in game.strategy_counts())): + labels = " │ ".join( + f"{game.strategy_name(i, s):>12}" for i, s in enumerate(profile) + ) + payoffs = ", ".join( + _color_payoff(game.payoff(i, profile)) for i in range(game.num_players) + ) + print(f" {labels} │ ({payoffs})") + + # --- Dominant strategies --- + print(f"\n {BOLD}Dominant Strategies{RESET}") + try: + dominant = solver.find_dominant_strategies(game) + except AttributeError: + print(f" {YELLOW}(not available — solver.find_dominant_strategies not yet implemented){RESET}") + dominant = None + + if dominant is not None: + has_any = False + for i, name in enumerate(game.players): + strat_idx = dominant.get(i) + if strat_idx is not None: + strat_name = game.strategy_name(i, strat_idx) + print(f" {CYAN}{name}{RESET}: {GREEN}{BOLD}{strat_name}{RESET}") + has_any = True + else: + print(f" {CYAN}{name}{RESET}: {DIM}(none){RESET}") + if not has_any: + print(f" {YELLOW}No dominant strategies found for any player.{RESET}") + + print(f"\n{rule}") + + +def _color_payoff(value): + """Color a payoff value: green for positive, red for negative, plain for zero.""" + formatted = f"{value:+.2f}" if value != 0 else " 0.00" + if value > 0: + return f"{GREEN}{formatted}{RESET}" + elif value < 0: + return f"{RED}{formatted}{RESET}" + return formatted + + + +def print_solution(game): + print("\n=== Theoretical solution ===") + pure_eq = solver.find_pure_nash_equilibria(game) + if pure_eq: + for profile in pure_eq: + named = [game.strategy_name(i, s) for i, s in enumerate(profile)] + print(f"Pure Nash equilibrium: {named}") + else: + print("No pure Nash equilibrium found.") + + mixed_eq = [] + if game.num_players == 2: + mixed_eq = solver.find_mixed_nash_equilibria(game) + for x, y in mixed_eq: + print( + f"Mixed Nash equilibrium: {game.players[0]}={x.round(3).tolist()} " + f"{game.players[1]}={y.round(3).tolist()}" + ) + return pure_eq, mixed_eq + + +def print_comparison(game, simulation, pure_eq): + print("\n=== Comparison ===") + converged_round = simulation.converged_round() + + if converged_round is not None: + profile = simulation.converged_profile() + named = [game.strategy_name(i, s) for i, s in enumerate(profile)] + matches_pure = any(profile == eq for eq in pure_eq) + if matches_pure: + print( + f"Simulation converged at round {converged_round + 1} to {named} " + "-- a true Nash equilibrium." + ) + else: + print( + f"Play held at {named} for {simulation.convergence_window}+ rounds, " + "but that is NOT a Nash equilibrium of this game. This is expected " + "when there's no pure equilibrium: fictitious play produces streaks " + "that eventually flip as history accumulates. Only the long-run " + "frequencies below reflect the actual (mixed) equilibrium." + ) + else: + print("Play never held a single repeated profile within the given rounds.") + + frequencies = simulation.strategy_frequencies() + print("Long-run strategy usage frequencies (final round):") + for i, name in enumerate(game.players): + freqs = { + strat: round(frequencies[i][s][-1], 3) + for s, strat in enumerate(game.strategies[i]) + } + print(f" {name}: {freqs}") + + +def main(): + args = build_arg_parser().parse_args() + rng = random.Random(args.seed) + + game = load_game(args) + + if args.reduce: + game, logs = solver.reduce_game(game) + for log in logs: + print(log) + + # --inspect mode: print game details and exit + if args.inspect: + print_inspection(game) + sys.exit(0) + + print(f"Game: {', '.join(game.players)}") + for i, name in enumerate(game.players): + print(f" {name} strategies: {game.strategies[i]}") + + pure_eq, _ = print_solution(game) + + if args.sim_type == "evolutionary": + print(f"\n=== Evolutionary Simulation ({args.rounds} rounds) ===") + simulation = EvolutionarySimulation(game, args.rounds) + simulation.run() + + print("Final population distribution:") + for i, name in enumerate(game.players): + freqs = { + strat: round(float(simulation.populations[i][s]), 3) + for s, strat in enumerate(game.strategies[i]) + } + print(f" {name}: {freqs}") + + chart_path = visualize.plot_evolutionary_trajectories(game, simulation, args.out) + print(f"\nStrategy usage chart saved to: {chart_path}") + else: + memory = args.memory == "on" + print(f"\n=== Simulation ({args.rounds} rounds, memory={args.memory}, player={args.player}) ===") + + # Build Simulation kwargs + sim_kwargs = dict( + memory=memory, + rng=rng, + player_kind=args.player, + ) + + # --verbose: wire a debug logger + if args.verbose: + debug_logger = lambda player_index, msg: print( + f"[Round {len(simulation.history) + 1}] Player {player_index}: {msg}" + ) + sim_kwargs["debug_logger"] = debug_logger + + simulation = Simulation(game, args.rounds, **sim_kwargs) + simulation.run() + visualize.print_history(game, simulation) + + print_comparison(game, simulation, pure_eq) + + chart_path = visualize.plot_strategy_frequencies(game, simulation, args.out) + print(f"\nStrategy usage chart saved to: {chart_path}") + + +if __name__ == "__main__": + main()