Spaces:
Running
Running
| """Sequential (extensive-form) games for KantBench.""" | |
| from __future__ import annotations | |
| from common.games import GAMES, GameConfig | |
| from constant_definitions.game_constants import SINGLE_SHOT_ROUNDS, DEFAULT_NUM_ROUNDS | |
| from constant_definitions.sequential_constants import ( | |
| DICTATOR_ENDOWMENT, | |
| CENTIPEDE_INITIAL_POT, CENTIPEDE_GROWTH_MULTIPLIER, CENTIPEDE_MAX_STAGES, | |
| CENTIPEDE_LARGE_SHARE_NUMERATOR, CENTIPEDE_LARGE_SHARE_DENOMINATOR, | |
| CENTIPEDE_SMALL_SHARE_NUMERATOR, CENTIPEDE_SMALL_SHARE_DENOMINATOR, | |
| STACKELBERG_DEMAND_INTERCEPT, STACKELBERG_DEMAND_SLOPE, | |
| STACKELBERG_MARGINAL_COST, STACKELBERG_MAX_QUANTITY, | |
| ) | |
| _ONE = int(bool(True)) | |
| # -- Dictator Game -- | |
| def _dictator_payoff(player_action: str, opponent_action: str) -> tuple[float, float]: | |
| """Dictator allocates from endowment; recipient has no choice.""" | |
| amount = int(player_action.rsplit("_", _ONE)[_ONE]) | |
| dictator_keeps = float(DICTATOR_ENDOWMENT - amount) | |
| recipient_gets = float(amount) | |
| return (dictator_keeps, recipient_gets) | |
| _DICTATOR_ACTIONS = [ | |
| f"give_{i}" for i in range(DICTATOR_ENDOWMENT + _ONE) | |
| ] | |
| # -- Centipede Game -- | |
| def _centipede_payoff(player_action: str, opponent_action: str) -> tuple[float, float]: | |
| """Alternating pass/take game with growing pot. | |
| Actions encode the stage: 'take_N' means take at stage N, | |
| 'pass_all' means pass through all stages. | |
| The opponent strategy similarly responds with take or pass. | |
| """ | |
| if player_action == "pass_all": | |
| player_stage = CENTIPEDE_MAX_STAGES + _ONE | |
| else: | |
| player_stage = int(player_action.rsplit("_", _ONE)[_ONE]) | |
| if opponent_action == "pass_all": | |
| opp_stage = CENTIPEDE_MAX_STAGES + _ONE | |
| else: | |
| opp_stage = int(opponent_action.rsplit("_", _ONE)[_ONE]) | |
| take_stage = min(player_stage, opp_stage) | |
| pot = CENTIPEDE_INITIAL_POT | |
| for _ in range(take_stage): | |
| pot = pot * CENTIPEDE_GROWTH_MULTIPLIER | |
| large = pot * CENTIPEDE_LARGE_SHARE_NUMERATOR // CENTIPEDE_LARGE_SHARE_DENOMINATOR | |
| small = pot * CENTIPEDE_SMALL_SHARE_NUMERATOR // CENTIPEDE_SMALL_SHARE_DENOMINATOR | |
| if player_stage <= opp_stage: | |
| return (float(large), float(small)) | |
| return (float(small), float(large)) | |
| _CENTIPEDE_ACTIONS = [ | |
| f"take_{i}" for i in range(CENTIPEDE_MAX_STAGES + _ONE) | |
| ] + ["pass_all"] | |
| # -- Stackelberg Competition -- | |
| def _stackelberg_payoff( | |
| player_action: str, opponent_action: str, | |
| ) -> tuple[float, float]: | |
| """Stackelberg duopoly: leader (player) and follower (opponent). | |
| Profit = (demand_intercept - slope * (q_leader + q_follower) - cost) * q | |
| """ | |
| q_leader = int(player_action.rsplit("_", _ONE)[_ONE]) | |
| q_follower = int(opponent_action.rsplit("_", _ONE)[_ONE]) | |
| total_q = q_leader + q_follower | |
| price = STACKELBERG_DEMAND_INTERCEPT - STACKELBERG_DEMAND_SLOPE * total_q | |
| leader_profit = float((price - STACKELBERG_MARGINAL_COST) * q_leader) | |
| follower_profit = float((price - STACKELBERG_MARGINAL_COST) * q_follower) | |
| return (leader_profit, follower_profit) | |
| _STACKELBERG_ACTIONS = [ | |
| f"produce_{i}" for i in range(STACKELBERG_MAX_QUANTITY + _ONE) | |
| ] | |
| # -- Register -- | |
| SEQUENTIAL_GAMES: dict[str, GameConfig] = { | |
| "dictator": GameConfig( | |
| name="Dictator Game", | |
| description=( | |
| "One player (the dictator) decides how to split an endowment " | |
| "with a passive recipient who has no say. Tests fairness " | |
| "preferences and altruistic behavior when there is no strategic " | |
| "incentive to share." | |
| ), | |
| actions=_DICTATOR_ACTIONS, | |
| game_type="dictator", | |
| default_rounds=SINGLE_SHOT_ROUNDS, | |
| payoff_fn=_dictator_payoff, | |
| ), | |
| "centipede": GameConfig( | |
| name="Centipede Game", | |
| description=( | |
| "Players alternate deciding to take or pass. Each pass doubles " | |
| "the pot. The taker gets the larger share while the other gets " | |
| "the smaller share. Backward induction predicts immediate taking, " | |
| "but cooperation through passing yields higher joint payoffs." | |
| ), | |
| actions=_CENTIPEDE_ACTIONS, | |
| game_type="centipede", | |
| default_rounds=SINGLE_SHOT_ROUNDS, | |
| payoff_fn=_centipede_payoff, | |
| ), | |
| "stackelberg": GameConfig( | |
| name="Stackelberg Competition", | |
| description=( | |
| "A quantity-setting duopoly where the leader commits to a " | |
| "production quantity first, and the follower observes and " | |
| "responds. The leader can exploit first-mover advantage. " | |
| "Price is determined by total market quantity." | |
| ), | |
| actions=_STACKELBERG_ACTIONS, | |
| game_type="stackelberg", | |
| default_rounds=DEFAULT_NUM_ROUNDS, | |
| payoff_fn=_stackelberg_payoff, | |
| ), | |
| } | |
| GAMES.update(SEQUENTIAL_GAMES) | |