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,
]