from otree.api import * import random doc = """ Threshold Public Goods Game (Phase 2): - 2 players contribute points towards group connection threshold - Threshold: 60 points total → group connection - Non-contributed points are doubled for each player - Endowment: 50 points per player """ class C(BaseConstants): NAME_IN_URL = 'threshold_public_goods' PLAYERS_PER_GROUP = 2 NUM_ROUNDS = 1 ENDOWMENT = 50 THRESHOLD = 60 MULTIPLIER = 2 CHAT_DURATION = 300 # 5 minutes BELIEF_BONUS = 15 BELIEF_TOLERANCE = 5 class Subsession(BaseSubsession): pass class Group(BaseGroup): total_contribution = models.IntegerField( initial=0, doc="Sum of all contributions in the group" ) group_connection_initial = models.StringField( doc="Type of group connection achieved" ) class Player(BasePlayer): endowment = models.IntegerField( initial=0, doc="Initial endowment for this player" ) contribution = models.IntegerField( min=0, label="" ) payoff_kept = models.IntegerField( doc="Points kept and doubled" ) # Belief elicitation belief_partner_contribution = models.IntegerField( label='', min=0, max=C.ENDOWMENT, ) # SWB fields swb_bf_happy = models.IntegerField( choices=[0, 1, 2, 3, 4, 5, 6], label='', widget=widgets.RadioSelectHorizontal, ) swb_bf_friendly = models.IntegerField( choices=[0, 1, 2, 3, 4, 5, 6], label='', widget=widgets.RadioSelectHorizontal, ) swb_bf_relaxed = models.IntegerField( choices=[0, 1, 2, 3, 4, 5, 6], label='', widget=widgets.RadioSelectHorizontal, ) swb_bf_stressed = models.IntegerField( choices=[0, 1, 2, 3, 4, 5, 6], label='', widget=widgets.RadioSelectHorizontal, ) swb_bf_sad = models.IntegerField( choices=[0, 1, 2, 3, 4, 5, 6], label='', widget=widgets.RadioSelectHorizontal, ) swb_bf_angry = models.IntegerField( choices=[0, 1, 2, 3, 4, 5, 6], label='', widget=widgets.RadioSelectHorizontal, ) swb_bf_frustrated = models.IntegerField( choices=[0, 1, 2, 3, 4, 5, 6], label='', widget=widgets.RadioSelectHorizontal, ) feeling_order_bf = models.StringField() # FUNCTIONS def set_payoffs(group: Group): players = group.get_players() group.total_contribution = sum([p.contribution for p in players]) if group.total_contribution >= C.THRESHOLD: group.group_connection_initial = "strong" else: group.group_connection_initial = "none" for player in players: points_kept = player.endowment - player.contribution player.payoff_kept = points_kept * C.MULTIPLIER player.payoff = player.payoff_kept # ── PAGES ───────────────────────────────────────────────────────────────────── class SWBI(Page): form_model = 'player' form_fields = [ 'swb_bf_happy', 'swb_bf_friendly', 'swb_bf_relaxed', 'swb_bf_stressed', 'swb_bf_sad', 'swb_bf_angry', 'swb_bf_frustrated', ] @staticmethod def vars_for_template(player: Player): feelings = ['happy', 'friendly', 'relaxed', 'stressed', 'sad', 'angry', 'frustrated'] random.shuffle(feelings) player.feeling_order_bf = ','.join(feelings) return dict(feeling_order=feelings) class ChatWaitPage(WaitPage): @staticmethod def is_displayed(player: Player): return player.participant.vars.get('communication') == 'communication' def title_text(self): if self.player.participant.language == 'en': return 'Please wait' return 'Bitte warten' def body_text(self): if self.player.participant.language == 'en': return 'Please wait until your partner is ready. We will continue as soon as both participants are present.' return 'Bitte warten Sie, bis Ihr*e Mitspieler*in bereit ist. Es geht weiter sobald beide Teilnehmer*innen anwesend sind.' class Chat(Page): timeout_seconds = C.CHAT_DURATION @staticmethod def is_displayed(player: Player): return player.participant.vars.get('communication') == 'communication' @staticmethod def vars_for_template(player: Player): lang = player.participant.language nickname = f'Participant {player.id_in_group}' if lang == 'en' else f'Teilnehmer*in {player.id_in_group}' return dict(chat_duration=C.CHAT_DURATION, nickname=nickname) class Contribute(Page): form_model = 'player' form_fields = ['contribution'] @staticmethod def vars_for_template(player: Player): player.endowment = C.ENDOWMENT return dict( endowment=player.endowment, threshold=C.THRESHOLD, multiplier=C.MULTIPLIER, ) @staticmethod def error_message(player: Player, values): if values['contribution'] is not None: if values['contribution'] > C.ENDOWMENT: return f'Sie können nicht mehr als {C.ENDOWMENT} Punkte beitragen.' if values['contribution'] < 0: return 'Sie können keine negativen Punkte beitragen.' class BeliefElicitation_Cont(Page): form_model = 'player' form_fields = ['belief_partner_contribution'] @staticmethod def vars_for_template(player: Player): return dict(endowment=C.ENDOWMENT) @staticmethod def error_message(player: Player, values): if values['belief_partner_contribution'] is not None: if values['belief_partner_contribution'] > C.ENDOWMENT: if player.participant.language == 'de': return f'Bitte geben Sie einen Wert zwischen 0 und {C.ENDOWMENT} ein.' return f'Please enter a value between 0 and {C.ENDOWMENT}.' class ResultsWaitPage(WaitPage): after_all_players_arrive = set_payoffs def title_text(self): if self.player.participant.language == 'en': return 'Please wait' return 'Bitte warten' def body_text(self): if self.player.participant.language == 'en': return 'Please wait until the other participant is ready.' return 'Bitte warten Sie bis der*die andere Teilnehmer*in bereit ist.' class Results(Page): @staticmethod def vars_for_template(player: Player): group = player.group other_player = player.get_others_in_group()[0] # Score belief belief_correct = ( abs(player.belief_partner_contribution - other_player.contribution) <= C.BELIEF_TOLERANCE ) belief_bonus = C.BELIEF_BONUS if belief_correct else 0 total_payoff = player.payoff_kept + belief_bonus return dict( endowment=player.endowment, total_contribution=group.total_contribution, group_connection=group.group_connection_initial, own_contribution=player.contribution, other_contribution=other_player.contribution, points_kept=player.endowment - player.contribution, payoff_kept=player.payoff_kept, threshold=C.THRESHOLD, payoff=player.payoff, belief_partner_contribution=player.belief_partner_contribution, belief_correct=belief_correct, belief_bonus=belief_bonus, total_payoff=total_payoff, ) @staticmethod def before_next_page(player, timeout_happened): other = player.get_others_in_group()[0] belief_correct = abs(player.belief_partner_contribution - other.contribution) <= C.BELIEF_TOLERANCE belief_bonus = C.BELIEF_BONUS if belief_correct else 0 player.participant.vars['points_p1'] = player.payoff_kept + belief_bonus player.participant.vars['group_connection_initial'] = player.group.group_connection_initial page_sequence = [SWBI, ChatWaitPage, Chat, Contribute, BeliefElicitation_Cont, ResultsWaitPage, Results]