"""Procedurally generated games for KantBench.""" from __future__ import annotations import random as _rand from common.games import GAMES, GameConfig from constant_definitions.game_constants import DEFAULT_NUM_ROUNDS from constant_definitions.auction_nplayer_constants import ( GENERATED_DEFAULT_ACTIONS, GENERATED_PAYOFF_MIN, GENERATED_PAYOFF_MAX, GENERATED_SEED_DEFAULT, ) _ONE = int(bool(True)) def _action_label(index: int) -> str: """Generate action label: a, b, c, ... z, aa, ab, ...""" alphabet_size = ord("z") - ord("a") + _ONE if index < alphabet_size: return chr(ord("a") + index) first = index // alphabet_size - _ONE second = index % alphabet_size return chr(ord("a") + first) + chr(ord("a") + second) def generate_random_symmetric( num_actions: int = GENERATED_DEFAULT_ACTIONS, payoff_min: int = GENERATED_PAYOFF_MIN, payoff_max: int = GENERATED_PAYOFF_MAX, seed: int = GENERATED_SEED_DEFAULT, ) -> GameConfig: """Generate a random symmetric NxN matrix game. In a symmetric game, the payoff for the first player choosing (a, b) equals the payoff for the second player facing (b, a). """ rng = _rand.Random(seed) actions = [_action_label(i) for i in range(num_actions)] matrix: dict[tuple[str, str], tuple[float, float]] = {} for i, a in enumerate(actions): for j, b in enumerate(actions): if (a, b) not in matrix: p_first = float(rng.randint(payoff_min, payoff_max)) p_second = float(rng.randint(payoff_min, payoff_max)) matrix[(a, b)] = (p_first, p_second) matrix[(b, a)] = (p_second, p_first) def _payoff(pa: str, oa: str) -> tuple[float, float]: return matrix[(pa, oa)] return GameConfig( name=f"Random Symmetric {num_actions}x{num_actions} (seed={seed})", description=( f"A randomly generated {num_actions}x{num_actions} symmetric " f"matrix game with payoffs in [{payoff_min}, {payoff_max}]. " f"Tests generalization to novel strategic structures." ), actions=actions, game_type="matrix", default_rounds=DEFAULT_NUM_ROUNDS, payoff_fn=_payoff, ) def generate_random_asymmetric( num_actions: int = GENERATED_DEFAULT_ACTIONS, payoff_min: int = GENERATED_PAYOFF_MIN, payoff_max: int = GENERATED_PAYOFF_MAX, seed: int = GENERATED_SEED_DEFAULT, ) -> GameConfig: """Generate a random asymmetric NxN matrix game. Each cell has independently drawn payoffs for both players. """ rng = _rand.Random(seed) actions = [_action_label(i) for i in range(num_actions)] matrix: dict[tuple[str, str], tuple[float, float]] = {} for a in actions: for b in actions: p_first = float(rng.randint(payoff_min, payoff_max)) p_second = float(rng.randint(payoff_min, payoff_max)) matrix[(a, b)] = (p_first, p_second) def _payoff(pa: str, oa: str) -> tuple[float, float]: return matrix[(pa, oa)] return GameConfig( name=f"Random Asymmetric {num_actions}x{num_actions} (seed={seed})", description=( f"A randomly generated {num_actions}x{num_actions} asymmetric " f"matrix game with independent payoffs in [{payoff_min}, {payoff_max}]. " f"Tests reasoning in novel non-symmetric strategic settings." ), actions=actions, game_type="matrix", default_rounds=DEFAULT_NUM_ROUNDS, payoff_fn=_payoff, ) def generate_parameterized_pd( temptation: int, reward: int, punishment: int, sucker: int, seed: int = GENERATED_SEED_DEFAULT, ) -> GameConfig: """Create a Prisoner's Dilemma with custom T > R > P > S payoffs.""" matrix: dict[tuple[str, str], tuple[float, float]] = { ("cooperate", "cooperate"): (float(reward), float(reward)), ("cooperate", "defect"): (float(sucker), float(temptation)), ("defect", "cooperate"): (float(temptation), float(sucker)), ("defect", "defect"): (float(punishment), float(punishment)), } def _payoff(pa: str, oa: str) -> tuple[float, float]: return matrix[(pa, oa)] return GameConfig( name=f"PD(T={temptation},R={reward},P={punishment},S={sucker})", description=( f"A parameterized Prisoner's Dilemma with T={temptation}, " f"R={reward}, P={punishment}, S={sucker}. Tests sensitivity " f"to varying incentive structures." ), actions=["cooperate", "defect"], game_type="matrix", default_rounds=DEFAULT_NUM_ROUNDS, payoff_fn=_payoff, ) # -- Register default generated instances -- _DEFAULT_SYMMETRIC = generate_random_symmetric() _DEFAULT_ASYMMETRIC = generate_random_asymmetric(seed=GENERATED_SEED_DEFAULT + _ONE) GENERATED_GAMES: dict[str, GameConfig] = { "random_symmetric_3x3": _DEFAULT_SYMMETRIC, "random_asymmetric_3x3": _DEFAULT_ASYMMETRIC, } GAMES.update(GENERATED_GAMES)