from otree.api import * doc = """ Investment (Trust) Game Investor sends money -> tripled -> Responder returns some amount Round 1 is practice (no payoffs shown), Round 2 is real. """ class C(BaseConstants): NAME_IN_URL = 'investment_game' PLAYERS_PER_GROUP = 2 NUM_ROUNDS = 2 ENDOWMENT = cu(100) MULTIPLIER = 3 INVESTCORRECT_CODE = '888' class Subsession(BaseSubsession): pass def creating_session(subsession): subsession.group_randomly() class InvestIntroduction(Page): form_model = 'player' form_fields = ['invest_passcode'] def is_displayed(player): return player.round_number == 1 def error_message(player, values): if values['invest_passcode'].strip().upper() != INVESTCORRECT_CODE.upper(): return 'Incorrect code. Please try again.' class Group(BaseGroup): amount_sent = models.IntegerField( min=0, max=100, label="How much do you want to invest?" ) amount_returned = models.IntegerField( min=0, label="How much do you want to return?" ) class Player(BasePlayer): invest_passcode = models.StringField(blank=True, label="") def multiplied_amount(self): amount = self.group.field_maybe_none('amount_sent') if amount is None: return 0 return amount * C.MULTIPLIER def investor_payoff(self): amount_sent = self.group.field_maybe_none('amount_sent') or 0 amount_returned = self.group.field_maybe_none('amount_returned') or 0 return C.ENDOWMENT - amount_sent + amount_returned def responder_payoff(self): return self.multiplied_amount() - self.group.amount_returned class Investor(Page): form_model = 'group' form_fields = ['amount_sent'] @staticmethod def is_displayed(player: Player): return player.id_in_group == 1 @staticmethod def before_next_page(player: Player, timeout_happened): pass class WaitForInvestor(WaitPage): title_text = "Please wait" body_text = "Waiting for the investor to choose an amount." class Responder(Page): form_model = 'group' form_fields = ['amount_returned'] @staticmethod def is_displayed(player: Player): return player.id_in_group == 2 @staticmethod def vars_for_template(player: Player): amount_sent = player.group.field_maybe_none('amount_sent') return dict( amount_sent=amount_sent if amount_sent is not None else 0, tripled_amount=(amount_sent * C.MULTIPLIER) if amount_sent else 0, ) class WaitForResponder(WaitPage): title_text = "Please wait" body_text = "Waiting for the responder to make a decision." @staticmethod def after_all_players_arrive(group: Group): p1 = group.get_player_by_id(1) p2 = group.get_player_by_id(2) # Only assign real payoffs in round 2 if group.round_number == 2: p1.payoff = p1.investor_payoff() p2.payoff = p2.responder_payoff() else: # Round 1 is practice — zero out payoffs p1.payoff = cu(0) p2.payoff = cu(0) 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): amount_sent = player.group.field_maybe_none('amount_sent') amount_returned = player.group.field_maybe_none('amount_returned') return dict( amount_sent=amount_sent if amount_sent is not None else 0, amount_returned=amount_returned if amount_returned is not None else 0, tripled_amount=(amount_sent * C.MULTIPLIER) if amount_sent is not None else 0, payoff=player.payoff, ) class InvestBetweenRounds(Page): def vars_for_template(player): return { 'round_number': player.round_number, } def is_displayed(player): # Show between-rounds page after round 1 (practice), before round 2 return player.round_number == 1 page_sequence = [InvestIntroduction, Investor, WaitForInvestor, Responder, WaitForResponder, Results, InvestBetweenRounds]