KantBench / common /games_meta /coalition_config.py
jtowarek's picture
Upload folder using huggingface_hub
688c130 verified
"""Coalition game configuration, payoff functions, and built-in game registry."""
from __future__ import annotations
from common.games import GameConfig
from common.games_meta.nplayer_config import NPLAYER_GAMES
from constant_definitions.nplayer.coalition_constants import (
COALITION_DEFAULT_ROUNDS, COALITION_DEFAULT_PENALTY_NUMERATOR,
COALITION_DEFAULT_PENALTY_DENOMINATOR,
ENFORCEMENT_CHEAP_TALK, ENFORCEMENT_PENALTY, ENFORCEMENT_BINDING,
CARTEL_NUM_PLAYERS, CARTEL_COLLUDE_THRESHOLD,
CARTEL_COLLUDE_HIGH, CARTEL_COLLUDE_LOW,
CARTEL_COMPETE_HIGH, CARTEL_COMPETE_LOW,
ALLIANCE_NUM_PLAYERS, ALLIANCE_SUPPORT_POOL,
ALLIANCE_BETRAY_GAIN, ALLIANCE_NO_SUPPORT,
VOTING_NUM_PLAYERS, VOTING_WINNER_PAYOFF, VOTING_LOSER_PAYOFF,
OSTRACISM_NUM_PLAYERS, OSTRACISM_BONUS_POOL,
OSTRACISM_EXCLUDED_PAYOFF, OSTRACISM_BASE_PAYOFF,
OSTRACISM_MAJORITY_NUMERATOR, OSTRACISM_MAJORITY_DENOMINATOR,
TRADE_NUM_PLAYERS, TRADE_DIVERSE_PAYOFF,
TRADE_HOMOGENEOUS_PAYOFF, TRADE_MINORITY_BONUS,
RULE_NUM_PLAYERS, RULE_EQUAL_PAY, RULE_WINNER_HIGH, RULE_WINNER_LOW,
COMMONS_NUM_PLAYERS, COMMONS_SUSTAINABLE_THRESHOLD,
COMMONS_LOW_SUSTAINABLE, COMMONS_HIGH_SUSTAINABLE,
COMMONS_LOW_DEPLETED, COMMONS_HIGH_DEPLETED,
)
CoalitionGameConfig = GameConfig
_ONE = int(bool(True))
_ZERO = int()
_PEN_N = COALITION_DEFAULT_PENALTY_NUMERATOR
_PEN_D = COALITION_DEFAULT_PENALTY_DENOMINATOR
COALITION_GAMES: dict[str, GameConfig] = {}
def get_coalition_game(name: str) -> GameConfig:
"""Look up a coalition game by name. Raises KeyError if not found."""
return COALITION_GAMES[name]
# ---------------------------------------------------------------------------
# Payoff functions
# ---------------------------------------------------------------------------
def _cartel_payoff(actions: tuple[str, ...]) -> tuple[float, ...]:
colluders = sum(_ONE for a in actions if a == "collude")
holds = colluders >= CARTEL_COLLUDE_THRESHOLD
return tuple(
float(CARTEL_COLLUDE_HIGH if holds else CARTEL_COLLUDE_LOW) if a == "collude"
else float(CARTEL_COMPETE_HIGH if holds else CARTEL_COMPETE_LOW)
for a in actions
)
def _alliance_payoff(actions: tuple[str, ...]) -> tuple[float, ...]:
supporters = sum(_ONE for a in actions if a == "support")
if supporters == _ZERO:
return tuple(float(ALLIANCE_NO_SUPPORT) for _ in actions)
return tuple(
float(ALLIANCE_SUPPORT_POOL) / supporters if a == "support"
else float(ALLIANCE_BETRAY_GAIN) for a in actions
)
def _coalition_voting_payoff(actions: tuple[str, ...]) -> tuple[float, ...]:
vote_a = sum(_ONE for a in actions if a == "vote_A")
winning = "vote_A" if vote_a >= len(actions) - vote_a else "vote_B"
return tuple(
float(VOTING_WINNER_PAYOFF) if a == winning
else float(VOTING_LOSER_PAYOFF) for a in actions
)
def _ostracism_payoff(actions: tuple[str, ...]) -> tuple[float, ...]:
n = len(actions)
majority = n * OSTRACISM_MAJORITY_NUMERATOR // OSTRACISM_MAJORITY_DENOMINATOR + _ONE
vote_counts: dict[str, int] = {}
for a in actions:
vote_counts[a] = vote_counts.get(a, _ZERO) + _ONE
excluded = -_ONE
for target, count in vote_counts.items():
if target != "exclude_none" and count >= majority:
excluded = int(target.rsplit("_", _ONE)[_ONE])
break
if excluded >= _ZERO:
non_excluded = n - _ONE
share = float(OSTRACISM_BONUS_POOL) / non_excluded if non_excluded > _ZERO else float(_ZERO)
return tuple(
float(OSTRACISM_EXCLUDED_PAYOFF) if i == excluded else share
for i in range(n)
)
return tuple(float(OSTRACISM_BASE_PAYOFF) for _ in range(n))
_OSTRACISM_ACTIONS = [f"exclude_{i}" for i in range(OSTRACISM_NUM_PLAYERS)] + ["exclude_none"]
def _resource_trading_payoff(actions: tuple[str, ...]) -> tuple[float, ...]:
n = len(actions)
count_a = sum(_ONE for a in actions if a == "produce_A")
count_b = n - count_a
if count_a == _ZERO or count_b == _ZERO:
return tuple(float(TRADE_HOMOGENEOUS_PAYOFF) for _ in actions)
payoffs: list[float] = []
for a in actions:
base = float(TRADE_DIVERSE_PAYOFF)
is_min = (a == "produce_A" and count_a < count_b) or (a == "produce_B" and count_b < count_a)
payoffs.append(base + float(TRADE_MINORITY_BONUS) if is_min else base)
return tuple(payoffs)
def _rule_voting_payoff(actions: tuple[str, ...]) -> tuple[float, ...]:
vote_counts: dict[str, int] = {}
for a in actions:
vote_counts[a] = vote_counts.get(a, _ZERO) + _ONE
winning, best = "rule_equal", _ZERO
for rule, count in sorted(vote_counts.items()):
if count > best:
best, winning = count, rule
if winning == "rule_equal":
return tuple(float(RULE_EQUAL_PAY) for _ in actions)
return tuple(
float(RULE_WINNER_HIGH) if a == winning else float(RULE_WINNER_LOW)
for a in actions
)
def _commons_governance_payoff(actions: tuple[str, ...]) -> tuple[float, ...]:
high = sum(_ONE for a in actions if a == "extract_high")
ok = high <= COMMONS_SUSTAINABLE_THRESHOLD
return tuple(
float(
(COMMONS_HIGH_SUSTAINABLE if ok else COMMONS_HIGH_DEPLETED) if a == "extract_high"
else (COMMONS_LOW_SUSTAINABLE if ok else COMMONS_LOW_DEPLETED)
) for a in actions
)
# ---------------------------------------------------------------------------
# Built-in coalition games
# ---------------------------------------------------------------------------
def _cfg(name: str, desc: str, actions: list[str], n: int,
fn: object, enf: str, side: bool = False) -> GameConfig:
return GameConfig(
name=name, description=desc, actions=actions, game_type="coalition",
num_players=n, default_rounds=COALITION_DEFAULT_ROUNDS,
payoff_fn=fn, # type: ignore[arg-type]
enforcement=enf, penalty_numerator=_PEN_N, penalty_denominator=_PEN_D,
allow_side_payments=side,
)
_BUILTIN_COALITION_GAMES: dict[str, GameConfig] = {
"coalition_cartel": _cfg(
"Cartel",
"Players collude or compete. If enough collude the cartel holds. "
"Defectors who promised to collude are fined under penalty enforcement.",
["collude", "compete"], CARTEL_NUM_PLAYERS, _cartel_payoff, ENFORCEMENT_PENALTY,
),
"coalition_alliance": _cfg(
"Alliance Formation",
"Form non-binding alliances. Supporters split a shared pool; "
"betrayers take a fixed gain. Cheap-talk: no enforcement.",
["support", "betray"], ALLIANCE_NUM_PLAYERS, _alliance_payoff, ENFORCEMENT_CHEAP_TALK,
),
"coalition_voting": _cfg(
"Coalition Voting",
"Form voting blocs bound to vote together. Majority earns a winner payoff. "
"Binding enforcement overrides defectors to their agreed vote.",
["vote_A", "vote_B"], VOTING_NUM_PLAYERS, _coalition_voting_payoff, ENFORCEMENT_BINDING,
),
"coalition_ostracism": _cfg(
"Ostracism",
"Vote to exclude a player. Excluded gets zero; others split a bonus. "
"Penalty enforcement fines defectors who break exclusion agreements.",
_OSTRACISM_ACTIONS, OSTRACISM_NUM_PLAYERS, _ostracism_payoff, ENFORCEMENT_PENALTY,
),
"coalition_resource_trading": _cfg(
"Resource Trading",
"Produce resource A or B. Diversity is rewarded; minority producers get a bonus. "
"Cheap-talk lets players agree on production but renegotiate freely.",
["produce_A", "produce_B"], TRADE_NUM_PLAYERS, _resource_trading_payoff,
ENFORCEMENT_CHEAP_TALK, side=True,
),
"coalition_rule_voting": _cfg(
"Rule Voting",
"Vote on payoff rule: equal split or winner-take-all. "
"Binding enforcement locks coalition members to their agreed vote.",
["rule_equal", "rule_winner"], RULE_NUM_PLAYERS, _rule_voting_payoff, ENFORCEMENT_BINDING,
),
"coalition_commons": _cfg(
"Commons Governance",
"Extract from a shared resource. Over-extraction degrades payoffs. "
"Penalty enforcement fines coalition members who exceed agreed limits.",
["extract_low", "extract_high"], COMMONS_NUM_PLAYERS,
_commons_governance_payoff, ENFORCEMENT_PENALTY,
),
}
COALITION_GAMES.update(_BUILTIN_COALITION_GAMES)
# Register coalition games as N-player games too (same GameConfig instances)
for _key, _c in _BUILTIN_COALITION_GAMES.items():
NPLAYER_GAMES[_key] = _c