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