diff --git a/Shayan1/Prod/nash/game.py b/Shayan1/Prod/nash/game.py new file mode 100644 index 0000000..0671fda --- /dev/null +++ b/Shayan1/Prod/nash/game.py @@ -0,0 +1,89 @@ +import itertools +import json + +import numpy as np + + +class GameValidationError(ValueError): + """Base exception for all game validation errors.""" + pass + + +class EmptyPlayersError(GameValidationError): + """Raised when the players list is empty.""" + pass + + +class EmptyStrategiesError(GameValidationError): + """Raised when a player's strategy list is empty.""" + pass + + +class DimensionMismatchError(GameValidationError): + """Raised when players, strategies, and payoffs lengths do not match.""" + pass + + +class PayoffShapeError(GameValidationError): + """Raised when a payoff tensor has an incorrect shape.""" + pass + + +class Game: + """A normal-form game: players, their strategies, and a payoff tensor per player.""" + + def __init__(self, players, strategies, payoffs): + if not players: + raise EmptyPlayersError("players list must not be empty") + if len(players) != len(strategies) or len(players) != len(payoffs): + raise DimensionMismatchError( + f"players ({len(players)}), strategies ({len(strategies)}), " + f"and payoffs ({len(payoffs)}) must have the same length" + ) + + self.players = list(players) + self.strategies = [list(s) for s in strategies] + self.num_players = len(players) + + for i, strats in enumerate(self.strategies): + if not strats: + raise EmptyStrategiesError(f"strategies[{i}] ({players[i]!r}) must not be empty") + + expected_shape = tuple(len(s) for s in self.strategies) + self.payoffs = [] + for i, p in enumerate(payoffs): + arr = np.array(p, dtype=float) + if arr.shape != expected_shape: + raise PayoffShapeError( + f"payoffs[{i}] ({players[i]!r}) has shape {arr.shape}, " + f"expected {expected_shape} " + f"(one dimension per player, sized to that player's strategy count)" + ) + self.payoffs.append(arr) + @classmethod + def load_from_json(cls, path): + try: + with open(path) as f: + data = json.load(f) + except (OSError, json.JSONDecodeError) as e: + raise ValueError(f"Failed to load game from {path!r}: {e}") from e + try: + return cls(data["players"], data["strategies"], data["payoffs"]) + except KeyError as e: + raise ValueError( + f"Game JSON is missing required field: {e}. " + f"Expected keys: 'players', 'strategies', 'payoffs'" + ) from e + + def strategy_counts(self): + return [len(s) for s in self.strategies] + + def payoff(self, player_index, profile): + """profile is a tuple of strategy indices, one per player.""" + return self.payoffs[player_index][profile] + + def all_profiles(self): + return itertools.product(*(range(n) for n in self.strategy_counts())) + + def strategy_name(self, player_index, strategy_index): + return self.strategies[player_index][strategy_index]