import itertools import random import time from otree.api import * doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = 'voting_app' PLAYERS_PER_GROUP = 2 NUM_ROUNDS = 10 PAYMENT_CORRECT_GUESS = cu(0.5) PAYMENT_QUIZ = cu(1) PAYMENT_VOTE = cu(5) PRIORITISE_NEW_PARTNERS = True MAX_WAITTIME_NEW_PARTNER = 60 class Subsession(BaseSubsession): pass class Group(BaseGroup): computer_signal_quality = models.FloatField() computer_signal = models.StringField() true_color = models.StringField() group_vote = models.StringField() group_vote_correctness = models.StringField() group_vote_correctness_with_tie = models.StringField() class Player(BasePlayer): is_treatment_group = models.BooleanField() round_is_paid = models.BooleanField(initial=False) continent = models.StringField( label="What continent lies directly south of Europe?", blank=True, choices=["Asia", "Africa", "Oceania"], ) toronto = models.StringField( label="In what North American country is the city Toronto located?", blank=True, choices=["Canada", "North America", "USA"], ) california = models.StringField( label="In what western U.S. state in the Silicon Valley?", blank=True, choices=["Arizona", "California", "Nevada"], ) prime = models.IntegerField( label="What is the smallest prime number greater than 3?", blank=True, choices=[5, 4, 7], ) water = models.StringField( label="What is the chemical symbol of water?", blank=True, choices=["W", "H2O", "He"], ) predator = models.StringField( label="What african predator is the fastest land animal", blank=True, choices=["Lion", "Cheetah", "Pronghorn"], ) atomic_bomb = models.StringField( label="On what country did the United States drop atomic bombs on during World War 2?", blank=True, choices=["Chernobyl", "Germany", "Japan"], ) communism = models.StringField( label="The Red Scare was a fear of what political system?", blank=True, choices=["Communism", "Terrorism", "Capitalism"], ) disney = models.StringField( label="Cinderella, The little Mermaid, Aladdin, and The Lion King are all films produced by what famous entertainment company?", blank=True, choices=["Warner", "Netflix", "Disney"], ) beattles = models.StringField( label="John Lennon, Paul McCartney, George Harrison and Ringo Starr were the four members of what famous classic rock band?", blank=True, choices=["The Beattles", "Rolling Stones", "Pink Floyd"], ) quiz_score = models.IntegerField() quiz_payoff = models.CurrencyField() own_score_estimate = models.IntegerField(min=0,max=10,label="Your Guess") own_score_estimate_payoff = models.CurrencyField(initial=0) other_score_estimate = models.IntegerField(min=0,max=10,label="Your Guess") other_score_estimate_payoff = models.CurrencyField(initial=0) signal_quality = models.FloatField() signal = models.StringField() guess_other_vote = models.BooleanField( label="Do you expect your team member to make a guess or not?", choices=[ [True,"Yes"], [False,"No"] ] ) voted = models.BooleanField( label="Please choose", choices=[ [True, "Guess in line with your signal"], [False, "Abstain"] ] ) vote_payoff = models.CurrencyField() age = models.IntegerField(mi=0, max=100) gender = models.StringField( choices=["Woman", "Man", "Other"] ) def creating_session(subsession: Subsession): treatment_cycle = itertools.cycle([True, True, False, False]) for p in subsession.get_players(): if p.round_number == 1: p.is_treatment_group = next(treatment_cycle) p.participant.vars["list_other_players_already_played_with"] = [] # set rounds paid list_of_rounds = list(range(1, C.NUM_ROUNDS+1)) random.shuffle(list_of_rounds) rounds_paid = list_of_rounds[:2] for round_paid in rounds_paid: p.in_round(round_paid).round_is_paid = True else: p.is_treatment_group = p.in_round(1).is_treatment_group def find_group(list_of_players): for p1 in list_of_players: for p2 in list_of_players: # check that players are not the same player if p1.id_in_subsession != p2.id_in_subsession and not p1.id_in_subsession in p2.participant.vars["list_other_players_already_played_with"]: p1.participant.vars["list_other_players_already_played_with"].append(p2.id_in_subsession) p2.participant.vars["list_other_players_already_played_with"].append(p1.id_in_subsession) return [p1,p2] # if the loop did not return anything we check if players are waiting longer than the maximum waittime list_of_players_above_max_waittime = [] for p in list_of_players: waittime = time.time() - p.participant.vars["time_started_waiting_" + str(p.round_number)] if waittime > C.MAX_WAITTIME_NEW_PARTNER: list_of_players_above_max_waittime.append(p) if len(list_of_players_above_max_waittime) >= 2: return list_of_players_above_max_waittime[:2] else: return False def group_by_arrival_time_method(subsession, waiting_players): wait_key = "time_started_waiting_" + str(subsession.round_number) for p in waiting_players: if wait_key not in p.participant.vars: p.participant.vars[wait_key] = time.time() treatment_players = [p for p in waiting_players if p.is_treatment_group] control_players = [p for p in waiting_players if not p.is_treatment_group] if C.PRIORITISE_NEW_PARTNERS: potential_group_treatment = find_group(treatment_players) # a list with values evaluates as true in an if check so we can just check if the value is true if potential_group_treatment: return potential_group_treatment else: potential_group_control = find_group(control_players) if potential_group_control: return potential_group_control else: if len(treatment_players) > C.PLAYERS_PER_GROUP: return treatment_players[:2] elif len(control_players) > C.PLAYERS_PER_GROUP: return control_players[:2] # PAGES class GroupingWaitPage(WaitPage): group_by_arrival_time = True # @staticmethod # def is_displayed(player: Player): # return player.round_number % 2 == 1 class Instructions(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Quiz(Page): form_model = "player" form_fields = ["continent","toronto","california","prime","water", "predator","atomic_bomb","communism","disney","beattles"] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): all_filled_out=True for field in ["continent","toronto","california","prime","water", "predator","atomic_bomb","communism","disney","beattles"]: if player.field_maybe_none(field) is None: all_filled_out = False if all_filled_out: player.quiz_payoff = C.PAYMENT_QUIZ player.payoff += player.quiz_payoff else: player.quiz_payoff = 0 # calculate score player.quiz_score = 0 if player.field_maybe_none("continent") == "Africa": player.quiz_score += 1 if player.field_maybe_none("toronto") == "Canada": player.quiz_score += 1 if player.field_maybe_none("california") == "California": player.quiz_score += 1 if player.field_maybe_none("prime") == 5: player.quiz_score += 1 if player.field_maybe_none("water") == "H2O": player.quiz_score += 1 if player.field_maybe_none("predator") == "Cheetah": player.quiz_score += 1 if player.field_maybe_none("atomic_bomb") == "Japan": player.quiz_score += 1 if player.field_maybe_none("communism") == "Communism": player.quiz_score += 1 if player.field_maybe_none("disney") == "Disney": player.quiz_score += 1 if player.field_maybe_none("beattles") == "The Beattles": player.quiz_score += 1 class OwnScoreEstimation(Page): form_model = "player" form_fields = ["own_score_estimate"] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): if player.own_score_estimate == player.quiz_score: player.own_score_estimate_payoff = C.PAYMENT_CORRECT_GUESS player.payoff += player.own_score_estimate_payoff if player.is_treatment_group: if player.quiz_score > 7: player.signal_quality = 0.7 else: player.signal_quality = 0.5 else: player.signal_quality = random.choice([0.7,0.5]) for i in range(1,C.NUM_ROUNDS+1): player.in_round(i).signal_quality = player.signal_quality class OtherScoreEstimation(Page): form_model = "player" form_fields = ["other_score_estimate"] # @staticmethod # def is_displayed(player: Player): # # print(player.participant.vars["list_other_players_already_played_with"]) # return player.round_number % 2 == 1 class BeforeDecisionWaitPage(WaitPage): @staticmethod def after_all_players_arrive(group: Group): # in rounds wit new grouping we randomly select the computers signal quality in other rounds we choose whatever was not chosen last round # if group.round_number % 2 == 1: # group.computer_signal_quality = random.choice([0.8,0.75]) # else: # if group.in_round(group.round_number-1).computer_signal_quality == 0.8: # group.computer_signal_quality = 0.75 # else: # group.computer_signal_quality = 0.8 group.computer_signal_quality = 0.8 # decide the true color group.true_color = random.choice(["blue", "red"]) # decide the signal of the computer if group.true_color == "blue": false_color = "red" else: false_color = "blue" if random.random() < group.computer_signal_quality: group.computer_signal = group.true_color else: group.computer_signal = false_color # decide the signal of the players for p in group.get_players(): if random.random() < p.signal_quality: p.signal = group.true_color else: p.signal = false_color # if the subjects made a guess about the other subjects trivia score in tis round it is evaluated #if group.round_number % 2 == 1: player_1 = group.get_player_by_id(1) player_2 = group.get_player_by_id(2) if player_1.other_score_estimate == player_2.in_round(1).quiz_score: player_1.other_score_estimate_payoff = C.PAYMENT_CORRECT_GUESS player_1.payoff += player_1.other_score_estimate_payoff if player_2.other_score_estimate == player_1.in_round(1).quiz_score: player_2.other_score_estimate_payoff = C.PAYMENT_CORRECT_GUESS player_2.payoff += player_2.other_score_estimate_payoff class GuessOtherVote(Page): form_model = "player" form_fields = ["guess_other_vote"] @staticmethod def vars_for_template(player: Player): other_player_signal_quality = player.get_others_in_group()[0].signal_quality return { "other_player_signal_quality": other_player_signal_quality, } class Vote(Page): form_model = "player" form_fields = ["voted"] class ResultsWaitPage(WaitPage): @staticmethod def after_all_players_arrive(group: Group): votes_red = 0 votes_blue = 0 # count votes for p in group.get_players(): if p.voted: if p.signal == "blue": votes_blue += 1 elif p.signal == "red": votes_red += 1 if group.computer_signal == "blue": votes_blue +=1 elif group.computer_signal == "red": votes_red += 1 # decide group vote and correctness includign tie if votes_red == votes_blue: group.group_vote = random.choice(["red", "blue"]) group.group_vote_correctness_with_tie = "tie" else: if votes_red > votes_blue: group.group_vote = "red" elif votes_red < votes_blue: group.group_vote = "blue" if group.group_vote == group.true_color: group.group_vote_correctness_with_tie = "correct" else: group.group_vote_correctness_with_tie = "false" # store vote correctness where only the outcome is considered regardless of whether it was random due to a tie if group.group_vote == group.true_color: group.group_vote_correctness = "correct" else: group.group_vote_correctness = "false" # in case of a tie if the color is correct by chance there is a payoff -> check the color not the correctness for the payoff for p in group.get_players(): # only if the vote is correct and this round is paid for a player we add the payoff if group.group_vote == group.true_color and p.round_is_paid: p.vote_payoff = C.PAYMENT_VOTE else: p.vote_payoff = 0 class Results(Page): @staticmethod def vars_for_template(player: Player): quiz_score = player.in_round(1).quiz_score return { "quiz_score": quiz_score, } class Survey(Page): form_model = "player" form_fields = ["age","gender"] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS class Payoffs(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS @staticmethod def vars_for_template(player: Player): participation_fee = player.session.config["participation_fee"] quiz_payoff = player.in_round(1).quiz_payoff guessing_payoff = sum([p.own_score_estimate_payoff for p in player.in_all_rounds()]) guessing_payoff += sum([p.other_score_estimate_payoff for p in player.in_all_rounds()]) vote_payoff = sum([p.vote_payoff for p in player.in_all_rounds()]) total_payoff = participation_fee + quiz_payoff + guessing_payoff + vote_payoff return { "participation_fee": participation_fee, "quiz_payoff": quiz_payoff, "guessing_payoff": guessing_payoff, "vote_payoff": vote_payoff, "total_payoff": total_payoff, } page_sequence = [ GroupingWaitPage, Instructions, Quiz, OwnScoreEstimation, OtherScoreEstimation, BeforeDecisionWaitPage, GuessOtherVote, Vote, ResultsWaitPage, Results, Survey, Payoffs ]