from otree.api import ( BaseConstants, BaseSubsession, BaseGroup, BasePlayer, models, widgets, Page, WaitPage, ) import random doc = """ Your app description """ # CONSTANTS class C(BaseConstants): NAME_IN_URL = "rotations" PLAYERS_PER_GROUP = 3 NUM_ROUNDS = 48 # TODO: set to 48 at the end A_ROLE = "a" # role for participant a B_ROLE = "b" # role for participant b C_ROLE = "c" # role for participant c class Subsession(BaseSubsession): pass class Group(BaseGroup): treatment = models.StringField() # treatment name 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_1 = models.IntegerField( min=0, max=99, label="What is your payoff in this period?" ) control_2 = models.IntegerField( min=0, max=99, label="What is your payoff in this period?" ) control_3 = models.IntegerField( min=0, max=99, label="What is your payoff in this period?" ) control_4 = models.IntegerField( min=0, max=99, label="What is your payoff in this period?" ) # 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"], ) choice = models.CharField() # contains only "field letter" (e.g. "A") # Player payoffs without currency "points" at the end payoff_without_currency = models.IntegerField() # current_total_payoff = models.IntegerField(initial=0) # Whether player had a timeout # timeout = models.BooleanField(initial=False) # FUNCTIONS def creating_session(subsession): # At the start of the game if subsession.round_number == 1: treatments = [ "lumpsum_high", "baseline_high", "baseline_low", ] # removed "lumpsum_low" treatment # Randomly assign group players to a role matrix = subsession.get_group_matrix() # matches participant IDs and groups for row in matrix: # for every group random.shuffle(row) # shuffle participants (reassigns roles) subsession.set_group_matrix(matrix) # update matrix # Randomly assign groups to a treatment for group in subsession.get_groups(): group.treatment = random.choice(treatments) # choose a random treatment group.lumpsum = "lumpsum" in group.treatment # set flag group.high_payoffs = "high" in group.treatment # set flag # In all other rounds else: subsession.group_like_round(1) # keep groups between rounds for group in subsession.get_groups(): group.treatment = group.in_round(1).treatment # same groups as in round 1 # keep treatment between rounds group.lumpsum = "lumpsum" in group.treatment # set flag group.high_payoffs = "high" in group.treatment # set flag def set_payoffs(group: Group): g = group.in_round(1) p1 = group.get_player_by_id(1) # Participant a p2 = group.get_player_by_id(2) # Participant b p3 = group.get_player_by_id(3) # Participant c # 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 # change factor when on own field # Determine number of other players on the same field num_others = -1 # dummy value for p_other in [p1, p2, p3]: if p.field_choice == p_other.field_choice: # if other player chose field num_others += 1 # Increase player payoff accordingly p.payoff += factor * num_others p.payoff_without_currency = int(p.payoff) # for prev in p.in_previous_rounds(): # p.current_total_payoff += prev.payoff # PAGES class A_GeneralInformation(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 # only in the first round 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_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_1", "control_2", "control_3", "control_4", ] @staticmethod def is_displayed(player): return player.round_number == 1 @staticmethod def error_message(player, values): g = player.group # Define right answers to control questions depending on treatment if player.role == "a": solutions = dict( # Lumpsum_High; Lumpsum_Low; Regular_High; Regular_Low control_1=40 if g.lumpsum else 20, # 40; 40; 20; 20 control_2=40 if g.lumpsum else 20, # 40; 40; 20; 20 control_3=30 if g.lumpsum else 10, # 30; 30; 10; 10 control_4=50 if g.lumpsum else 30, # 50; 50; 30; 30 ) elif player.role == "b": solutions = dict( # Lumpsum_High; Lumpsum_Low; Regular_High; Regular_Low control_1=40 if g.high_payoffs else 21, # 40; 21; 40; 21 control_2=10, # 10; 10; 10; 10 control_3=10, # 10; 10; 10; 10 control_4=30, # 30; 30; 30; 30 ) else: solutions = dict( # Lumpsum_High; Lumpsum_Low; Regular_High; Regular_Low control_1=10, # 10; 10; 10; 10 control_2=40 if g.high_payoffs else 21, # 40; 21; 40; 21 control_3=10, # 10; 10; 10; 10 control_4=70 if g.high_payoffs else 32, # 70; 32; 70; 32 ) error_messages = dict() # Display error when answer was not correct for field_name in solutions: if values[field_name] != solutions[field_name]: error_messages[field_name] = ( "This response is incorrect. Please try again." ) return error_messages class G_GameStart(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == 1 class H_Choice(Page): form_model = "player" form_fields = ["field_choice"] # timeout_seconds = 30 @staticmethod def before_next_page(player: Player, timeout_happened): player.choice = player.field_choice[9] # 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 @staticmethod def vars_for_template(player): # Calculates total payoff of players without "points" at the end # Initial values total_prev_payoff_A = 0 total_prev_payoff_B = 0 total_prev_payoff_C = 0 # Sum payoffs over all previous periods for g in player.group.in_previous_rounds(): total_prev_payoff_A += g.get_player_by_id(1).payoff # Participant a total_prev_payoff_B += g.get_player_by_id(2).payoff # Participant b total_prev_payoff_C += g.get_player_by_id(3).payoff # Participant c # Remove currency at the end total_prev_payoff_A = int(total_prev_payoff_A) total_prev_payoff_B = int(total_prev_payoff_B) total_prev_payoff_C = int(total_prev_payoff_C) # Pass new variable to html template return dict( total_prev_payoff_A=total_prev_payoff_A, total_prev_payoff_B=total_prev_payoff_B, total_prev_payoff_C=total_prev_payoff_C, ) class ResultsWaitPage(WaitPage): after_all_players_arrive = set_payoffs class I_RoundReward(Page): form_model = "player" # timeout_seconds = 30 @staticmethod def vars_for_template(player): # Calculates total payoff of players without "points" at the end # Initial values total_prev_payoff_A = 0 total_prev_payoff_B = 0 total_prev_payoff_C = 0 # Sum payoffs over all previous periods for g in player.group.in_previous_rounds(): total_prev_payoff_A += g.get_player_by_id(1).payoff # Participant a total_prev_payoff_B += g.get_player_by_id(2).payoff # Participant b total_prev_payoff_C += g.get_player_by_id(3).payoff # Participant c # Remove currency at the end total_prev_payoff_A = int(total_prev_payoff_A) total_prev_payoff_B = int(total_prev_payoff_B) total_prev_payoff_C = int(total_prev_payoff_C) # Pass new variable to html template return dict( total_prev_payoff_A=total_prev_payoff_A, total_prev_payoff_B=total_prev_payoff_B, total_prev_payoff_C=total_prev_payoff_C, ) class J_FinalResult(Page): form_model = "player" @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS # only in the last round # Sequence of pages that are displayed to the player page_sequence = [ A_GeneralInformation, B_Instructions_1, C_Instructions_2, D_Instructions_3, E_Instructions_4, F_ControlQuestions, G_GameStart, H_Choice, ResultsWaitPage, I_RoundReward, J_FinalResult, ]