jtowarek's picture
Upload folder using huggingface_hub
f7e2ae6 verified
"""N-player social dilemma games for KantBench.
Modeled as one agent vs one opponent (representing aggregate of others).
"""
from __future__ import annotations
from common.games import GAMES, GameConfig
from constant_definitions.game_constants import DEFAULT_NUM_ROUNDS, SINGLE_SHOT_ROUNDS
from constant_definitions.auction_nplayer_constants import (
COMMONS_RESOURCE_CAPACITY, COMMONS_MAX_EXTRACTION,
COMMONS_DEPLETION_PENALTY,
VOLUNTEER_BENEFIT, VOLUNTEER_COST, VOLUNTEER_NO_VOL,
EL_FAROL_ATTEND_REWARD, EL_FAROL_CROWD_PENALTY, EL_FAROL_STAY_HOME,
EL_FAROL_CAPACITY,
)
_ONE = int(bool(True))
_ZERO_F = float()
# -- Tragedy of the Commons --
def _commons_payoff(
player_action: str, opponent_action: str,
) -> tuple[float, float]:
"""Resource extraction game.
Each player extracts from a shared resource. If total extraction
exceeds capacity, both suffer a depletion penalty.
"""
p_extract = int(player_action.rsplit("_", _ONE)[_ONE])
o_extract = int(opponent_action.rsplit("_", _ONE)[_ONE])
total = p_extract + o_extract
if total > COMMONS_RESOURCE_CAPACITY:
return (float(COMMONS_DEPLETION_PENALTY), float(COMMONS_DEPLETION_PENALTY))
return (float(p_extract), float(o_extract))
_COMMONS_ACTIONS = [
f"extract_{i}" for i in range(COMMONS_MAX_EXTRACTION + _ONE)
]
# -- Volunteer's Dilemma --
def _volunteer_payoff(
player_action: str, opponent_action: str,
) -> tuple[float, float]:
"""At least one must volunteer for everyone to benefit.
Volunteering costs the volunteer but benefits all.
If nobody volunteers, everyone gets nothing.
"""
p_vol = player_action == "volunteer"
o_vol = opponent_action == "volunteer"
if not p_vol and not o_vol:
return (float(VOLUNTEER_NO_VOL), float(VOLUNTEER_NO_VOL))
p_pay = float(VOLUNTEER_BENEFIT - VOLUNTEER_COST) if p_vol else float(VOLUNTEER_BENEFIT)
o_pay = float(VOLUNTEER_BENEFIT - VOLUNTEER_COST) if o_vol else float(VOLUNTEER_BENEFIT)
return (p_pay, o_pay)
# -- El Farol Bar Problem --
def _el_farol_payoff(
player_action: str, opponent_action: str,
) -> tuple[float, float]:
"""Bar attendance decision game.
Going to the bar is fun if few attend (under capacity), but
unpleasant if crowded. Staying home gives a moderate fixed payoff.
"""
p_goes = player_action == "attend"
o_goes = opponent_action == "attend"
attendees = int(p_goes) + int(o_goes)
crowded = attendees > _ONE
if not p_goes:
p_pay = float(EL_FAROL_STAY_HOME)
elif crowded:
p_pay = float(EL_FAROL_CROWD_PENALTY)
else:
p_pay = float(EL_FAROL_ATTEND_REWARD)
if not o_goes:
o_pay = float(EL_FAROL_STAY_HOME)
elif crowded:
o_pay = float(EL_FAROL_CROWD_PENALTY)
else:
o_pay = float(EL_FAROL_ATTEND_REWARD)
return (p_pay, o_pay)
# -- Register --
NPLAYER_GAMES: dict[str, GameConfig] = {
"tragedy_of_commons": GameConfig(
name="Tragedy of the Commons",
description=(
"Players extract resources from a shared pool. Individual "
"incentive is to extract more, but if total extraction exceeds "
"the sustainable capacity, the resource collapses and everyone "
"suffers. Models environmental and resource management dilemmas."
),
actions=_COMMONS_ACTIONS,
game_type="commons",
default_rounds=DEFAULT_NUM_ROUNDS,
payoff_fn=_commons_payoff,
),
"volunteer_dilemma": GameConfig(
name="Volunteer's Dilemma",
description=(
"At least one player must volunteer (at personal cost) for "
"everyone to receive a benefit. If nobody volunteers, all get "
"nothing. Models bystander effects and public good provision."
),
actions=["volunteer", "abstain"],
game_type="matrix",
default_rounds=DEFAULT_NUM_ROUNDS,
payoff_fn=_volunteer_payoff,
),
"el_farol": GameConfig(
name="El Farol Bar Problem",
description=(
"Each player decides whether to attend a bar. If attendance "
"is below capacity, going is better than staying home. If the "
"bar is crowded, staying home is better. Models minority games "
"and congestion dynamics."
),
actions=["attend", "stay_home"],
game_type="matrix",
default_rounds=DEFAULT_NUM_ROUNDS,
payoff_fn=_el_farol_payoff,
),
}
GAMES.update(NPLAYER_GAMES)