from otree.api import * from markupsafe import Markup from part1 import live_roll, set_automatic_report from part2_intro import Constants as Part2IntroConstants, default_vars_for_template from itertools import cycle from random import shuffle from collections import defaultdict from random import randint from time import time c = Currency doc = """ Your app description """ class Constants(Part2IntroConstants): name_in_url = 'part2' players_per_group = None group_size = 6 num_rounds = 1 max_seconds_on_page = dict(task=20, price_page=20, task_trading_results=30, next_part_intro=90) pairs_ids = list(range(1, group_size, 2)) class Subsession(BaseSubsession): pass class Group(BaseGroup): treatment = models.StringField() has_unmatched = models.BooleanField() has_dropout = models.BooleanField(initial=False) class Player(BasePlayer): dropout = models.BooleanField(initial=False) timeout = models.BooleanField(initial=False) pair_id = models.IntegerField() game_role = models.StringField(initial='') rolled = models.BooleanField(initial=False) num = models.IntegerField(initial=0) 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] ) traded = models.BooleanField(initial=False) # FUNCTIONS def creating_session(subsession: Subsession): if subsession.round_number: session = subsession.session session.group_matrix = [] def set_groups(subssesion: Subsession): const = Constants session = subssesion.session ps = session.get_participants() group_matrix = session_group_matrix(session, ps) subssesion.set_group_matrix(group_matrix) groups = subssesion.get_groups() for group in groups: set_roles(group, const) set_group(group) def session_group_matrix(session, participants: list) -> list: const = Constants if not session.group_matrix: ps_active = [p.id_in_session for p in participants if not p.dropout and not p.no_consent] ps_inactive = [p.id_in_session for p in participants if p.dropout or p.no_consent] ps = ps_active + ps_inactive group_matrix = [ps[n:n + const.group_size] for n in range(0, len(ps), const.group_size)] session.group_matrix = group_matrix return session.group_matrix def set_roles(group: Group, const: Constants): roles = cycle(const.roles) for p in group.get_players(): game_role = next(roles) p.game_role = game_role p.participant.game_role = game_role def set_group(group: Group): const = Constants players = group.get_players() group.has_unmatched = set_unmatched(players) set_treatment(players, group) set_pairs(players, const) def set_unmatched(players: list) -> bool: const = Constants has_unmatched = False ps = [p.participant for p in players] if len(ps) < const.group_size or any(p.dropout or p.no_consent for p in ps): for p in ps: if not p.dropout and not p.no_consent: p.unmatched = True has_unmatched = True return has_unmatched def set_treatment(players: list, group: Group): group.treatment = players[0].participant.treatment def set_pairs(players: list, const: Constants): ps_by_role = {role: [p for p in players if p.participant.game_role == role] for role in const.roles} for role in const.roles: shuffle(ps_by_role[role]) pairs_ids = iter(const.pairs_ids) pairs = list(zip(*ps_by_role.values())) for pair in pairs: pair_id = next(pairs_ids) for p in pair: p.pair_id = pair_id def get_other_in_pair(player: Player): return [p for p in player.get_others_in_group() if p.pair_id == player.pair_id][0] def get_pairs_in_group(players) -> dict: pairs_ids = Constants.pairs_ids pairs = dict() for pair_id in pairs_ids: pairs[pair_id] = [p for p in players if p.pair_id == pair_id] return pairs def set_random_price(player: Player): participant = player.participant if participant.vars['treatment'] == 'manualrand': player.price = participant.vars['prices'].pop() def set_task_payoff(group: Group): const = Constants players = group.get_players() pairs = get_pairs_in_group(players) for pair in const.pairs_ids: ps = pairs.get(pair) if ps: a = [p for p in ps if p.participant.game_role == const.roles[0]][0] b = [p for p in ps if p.participant.game_role == const.roles[1]][0] if not any(p.participant.vars.get('dropout') for p in ps): traded = a.report >= b.price a.traded = b.traded = traded b.report = a.report a.price = b.price if traded: a.payoff = a.report - b.price + const.bonus_for_object b.payoff = b.price else: a.payoff = a.report b.payoff = 0 else: a.payoff = 0 b.payoff = 0 # PAGES class TaskPage(Page): timeout_seconds = Constants.max_seconds_on_page['task'] timer_text = Constants.timer_text @staticmethod @default_vars_for_template def vars_for_template(player: Player): rules = Constants.rules return dict( is_example=False, role=player.participant.vars['game_role'], rule=rules[0] if player.participant.vars['treatment'] == 'autoset' else rules[1], display_round_number=False ) class GroupingPage(WaitPage): wait_for_all_groups = True after_all_players_arrive = 'set_groups' @staticmethod def app_after_this_page(player, upcoming_apps): if player.participant.unmatched: return upcoming_apps[-3] class TaskRole(TaskPage): @staticmethod @default_vars_for_template def vars_for_template(player: Player): return dict( is_example=False, role=player.participant.game_role, rule='', display_round_number=False ) @staticmethod def before_next_page(player: Player, timeout_happened): set_random_price(player) class TaskRule(TaskPage): @staticmethod @default_vars_for_template def vars_for_template(player: Player): return dict( is_example=False, role=player.participant.game_role, rule='', display_round_number=False ) class TaskDice(TaskPage): form_model = 'player' @staticmethod def get_form_fields(player: Player): fields = ['report'] if player.participant.vars['treatment'] != 'autoset' else [] return fields @staticmethod def live_method(player: Player, data): live_roll(player, data) @staticmethod def is_displayed(player: Player): return player.participant.game_role == Constants.roles[0] @staticmethod def before_next_page(player: Player, timeout_happened): set_automatic_report(player, timeout_happened, player.participant.vars['treatment']) class TaskAutoset(TaskPage): @staticmethod def is_displayed(player: Player): return player.participant.game_role == Constants.roles[0] and \ player.participant.vars['treatment'] == 'autoset' class TaskTrading(TaskPage): 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: Player): rules = Constants.rules return dict( is_example=False, role=player.participant.game_role, rule=rules[0] if player.participant.vars['treatment'] == 'autoset' else rules[1], price=player.price if player.participant.vars['treatment'] == 'manualrand' else 0, display_round_number=False ) @staticmethod def is_displayed(player: Player): return player.participant.game_role == Constants.roles[1] @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 TaskTradingResultsWaitingPage(WaitPage): after_all_players_arrive = 'set_task_payoff' @staticmethod def app_after_this_page(player, upcoming_apps): if player.group.has_dropout: return upcoming_apps[-1] class TaskTradingResults(TaskPage): timeout_seconds = Constants.max_seconds_on_page['task_trading_results'] @staticmethod @default_vars_for_template def vars_for_template(player): rules = Constants.rules return dict( is_example=False, role=player.participant.game_role, rule=rules[0] if player.participant.vars['treatment'] == 'autoset' else rules[1], earnings=int(player.payoff), display_round_number=False ) class TaskResults(TaskPage): @staticmethod @default_vars_for_template def vars_for_template(player): earnings = int(player.payoff) player.participant.vars['payoff_part_2'] = earnings return dict( is_example=False, role=player.participant.game_role, rule='', earnings=earnings, display_round_number=False ) class NextPartIntro(TaskPage): timeout_seconds = Constants.max_seconds_on_page['next_part_intro'] @staticmethod @default_vars_for_template def vars_for_template(player): return dict( part_n=3, is_example=False, role=player.participant.game_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] page_sequence = [ GroupingPage, TaskRole, TaskRule, TaskDice, TaskAutoset, TaskTrading, TaskTradingResultsWaitingPage, TaskTradingResults, TaskResults, NextPartIntro ] # page_sequence = [GroupingPage]