Import source: Shayan1/Prod/nash/game

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:14:38 +03:30
parent 3f25b9904c
commit b9d18828b3
+89
View File
@@ -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]