from otree.api import * import random doc = """ Ultimatum Game with random role assignment and slider offer input. Round 1 is practice (no payoffs shown), Round 2 is real. """ class C(BaseConstants): NAME_IN_URL = 'modified_split_the_pie' PLAYERS_PER_GROUP = 2 NUM_ROUNDS = 2 ENDOWMENT = cu(100) MODCORRECT_CODE = '567' class Subsession(BaseSubsession): pass class Group(BaseGroup): offer_amount = models.CurrencyField(min=0, max=C.ENDOWMENT) offer_accepted = models.IntegerField( label="Your decision:", choices=[ [1, 'Accept'], [0, 'Reject'], ], widget=widgets.RadioSelectHorizontal ) class Player(BasePlayer): is_proposer = models.BooleanField() modified_passcode = models.StringField(blank=True, label="") # ── FUNCTIONS ──────────────────────────────────────────────────────────────── def creating_session(subsession: Subsession): for group in subsession.get_groups(): players = group.get_players() random.shuffle(players) players[0].is_proposer = True players[1].is_proposer = False def get_proposer(group: Group): return next(p for p in group.get_players() if p.is_proposer) def get_responder(group: Group): return next(p for p in group.get_players() if not p.is_proposer) def set_payoffs(group: Group): proposer = get_proposer(group) responder = get_responder(group) # Only assign real payoffs in round 2; zero out for practice round if group.round_number == 2: if group.offer_accepted == 1: proposer.payoff = C.ENDOWMENT - group.offer_amount responder.payoff = group.offer_amount else: proposer.payoff = cu(0) responder.payoff = cu(0) else: proposer.payoff = cu(0) responder.payoff = cu(0) # ── PAGES ──────────────────────────────────────────────────────────────────── class ModifiedIntroduction(Page): form_model = 'player' form_fields = ['modified_passcode'] def is_displayed(player): return player.round_number == 1 def error_message(player, values): if values['modified_passcode'].strip().upper() != MODCORRECT_CODE.upper(): return 'Incorrect code. Please try again.' class Propose(Page): form_model = 'group' form_fields = ['offer_amount'] @staticmethod def is_displayed(player: Player): return player.is_proposer @staticmethod def vars_for_template(player: Player): return dict( endowment=int(C.ENDOWMENT), ) @staticmethod def js_vars(player: Player): return dict(endowment=int(C.ENDOWMENT)) class ProposerWaitPage(WaitPage): title_tag = 'Please Wait' body_text = 'Waiting for the Proposer to make their offer.' @staticmethod def is_displayed(player: Player): return not player.is_proposer class RespondWaitPage(WaitPage): title_tag = 'Please Wait' body_text = 'Waiting for the Responder to decide.' @staticmethod def is_displayed(player: Player): return player.is_proposer class Respond(Page): form_model = 'group' form_fields = ['offer_accepted'] @staticmethod def is_displayed(player: Player): return not player.is_proposer @staticmethod def vars_for_template(player: Player): group = player.group return dict( offer_amount=int(group.offer_amount), proposer_keeps=int(C.ENDOWMENT - group.offer_amount), endowment=int(C.ENDOWMENT), ) class ResultsWaitPage(WaitPage): after_all_players_arrive = set_payoffs def vars_for_template(player: Player): group = player.group return dict( offer_amount=int(group.field_maybe_none('offer_amount') or 0), proposer_keeps=int(C.ENDOWMENT - (group.field_maybe_none('offer_amount') or 0)), endowment=int(C.ENDOWMENT), ) class Results(Page): @staticmethod def is_displayed(player: Player): # Only show results in round 2 return player.round_number == 2 @staticmethod def vars_for_template(player: Player): return dict( is_proposer=player.is_proposer, offer_amount=int(player.group.offer_amount), offer_accepted=player.group.offer_accepted == 1, payoff=int(player.payoff), endowment=int(C.ENDOWMENT), ) class ModifiedBetweenRounds(Page): def vars_for_template(player): return { 'round_number': player.round_number, } def is_displayed(player): return player.round_number == 1 page_sequence = [ ModifiedIntroduction, Propose, ProposerWaitPage, Respond, ResultsWaitPage, Results, ModifiedBetweenRounds, ]