# TODO: Points are always rounded to integers => introduce new field or change payoff factors # from otree.api import * from otree.api import ( BaseConstants, BaseSubsession, BaseGroup, BasePlayer, models, widgets, Page, WaitPage, ) import random doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = "rotations" PLAYERS_PER_GROUP = 3 NUM_ROUNDS = 3 # TODO: set to 48 at the end A_ROLE = "a" # participant a B_ROLE = "b" # participant b C_ROLE = "c" # participant c class Subsession(BaseSubsession): pass class Group(BaseGroup): treatment = models.StringField() lumpsum = models.BooleanField() # whether group is in lumspum treatment high_payoffs = models.BooleanField() # whether group is in high-payoffs treatment class Player(BasePlayer): # Control questions control_1a = models.IntegerField( min=0, max=99, label="Income participant a:" ) control_1b = models.IntegerField( min=0, max=99, label="Income participant b:" ) control_1c = models.IntegerField( min=0, max=99, label="Income participant c:" ) control_2a = models.IntegerField( min=0, max=99, label="Income participant a:" ) control_2b = models.IntegerField( min=0, max=99, label="Income participant b:" ) control_2c = models.IntegerField( min=0, max=99, label="Income participant c:" ) control_3a = models.IntegerField( min=0, max=99, label="Income participant a:" ) control_3b = models.IntegerField( min=0, max=99, label="Income participant b:" ) control_3c = models.IntegerField( min=0, max=99, label="Income participant c:" ) control_4a = models.IntegerField( min=0, max=99, label="Income participant a:" ) control_4b = models.IntegerField( min=0, max=99, label="Income participant b:" ) control_4c = models.IntegerField( min=0, max=99, label="Income participant c:" ) # Field the player chooses in current round field_choice = models.StringField( label="Please choose a field:", widget=widgets.RadioSelectHorizontal, choices=["Field A", "Field B", "Field C"], ) # Whether player had a timeout timeout = models.BooleanField(initial=False) # FUNCTIONS def creating_session(subsession): if subsession.round_number == 1: treatments = ["lumpsum_high", "lumpsum_low", "baseline_high", "baseline_low"] # Randomly assign group players to a role matrix = subsession.get_group_matrix() for row in matrix: random.shuffle(row) subsession.set_group_matrix(matrix) # Randomly assign groups to a treatment for group in subsession.get_groups(): group.treatment = random.choice(treatments) group.lumpsum = "lumpsum" in group.treatment group.high_payoffs = "high" in group.treatment else: subsession.group_like_round(1) # keep groups between rounds def set_payoffs(group: Group): g = group.in_round(1) p1 = group.get_player_by_id(1) p2 = group.get_player_by_id(2) p3 = group.get_player_by_id(3) # Base payoffs p1.payoff = 30 if g.lumpsum else 10 p2.payoff = 10 p3.payoff = 10 # Field payoffs for p in [p1, p2, p3]: # Determine payoff height through treatment factor = 10 if p.field_choice == ("Field " + p.role.upper() + ""): factor = 30 if g.high_payoffs else 11 # Determine number of other players on the same field num_others = -1 for p_other in [p1, p2, p3]: if p.field_choice == p_other.field_choice: num_others += 1 # Increase player payoff accordingly p.payoff += factor * num_others # PAGES class A_GeneralInformation(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class B_Instructions_1(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class C_Instructions_2(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class D_Instructions_3(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class E_Examples(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class F_Instructions_4(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class F_ControlQuestions(Page): form_model = "player" form_fields = [ "control_1a", "control_1b", "control_1c", "control_2a", "control_2b", "control_2c", "control_3a", "control_3b", "control_3c", "control_4a", "control_4b", "control_4c", ] @staticmethod def is_displayed(player): return player.round_number == 1 @staticmethod def error_message(player, values): g = player.group solutions = dict( # Lumpsum_High; Lumpsum_Low; Regular_High; Regular_Low control_1a=40 if g.lumpsum else 20, # 40; 40; 20; 20 control_1b=40 if g.high_payoffs else 21, # 40; 21; 40; 21 control_1c=10, # 10; 10; 10; 10 control_2a=40 if g.lumpsum else 20, # 40; 40; 20; 20 control_2b=10, # 10; 10; 10; 10 control_2c=40 if g.high_payoffs else 21, # 40; 21; 40; 21 control_3a=30 if g.lumpsum else 10, # 30; 30; 10; 10 control_3b=10, # 10; 10; 10; 10 control_3c=10, # 10; 10; 10; 10 control_4a=50 if g.lumpsum else 30, # 50; 50; 30; 30 control_4b=30, # 30; 30; 30; 30 control_4c=70 if g.high_payoffs else 32, # 70; 32; 70; 32 ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[ field_name ] = "This response is incorrect. Please try again. To re-read the instructions, click on the blue button above." return error_messages class G_ControlQuestions_1(Page): form_model = "player" form_fields = ["control_1a", "control_1b", "control_1c"] @staticmethod def is_displayed(player): return player.round_number == 1 @staticmethod def error_message(player, values): g = player.group solutions = dict( # Lumpsum_High; Lumpsum_Low; Regular_High; Regular_Low control_1a=40 if g.lumpsum else 20, # 40; 40; 20; 20 control_1b=40 if g.high_payoffs else 21, # 40; 21; 40; 21 control_1c=10, # 10; 10; 10; 10 ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[ field_name ] = "This response is incorrect. Please try again. To re-read the instructions, click on the blue button above." return error_messages class H_ControlQuestions_2(Page): form_model = "player" form_fields = ["control_2a", "control_2b", "control_2c"] @staticmethod def is_displayed(player): return player.round_number == 1 @staticmethod def error_message(player, values): g = player.group solutions = dict( # Lumpsum_High; Lumpsum_Low; Regular_High; Regular_Low control_2a=40 if g.lumpsum else 20, # 40; 40; 20; 20 control_2b=10, # 10; 10; 10; 10 control_2c=40 if g.high_payoffs else 21, # 40; 21; 40; 21 ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[ field_name ] = "This response is incorrect. Please try again. To re-read the instructions, click on the blue button above." return error_messages class I_ControlQuestions_3(Page): form_model = "player" form_fields = ["control_3a", "control_3b", "control_3c"] @staticmethod def is_displayed(player): return player.round_number == 1 @staticmethod def error_message(player, values): g = player.group solutions = dict( # Lumpsum_High; Lumpsum_Low; Regular_High; Regular_Low control_3a=30 if g.lumpsum else 10, # 30; 30; 10; 10 control_3b=10, # 10; 10; 10; 10 control_3c=10, # 10; 10; 10; 10 ) error_messages = dict() for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[ field_name ] = "This response is incorrect. Please try again. To re-read the instructions, click on the blue button above." return error_messages class J_GameStart(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class K_Choice(Page): form_model = "player" form_fields = ["field_choice"] timeout_seconds = 30 @staticmethod def before_next_page(player: Player, timeout_happened): # If player takes longer than 30s, choose a random field if timeout_happened: player.field_choice = random.choice( ["Field A", "Field B", "Field C"] ) player.timeout = True class ResultsWaitPage(WaitPage): after_all_players_arrive = set_payoffs class L_RoundReward(Page): form_model = "player" timeout_seconds = 30 class M_FinalResult(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS page_sequence = [ # A_GeneralInformation, # B_Instructions_1, # C_Instructions_2, D_Instructions_3, # E_Examples, # F_Instructions_4, F_ControlQuestions, # G_ControlQuestions_1, # H_ControlQuestions_2, # I_ControlQuestions_3, J_GameStart, K_Choice, ResultsWaitPage, L_RoundReward, M_FinalResult, ]