from otree.api import * import random doc = """ First main task """ class C(BaseConstants): NAME_IN_URL = 'main1' PLAYERS_PER_GROUP = 4 NUM_ROUNDS = 24 instructions_template = 'Instructions1.html' instructions_template1 = 'InstructionsProb.html' options = ['Do not protest', 'Protest'] player1_ROLE = 'Player 1' player2_ROLE = 'Player 2' player3_ROLE = 'Player 3' player4_ROLE = 'Player 4' treatments = [(0, 0), (0, 0), (0, 1), (0, 1), (1, 0), (1, 0), (1, 1), (1, 1), (1, 2), (1, 2), (2, 1), (2, 1), (2, 2), (2, 2), (2, 3), (2, 3), (3, 2), (3, 2), (3, 3), (3, 3), (4, 4), (4, 4), (4, 4), (4, 4)] random.seed(423789234) treatment_order = random.sample(treatments, len(treatments)) prob_rounds = [7, 10, 12, 17, 22] # NOTE: these are rounds starting from 1 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): proceed_code = models.IntegerField(label="If you have been told the proceed code, please enter it here: ") protest_action = models.IntegerField( choices=[[0, 'Do not protest'], [1, 'Protest']], widget=widgets.RadioSelect, ) knowledge_level = models.IntegerField() HOB1 = models.IntegerField() HOB2 = models.IntegerField() HOB3 = models.IntegerField() HOB1_success = models.BooleanField() HOB2_success = models.BooleanField() HOB3_success = models.BooleanField() prob1 = models.FloatField() prob2 = models.FloatField() prob3 = models.FloatField() # FUNCTIONS def other_players(player: Player): # get other player return dict(opp1=player.get_others_in_group()[0], opp2=player.get_others_in_group()[1], opp3=player.get_others_in_group()[2]) def prob_opponent(player: Player): group = player.group if player.role == 'Player 1': return group.get_player_by_role('Player 3') elif player.role == 'Player 2': return group.get_player_by_role('Player 4') elif player.role == 'Player 3': return group.get_player_by_role('Player 1') else: return group.get_player_by_role('Player 2') def creating_session(subsession): # group players randomly each round subsession.group_randomly(fixed_id_in_group=True) def role_to_number(player: Player): if player.role in ['Player 1', 'Player 2']: return 0 else: return 1 # PAGES class PauseMain1(Page): @staticmethod def is_displayed(player): if player.round_number == 1: return True else: return False form_model = 'player' form_fields = ['proceed_code'] @staticmethod def error_message(player: Player, values): ans = dict(proceed_code=643) errors = {f: 'The code you have entered is incorrect.' for f in ans if values[f] != ans[f]} if errors: return errors class Intro(Page): @staticmethod def is_displayed(player): if player.round_number == 1: return True else: return False class Decision(Page): form_model = 'player' form_fields = ['protest_action'] @staticmethod def vars_for_template(player: Player): # use appropriate player treatment player.knowledge_level = C.treatment_order[player.round_number - 1][role_to_number(player)] @staticmethod def before_next_page(player: Player, timeout_happened): if player.round_number == 1: player.participant.main_1_choices = [] player.participant.main_1_outcomes = [] player.participant.main_1_probs = [{}, {}, {}, {}, {}] player.session.symmetric_round_outcomes = [] player.participant.bonus_outcomes = [] class ProbabilityGuess(Page): @staticmethod def is_displayed(player): if player.round_number in C.prob_rounds: return True else: return False form_model = 'player' form_fields = ['HOB1', 'HOB2', 'HOB3'] @staticmethod def before_next_page(player: Player, timeout_happened): for i in range(5): if i == player.knowledge_level: player.participant.main_1_probs[i]['Primary'] = player.HOB1 player.participant.main_1_probs[i]['Secondary'] = player.HOB2 player.participant.main_1_probs[i]['Tertiary'] = player.HOB3 class ResultsWaitPage(WaitPage): @staticmethod def after_all_players_arrive(group: Group): for player in group.get_players(): if player.round_number in C.prob_rounds: opponent = prob_opponent(player) # assign variables for probability guesses HOB1_prob = player.HOB1 HOB2_prob = player.HOB2 HOB3_prob = player.HOB3 opponent_p1 = opponent.HOB1 opponent_p2 = opponent.HOB2 # use binary scoring rule to see if bonus received for each guess # level 1 if opponent.protest_action == 1: player.prob1 = 0.8*(1 - (1 - HOB1_prob/100)**2) else: player.prob1 = 0.8*(1 - (HOB1_prob/100)**2) player.HOB1_success = bool(random.choices(population=[1, 0], weights=[player.prob1, 1 - player.prob1])[0]) # level 2 player.prob2 = 0.8*(1 - ((opponent_p1 - HOB2_prob)/100)**2) player.HOB2_success = bool(random.choices(population=[1, 0], weights=[player.prob2, 1 - player.prob2])[0]) # level 3 player.prob3 = 0.8*(1 - ((opponent_p2 - HOB3_prob)/100)**2) player.HOB3_success = bool(random.choices(population=[1, 0], weights=[player.prob3, 1 - player.prob3])[0]) # update bonus payoff player.participant.bonus_outcomes.append(int(player.HOB1_success) + int(player.HOB2_success) + int(player.HOB3_success)) class Results(Page): @staticmethod def vars_for_template(player: Player): opponent = prob_opponent(player) opponents = other_players(player) opp1 = opponents['opp1'] opp2 = opponents['opp2'] opp3 = opponents['opp3'] protesters = player.protest_action + opp1.protest_action + opp2.protest_action + opp3.protest_action if protesters == 4: protest = True else: protest = False if player.round_number in C.prob_rounds: prob_show = True bonus = int(player.HOB1_success) + int(player.HOB2_success) + int(player.HOB3_success) if round(player.prob1 * 100) not in [0, 100]: prob100_1 = round(player.prob1 * 100) else: prob100_1 = round(player.prob1 * 100, 1) if round(player.prob2 * 100) not in [0, 100]: prob100_2 = round(player.prob2 * 100) else: prob100_2 = round(player.prob2 * 100, 1) if round(player.prob3 * 100) not in [0, 100]: prob100_3 = round(player.prob3 * 100) else: prob100_3 = round(player.prob3 * 100, 1) else: prob_show = False bonus = 0 prob100_1 = 0 prob100_2 = 0 prob100_3 = 0 return dict( protesters=protesters, others=protesters - player.protest_action, outcome=protest, same_choice=player.protest_action == opp1.protest_action == opp2.protest_action == opp3.protest_action, my_decision=player.field_display('protest_action'), prob_show=prob_show, HOB1_success=player.field_maybe_none('HOB1_success'), HOB2_success=player.field_maybe_none('HOB2_success'), HOB3_success=player.field_maybe_none('HOB3_success'), opponent_decision= opponent.field_display('protest_action'), opponent_p1=opponent.field_maybe_none('HOB1'), opponent_p2=opponent.field_maybe_none('HOB2'), bonus_payout=bonus, prob100_1=prob100_1, prob100_2=prob100_2, prob100_3=prob100_3) @staticmethod def before_next_page(player: Player, timeout_happened): opponents = other_players(player) opp1 = opponents['opp1'] opp2 = opponents['opp2'] opp3 = opponents['opp3'] protesters = player.protest_action + opp1.protest_action + opp2.protest_action + opp3.protest_action if protesters == 4: protest = True else: protest = False # store player's choice player.participant.main_1_choices.append(player.protest_action) # store outcome if protest: player.participant.main_1_outcomes.append(30) else: if player.protest_action == 1: player.participant.main_1_outcomes.append(0) else: player.participant.main_1_outcomes.append(10) # store outcomes of symmetrical treatments to be used in main task 2 opponent1_id = opp1.participant.id_in_session opponent2_id = opp2.participant.id_in_session opponent3_id = opp3.participant.id_in_session opponent1_level = opp1.knowledge_level opponent2_level = opp2.knowledge_level opponent3_level = opp3.knowledge_level opponent1_action = opp1.protest_action opponent2_action = opp2.protest_action opponent3_action = opp3.protest_action if player.role == 'Player 1': # avoid duplicating - only need to save for one player if player.knowledge_level == opponent1_level == opponent2_level == opponent3_level: player.session.symmetric_round_outcomes.append({'Players': [player.participant.id_in_session, opponent1_id, opponent2_id, opponent3_id], 'Treatment': player.knowledge_level, 'Choices': [player.protest_action, opponent1_action, opponent2_action, opponent3_action]}) page_sequence = [PauseMain1, Intro, Decision, ProbabilityGuess, ResultsWaitPage, Results]