from otree.api import *
from markupsafe import Markup
from part1 import live_roll, set_automatic_report
from itertools import cycle
from random import shuffle, choices, randint
import csv
c = Currency
doc = """
Your app description
"""
class Constants(BaseConstants):
name_in_url = 'part2_intro'
players_per_group = None
group_size = 6
num_rounds = 1
part2_num_rounds = 1
part3_num_rounds = 20
timer_text = 'Time left:'
max_seconds_on_page = dict(
instructions=210, ex_page=20, ex_page_longer=30,
quiz_intro=20, quiz_long=60, quiz_short=50,
task_intro=60, dice_page=20, price_page=20
)
exchange_rate_ex_1 = c(6)
treatments = ['autoset', 'manualset', 'manualrand']
roles = ['A', 'B']
rules = [
'The result of the die is automatically reported',
'The result of the die is reported by A',
]
q_corrects = dict(q1=True, q2=True, q3=15, q4=4, q5=5, q6=0)
dice = [1, 12]
dice_num_ex = 6
price_ex_1 = 5
price_ex_2 = 4
a_balance_ex = 7
bonus_for_object = 12
class Subsession(BaseSubsession):
pass
def creating_session(subsession: Subsession):
if subsession.round_number == 1:
const = Constants
players = subsession.get_players()
treatment = subsession.session.config.get('treatment')
if treatment:
if treatment == 'manualrand':
prices = get_prices_from_prev_sessions()
for p in players:
p.treatment = treatment
p.participant.vars['treatment'] = treatment
if treatment == 'manualrand':
p.price = const.price_ex_1
p.participant.vars['prices'] = choices(prices, k=const.part2_num_rounds + const.part3_num_rounds)
else:
treatments = const.treatments.copy()
shuffle(treatments)
treatments = cycle([t for t in treatments for _ in range(const.group_size)])
shuffle(players)
prices = get_prices_from_prev_sessions()
for p in players:
treatment = next(treatments)
p.treatment = treatment
p.participant.vars['treatment'] = treatment
if treatment == 'manualrand':
p.price = const.price_ex_1
p.participant.vars['prices'] = choices(prices, k=const.part2_num_rounds + const.part3_num_rounds)
def get_prices_from_prev_sessions() -> list:
with open('_static/data/data.csv', newline='') as f:
prices = [int(row.get('price')) for row in csv.DictReader(f)]
return prices
class Group(BaseGroup):
pass
class Player(BasePlayer):
timeout = models.BooleanField(initial=False)
dropout = models.BooleanField(initial=False)
treatment = models.StringField()
game_role = models.StringField(initial='')
rolled = models.BooleanField(initial=False)
num = models.IntegerField(initial=Constants.dice_num_ex)
report = models.IntegerField(
label="Please report the result of the die roll here:", min=Constants.dice[0], max=Constants.dice[1]
)
price = models.IntegerField(
label='Please set the price for the fictional object:', min=Constants.dice[0], max=Constants.dice[1]
)
q1 = models.BooleanField(
label=Markup(f'1. If you take on the role of {Constants.roles[0]}, what is your task?'),
widget=widgets.RadioSelect
)
q2 = models.BooleanField(
label=Markup(f'2. If you take on the role of {Constants.roles[1]}, what is your task?'),
widget=widgets.RadioSelect
)
q3 = models.IntegerField()
q4 = models.IntegerField()
q5 = models.IntegerField()
q6 = models.IntegerField()
# QUIZ FUNCTIONS
def q1_choices(player: Player):
choices = [(True, 'Reporting the balance in the die roll phase.')]
if player.treatment == 'manualrand':
choices.append((False, 'Confirming the random price in the trading phase.'))
else:
choices.append((False, 'Setting the price in the trading phase.'))
return choices
def q2_choices(player: Player):
choices = [(False, 'Reporting the balance in the die roll phase.')]
if player.treatment == 'manualrand':
choices.append((True, 'Confirming the random price in the trading phase.'))
else:
choices.append((True, 'Setting the price in the trading phase.'))
return choices
# PAGES
# Decorator of parent class' method
def default_vars_for_template(func):
def vars_for_template_with_default(*args, **kwargs):
_vars = _default_vars_for_template(*args, **kwargs)
page_vars = func(*args, **kwargs)
if page_vars:
_vars.update(page_vars)
return _vars
return vars_for_template_with_default
def _default_vars_for_template(player: Player):
const = Constants
return dict(
part_n=2,
role='A',
use_summary=True,
is_example=True,
rule=const.rules[0],
earnings_ex_1=const.exchange_rate_ex_1.to_real_world_currency(player.session),
)
class ExamplePage(Page):
timeout_seconds = Constants.max_seconds_on_page['ex_page']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player):
pass
class WaitingPart2(WaitPage):
body_text = "The next part starts when all participants arrive at this stage. " \
"Please be patient if other participants need more time."
wait_for_all_groups = True
class Instructions(Page):
timeout_seconds = Constants.max_seconds_on_page['instructions']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
role='',
use_summary=False,
is_example=False,
rule='',
pages_carousel=[f'part2_intro/includes/p{n}.html' for n in range(1, 4 + 1)]
)
class ExP1(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
rule=''
)
class ExP2(ExamplePage):
pass
class ExP3(ExamplePage):
@staticmethod
def live_method(player: Player, data):
live_roll(player, data)
@staticmethod
def before_next_page(player: Player, timeout_happened):
# Reset rolled field of the player so that die do not automatically rolls on page load in ExP8
player.rolled = False
class ExP4(ExamplePage):
pass
class ExP5(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
earnings=player.num - Constants.price_ex_1 + Constants.bonus_for_object
)
class ExP6(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
rule=''
)
class ExP7(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
rule=Constants.rules[1]
)
class ExP8(ExamplePage):
form_model = 'player'
form_fields = ['report']
@staticmethod
def live_method(player: Player, data):
live_roll(player, data)
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
rule=Constants.rules[1]
)
@staticmethod
def before_next_page(player: Player, timeout_happened):
set_automatic_report(player, timeout_happened)
class ExP9(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
if player.report >= Constants.price_ex_2:
traded = True
earnings = player.report - Constants.price_ex_2 + Constants.bonus_for_object
else:
traded = False
earnings = player.report
return dict(
traded=traded,
earnings=earnings
)
class ExP10(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
role=Constants.roles[1],
rule=''
)
class ExP11(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
role=Constants.roles[1],
rule=Constants.rules[1]
)
class ExP12(ExamplePage):
form_model = 'player'
@staticmethod
def get_form_fields(player):
fields = []
if player.participant.vars['treatment'] != 'manualrand':
fields.append('price')
return fields
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(
role=Constants.roles[1],
rule=Constants.rules[1],
price=player.price if player.participant.vars['treatment'] == 'manualrand' else 0
)
@staticmethod
def before_next_page(player: Player, timeout_happened):
if timeout_happened:
player.timeout = True
if player.participant.vars['treatment'] != 'manualrand':
const = Constants
player.price = randint(const.dice[0], const.dice[1])
class ExP13(ExamplePage):
@staticmethod
@default_vars_for_template
def vars_for_template(player):
traded = player.price <= Constants.a_balance_ex
return dict(
role=Constants.roles[1],
rule=Constants.rules[1],
traded=traded,
earnings=player.price * traded
)
class QuizIntro(Page):
timeout_seconds = Constants.max_seconds_on_page['quiz_intro']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(role='', rule='', is_example=False)
class Quiz1(Page):
form_model = 'player'
form_fields = ['q1', 'q2']
timeout_seconds = Constants.max_seconds_on_page['quiz_long']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player):
return dict(role='', rule='', is_example=False)
@staticmethod
def error_message(player: Player, values):
errors = {k: 'Please, select the correct answer' for k, v in values.items() if v != Constants.q_corrects[k]}
return errors
class Quiz2(Page):
form_model = 'player'
form_fields = ['q3', 'q4']
timeout_seconds = Constants.max_seconds_on_page['quiz_short']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player: Player):
q3_label_dynamic = "A random price of 5 is selected." if player.treatment == 'manualrand' else \
f"{Constants.roles[1]} sets a price of 5"
q4_label_dynamic = "A random price of 6 is selected." if player.treatment == 'manualrand' else \
f"{Constants.roles[1]} sets a price of 6"
q3_label = f"3. Example I: You are acting in the role of {Constants.roles[0]} and reported a balance " \
f"of 8 in the die roll phase. {q3_label_dynamic}. " \
f"What are your earnings at the end of the interaction?"
q4_label = f"4. Example II: You are acting in the role of {Constants.roles[0]} and reported a balance " \
f"of 4 in the die roll phase. {q4_label_dynamic}. " \
f"What are your earnings at the end of the interaction?"
return dict(role='', rule='', q3_label=q3_label, q4_label=q4_label, is_example=False)
class Quiz3(Page):
timeout_seconds = Constants.max_seconds_on_page['quiz_short']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player: Player):
q3_label_dynamic = "A random price of 5 is selected." if player.treatment == 'manualrand' else \
f"{Constants.roles[1]} sets a price of 5"
q4_label_dynamic = "A random price of 6 is selected." if player.treatment == 'manualrand' else \
f"{Constants.roles[1]} sets a price of 6"
q3_label = f"3. Example I: You are acting in the role of {Constants.roles[0]} and reported a balance " \
f"of 8 in the die roll phase. {q3_label_dynamic}. " \
f"What are your earnings at the end of the interaction?"
q4_label = f"4. Example II: You are acting in the role of {Constants.roles[0]} and reported a balance " \
f"of 4 in the die roll phase. {q4_label_dynamic}. " \
f"What are your earnings at the end of the interaction?"
return dict(role='', rule='', q3_label=q3_label, q4_label=q4_label, is_example=False)
class Quiz4(Page):
form_model = 'player'
form_fields = ['q5', 'q6']
timeout_seconds = Constants.max_seconds_on_page['quiz_short']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player: Player):
label_dynamic = "select a random price of 5" if player.treatment == 'manualrand' else "set a price of 5"
q5_label = f"5. Example III: You are acting in the role of {Constants.roles[1]} and {label_dynamic}. " \
f"You are told that {Constants.roles[0]}'s balance was sufficient, and the trade takes " \
f"place. What are your earnings at the end of the interaction?"
q6_label = f"6. Example IV: You are acting in the role of {Constants.roles[1]} and {label_dynamic}. " \
f"You are told that {Constants.roles[0]}'s balance was not sufficient, and the trade does " \
f"not take place. What are your earnings at the end of the interaction?"
return dict(role='', rule='', q5_label=q5_label, q6_label=q6_label, is_example=False)
class Quiz5(Page):
timeout_seconds = Constants.max_seconds_on_page['quiz_short']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player: Player):
label_dynamic = "select a random price of 5" if player.treatment == 'manualrand' else "set a price of 5"
q5_label = f"5. Example III: You are acting in the role of {Constants.roles[1]} and {label_dynamic}. " \
f"You are told that {Constants.roles[0]}'s balance was sufficient, and the trade takes " \
f"place. " \
f"What are your earnings at the end of the interaction?"
q6_label = f"6. Example IV: You are acting in the role of {Constants.roles[1]} and {label_dynamic}. " \
f"You are told that {Constants.roles[0]}'s balance was not sufficient, and the trade does " \
f"not take place. What are your earnings at the end of the interaction?"
return dict(role='', rule='', q5_label=q5_label, q6_label=q6_label, is_example=False)
class TaskIntro(Page):
timeout_seconds = Constants.max_seconds_on_page['task_intro']
timer_text = Constants.timer_text
@staticmethod
@default_vars_for_template
def vars_for_template(player: Player):
return dict(is_example=False, role='', rule='')
@staticmethod
def before_next_page(player: Player, timeout_happened):
if timeout_happened:
player.dropout = True
player.participant.vars['dropout'] = True
@staticmethod
def app_after_this_page(player, upcoming_apps):
if player.participant.vars.get('dropout'):
return upcoming_apps[-1]
def get_subcls(cls):
for subclass in cls.__subclasses__():
yield from get_subcls(subclass)
yield subclass
page_sequence = [WaitingPart2, Instructions] + \
[p for p in get_subcls(Page) if 'ExP' in p.__name__] + \
[QuizIntro] + \
[p for p in get_subcls(Page) if 'Quiz' in p.__name__ and any(i.isdigit() for i in p.__name__)] + \
[TaskIntro]
# page_sequence = [TaskIntro]