from otree.api import * from constants import * import random doc = """Understanding Questions for Generation 2.""" class C(BaseConstants): NAME_IN_URL = 'E_PGGame_UQ_Gen2_D' PLAYERS_PER_GROUP = 3 NUM_ROUNDS = 1 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): Ana_generation = models.IntegerField() Ana_own_id = models.IntegerField() Ana_family_assignment = models.StringField() state = models.StringField() manual_entry = models.StringField(blank=True, label="Enter code (or leave blank to continue):") Ana_PQ_hp_token = models.IntegerField( label="If you were to play the Token Grabbing Task, how many of the 10 available tokens would you grab?", blank=False, min=0, max=10) Ana_PQ_hp_coin = models.IntegerField( label="If you were to play the Coin Flipping Task, how many times out of 10 do you think you would get heads?", blank=False, min=0, max=10) # Understanding questions UQ1 = models.IntegerField( label="""1) True or False? "In a round of the Decision Task, holding Member 1 and 2's contributions constant, if Member 3 decreases his/her contribution, his/her earnings will increase." """, choices=[ (1, 'True'), (2, 'False'), ], widget=widgets.RadioSelect, blank=True, ) UQ2 = models.IntegerField( label="2) If all three group members contribute their entire endowments to the joint project in a round of Decision Task, what will the outcome be?", choices=[ (1, 'Each member earns 30 ECUs'), (2, 'The high endowment member earns 30 ECUs. The low endowment members earn 15 ECUs.'), (3, 'The high endowment member earns 37.5 ECUs. The low endowment members earn 22.5 ECUs.'), ], widget=widgets.RadioSelect, blank=True, ) UQ3 = models.IntegerField( label="3) If all three group members contribute nothing to the joint project in a round of Decision Task, what will the outcome be?", choices=[ (1, 'Each member earns 30 ECUs'), (2, 'The high endowment member earns 30 ECUs. The low endowment members earn 15 ECUs.'), (3, 'The high endowment member earns 37.5 ECUs. The low endowment members earn 22.5 ECUs.'), ], widget=widgets.RadioSelect, blank=True, ) UQ4 = models.IntegerField( label="4) If each group member contributes 15 ECUs to the joint project in a round of the Decision Task, what will the outcome be?", choices=[ (1, 'Each member earns 30 ECUs'), (2, 'The high endowment member earns 30 ECUs. The low endowment members earn 15 ECUs.'), (3, 'The high endowment member earns 37.5 ECUs. The low endowment members earn 22.5 ECUs.'), ], widget=widgets.RadioSelect, blank=True, ) UQ5 = models.IntegerField( label="5) When assigning points to others based on their contributions, what does more points mean?", choices=[ (1, 'More approval'), (2, 'More disapproval'), ], widget=widgets.RadioSelect, blank=True, ) UQ6 = models.IntegerField( label="6) Will (i) assigning points or (ii) being assigned points affect your earnings directly in a round?", choices=[ (1, 'Both affect my earnings'), (2, 'Only ii) being assigned points affects my earnings'), (3, 'Neither affects my earnings'), ], widget=widgets.RadioSelect, blank=True, ) UQ7 = models.IntegerField( label="""7) True or False? "During the 16 rounds of the decision task, the group of families you will be interacting with will remain the same throughout." """, choices=[ (1, 'True'), (2, 'False'), ], widget=widgets.RadioSelect, blank=True, ) UQ8 = models.IntegerField( label="""8) True or False? "During the 16 rounds of the decision task, your endowment type (either low or high) remains fixed throughout." """, choices=[ (1, 'True'), (2, 'False'), ], widget=widgets.RadioSelect, blank=True, ) Ana_practice_contrib_high = models.IntegerField(label="", min=0, max=30, blank=False) Ana_practice_contrib_low = models.IntegerField(label="", min=0, max=15, blank=False) Ana_demo_group_id = models.IntegerField() Ana_demo_endowment = models.IntegerField() Ana_demo_contribution = models.IntegerField() def UQ1_error_message(player, value): if value != 1: return 'UQ1: Incorrect' def UQ2_error_message(player, value): if value != 1: return 'UQ2: Incorrect' def UQ3_error_message(player, value): if value != 2: return 'UQ3: Incorrect' def UQ4_error_message(player, value): if value != 1: return 'UQ4: Incorrect' class ManualDrop_G2(Page): form_model = 'player' form_fields = ['manual_entry'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def before_next_page(player: Player, timeout_happened=False): if player.manual_entry and player.manual_entry.strip().upper() == "QUIT": player.state = "inactive" player.participant.vars['state'] = "inactive" return session = player.session if session.vars.get('allowed_gen2') is None: _compute_allowed_gen2(player.subsession) allowed = session.vars['allowed_gen2'] session.vars['gen2_slot_counter'] += 1 slot = session.vars['gen2_slot_counter'] if slot <= allowed: player.Ana_own_id = slot player.participant.vars['Ana_own_id'] = slot player.state = "active" player.participant.vars['state'] = "active" session.vars['own_id_to_subsession_id'][f"gen2_{slot}"] = player.id_in_subsession else: player.state = "closed" player.participant.vars['state'] = "closed" class WaitForGen2(WaitPage): Timeout_seconds = 15 @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') in ("active", "closed")) class Gen2RedirectOrContinue(Page): @staticmethod def is_displayed(player): return False @staticmethod def app_after_this_page(player, upcoming_apps): state = player.participant.vars.get('state') if state in ("inactive", "closed"): if 'I_Post_Questionnaire_D' in upcoming_apps: return 'I_Post_Questionnaire_D' return upcoming_apps[-1] if upcoming_apps else None return None def _compute_allowed_gen2(subsession): all_players = subsession.get_players() active_gen2 = sum(1 for p in all_players if p.participant.vars.get('Ana_generation') == 2 and p.participant.vars.get('state') == "active") final_gen1 = subsession.session.vars.get('final_gen1_count', active_gen2) allowed = min(final_gen1, active_gen2) allowed = (allowed // 3) * 3 subsession.session.vars['allowed_gen2'] = allowed class UnderstandingQ0_G2(Page): @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def vars_for_template(player): return {'Ana_generation': player.participant.vars.get('Ana_generation'), 'round_number': player.round_number} class UnderstandingQ1_G2(Page): form_model = 'player' form_fields = ['UQ1', 'UQ2', 'UQ3', 'UQ4'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def error_message(player, values): player.participant.vars['Ana_UQ_attempts_page1'] = player.participant.vars.get('Ana_UQ_attempts_page1', 0) + 1 class UnderstandingQ2_G2(Page): form_model = 'player' form_fields = ['UQ5', 'UQ6'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def error_message(player, values): player.participant.vars['Ana_UQ_attempts_page2'] = player.participant.vars.get('Ana_UQ_attempts_page2', 0) + 1 class UnderstandingQ3_G2(Page): form_model = 'player' form_fields = ['UQ7', 'UQ8'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def error_message(player, values): player.participant.vars['Ana_UQ_attempts_page3'] = player.participant.vars.get('Ana_UQ_attempts_page3', 0) + 1 class Hypo0_G2(Page): @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") class Hypo_G2(Page): form_model = 'player' form_fields = ['Ana_PQ_hp_token', 'Ana_PQ_hp_coin'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") class DemoRound_Intro_G2(Page): @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def vars_for_template(player): return {'first_practice_order': player.participant.vars.get('first_practice_order')} class DemoRound_High_Endowment(Page): form_model = 'player' form_fields = ['Ana_practice_contrib_high'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active" and player.participant.vars.get('first_practice_order') == 0) @staticmethod def before_next_page(player, timeout_happened): player.participant.vars['Ana_practice_contrib_high'] = player.Ana_practice_contrib_high class DemoRound_Low_Endowment(Page): form_model = 'player' form_fields = ['Ana_practice_contrib_low'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def before_next_page(player, timeout_happened): player.participant.vars['Ana_practice_contrib_low'] = player.Ana_practice_contrib_low class DemoRound_High_Endowment_2(Page): form_model = 'player' form_fields = ['Ana_practice_contrib_high'] @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active" and player.participant.vars.get('first_practice_order') == 1) @staticmethod def before_next_page(player, timeout_happened): player.participant.vars['Ana_practice_contrib_high'] = player.Ana_practice_contrib_high class DemoRound_Calculation_G2(WaitPage): @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def after_all_players_arrive(group): subsession = group.subsession players = [p for p in subsession.get_players() if p.participant.vars.get('Ana_generation') == 2 and p.participant.vars.get('state') not in ("inactive", "closed")] random.shuffle(players) for group_idx, i in enumerate(range(0, len(players), 3)): grp = players[i:i + 3] if len(grp) != 3: continue high = random.choice(grp) for p in grp: p.Ana_demo_group_id = group_idx if p == high: p.Ana_demo_endowment = 30 p.Ana_demo_contribution = p.participant.vars.get('Ana_practice_contrib_high', 0) else: p.Ana_demo_endowment = 15 p.Ana_demo_contribution = p.participant.vars.get('Ana_practice_contrib_low', 0) total = sum(p.Ana_demo_contribution for p in grp) for p in grp: payoff = (p.Ana_demo_endowment - p.Ana_demo_contribution) + (1.5 * total) / 3 p.participant.vars['DemoRound_Payoff'] = payoff p.participant.vars['Ana_demo_endowment'] = p.Ana_demo_endowment p.participant.vars['Ana_demo_contribution'] = p.Ana_demo_contribution p.participant.vars['Ana_demo_group_id'] = p.Ana_demo_group_id class DemoRound_Outro(Page): @staticmethod def is_displayed(player): return (player.round_number == C.NUM_ROUNDS and player.participant.vars.get('Ana_generation') == 2 and player.participant.vars.get('state') == "active") @staticmethod def vars_for_template(player): return { 'DemoRound_Payoff': player.participant.vars.get('DemoRound_Payoff'), 'Ana_demo_endowment': player.participant.vars.get('Ana_demo_endowment'), 'Ana_demo_contribution': player.participant.vars.get('Ana_demo_contribution'), 'Ana_demo_group_id': player.participant.vars.get('Ana_demo_group_id'), } page_sequence = [ ManualDrop_G2, WaitForGen2, Gen2RedirectOrContinue, UnderstandingQ0_G2, UnderstandingQ1_G2, UnderstandingQ2_G2, UnderstandingQ3_G2, Hypo_G2, DemoRound_Intro_G2, DemoRound_High_Endowment, DemoRound_Low_Endowment, DemoRound_High_Endowment_2, DemoRound_Calculation_G2, DemoRound_Outro, ]