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]