from otree.api import * import random author = 'Your name here' doc = """ Your app description """ class C(BaseConstants): NAME_IN_URL = 'individual_common_commitment_treatments' PLAYERS_PER_GROUP = 3 OTHER_PLAYERS_PER_GROUP = int(PLAYERS_PER_GROUP - 1) NUM_ROUNDS = 17 # NUM_ROUNDS has to be (a number divisible by 4) + 1. For example, if people are supposed to play # 8 rounds in total, #NUM_ROUNDS needs to be set to 8*2+1 = 17 half_NUM_ROUNDS = int(NUM_ROUNDS / 2) half_NUM_ROUNDS_plus_two = half_NUM_ROUNDS + 2 one_quarter_NUM_ROUNDS = int(NUM_ROUNDS * 1 / 4) one_quarter_NUM_ROUNDS_plus_two = one_quarter_NUM_ROUNDS + 2 three_quarters_NUM_ROUNDS = int(NUM_ROUNDS * 3 / 4) INVESTMENT_FACTOR = 2 BUDGET = 10 dictator_endowment = 20 number_intervals_vars_for_admin = 10 currency_name = "GBP" timeout = True timeout_seconds = 60 icg_and_ccg_treatment_included = True let_participants_continue_experiment_after_failed_attention_check = True how_many_players_not_paid_for_one_player_paid = 9 player_paid = [False] * how_many_players_not_paid_for_one_player_paid + [True] how_many_dictator_restrictions_not_exercised_for_one_dictator_restriction_exercised = 9 dictator_restriction_exercised = [ False] * how_many_dictator_restrictions_not_exercised_for_one_dictator_restriction_exercised + [ True] one_out_of_x_players_paid = how_many_players_not_paid_for_one_player_paid + 1 min_seconds_free_text_field = 30 choices_for_GPS_questions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] class Subsession(BaseSubsession): pass class Group(BaseGroup): total_contribution = models.FloatField(initial=0) total_profit = models.FloatField() timeout = models.BooleanField(initial=False) class Player(BasePlayer): contribution = models.FloatField( min=0, max=C.BUDGET, label="How much do you want to invest?" ) profit = models.FloatField() round_paid = models.IntegerField() minimum_donation_amount = models.FloatField( label="What is the minimum percentage that the future experiment participant must donate to Atmosfair?", min=0, max=100) expected_donation_amount = models.FloatField( label="What do you think, what fraction of the endowment of the future experiment participant do the other participants expect him to give to the charity?", min=0, max=100) free_text_field = models.LongStringField(label="How did you come up with a number for the restriction decision of " "the future experiment participant? Please give a brief motivation.") selected_for_payment = models.BooleanField() selected_for_dictator_restriction = models.BooleanField() attention_check_ICG_1 = models.BooleanField( label="What happens to the investment into the public project?", choices=[ [False, "Only my investment will be doubled"], [False, "Only the other players' investments will be doubled"], [True, "All investments will be doubled"], ], ) attention_check_ICG_2 = models.BooleanField( label="You have an endowment of 10 GBP and invest 4 GBP into the public project. The two other players decide " "to invest 3 GBP and 8 GBP, respectively. How much money do you have in total after the reward from the " "public project has been paid out?", choices=[ [False, "14"], [True, "16"], [False, "18"], ], ) attention_check_CCG_1 = models.BooleanField( label="What defines the investment into the public project for all players?", choices=[ [False, "The maximum amount invested by any of the players"], [True, "The minimum amount invested by any of the players"], [False, "Each player's investment into the public project is independent and can therefore differ from " "the investments made by other players"], ], ) attention_check_CCG_2 = models.BooleanField( label="You have an endowment of 10 GBP and invest 2 GBP into the public project. The two other players decide " "to invest 3 GBP and 6 GBP, respectively. How much money do you have in total after the reward from the " "public project has been paid out?", choices=[ [True, "12"], [False, "13"], [False, "16"], ], ) bonus = models.FloatField(initial=0) WP13420 = models.IntegerField(label="How willing are you to punish someone who treats others unfairly, even if " "there may be costs for you?", choices=C.choices_for_GPS_questions, widget=widgets.RadioSelectHorizontal) WP13421 = models.IntegerField(label="How willing are you to give to good causes without expecting anything in " "return?", choices=C.choices_for_GPS_questions, widget=widgets.RadioSelectHorizontal) WP13422 = models.IntegerField(label="When someone does me a favor, I am willing to return it.", choices=C.choices_for_GPS_questions, widget=widgets.RadioSelectHorizontal) WP13458 = models.StringField(label="Do you give one of the presents to the stranger as a “thank you” gift?", choices=["Yes", "No"]) WP13458_yes = models.StringField(label="Which present do you give to the stranger?", initial="No present", choices=[f"The present worth 5 {C.currency_name}", f"The present worth 10 {C.currency_name}", f"The present worth 15 {C.currency_name}", f"The present worth 20 {C.currency_name}", f"The present worth 25 {C.currency_name}", f"The present worth 30 {C.currency_name}", "No present"], ) WP13459 = models.FloatField( label=f"Imagine the following situation: Today you unexpectedly received 1000 {C.currency_name}. How much of this amount would you donate to a good cause?", min=0, max=1000) prolific_id = models.StringField(label="Prolific ID") # FUNCTIONS def creating_session(subsession): counter = 1 for p in subsession.get_players(): if C.icg_and_ccg_treatment_included: if 1 <= counter <= C.PLAYERS_PER_GROUP: treatment = "ICG" if C.PLAYERS_PER_GROUP + 1 <= counter <= 2 * C.PLAYERS_PER_GROUP: treatment = "CCG" if 2 * C.PLAYERS_PER_GROUP + 1 <= counter <= 3 * C.PLAYERS_PER_GROUP: treatment = "ICG_then_CCG" counter += 1 if counter == 3 * C.PLAYERS_PER_GROUP + 1: counter = 1 if treatment == "ICG": p.participant.vars["treatment"] = "ICG" elif treatment == "CCG": p.participant.vars["treatment"] = "CCG" else: p.participant.vars["treatment"] = "ICG_then_CCG" else: if 1 <= counter <= C.PLAYERS_PER_GROUP: treatment = "ICG" if C.PLAYERS_PER_GROUP + 1 <= counter <= 2 * C.PLAYERS_PER_GROUP: treatment = "CCG" counter += 1 if counter == 2 * C.PLAYERS_PER_GROUP + 1: counter = 1 if treatment == "ICG": p.participant.vars["treatment"] = "ICG" if treatment == "CCG": p.participant.vars["treatment"] = "CCG" for p in subsession.get_players(): p.participant.vars["passed_all_attention_checks"] = False p.in_round(C.NUM_ROUNDS).selected_for_payment = random.choice(C.player_paid) p.in_round(C.NUM_ROUNDS).selected_for_dictator_restriction = random.choice(C.dictator_restriction_exercised) class Prolific(Page): form_model = "player" form_fields = ["prolific_id"] @staticmethod def is_displayed(player: Player): return player.round_number == 1 class InstructionsICG(Page): @staticmethod def is_displayed(player: Player): return ((player.participant.vars["treatment"] == "ICG" and player.round_number == 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and player.round_number == 1)) @staticmethod def vars_for_template(player: Player): return { "treatment": player.participant.vars["treatment"] } class InstructionsCCG(Page): @staticmethod def is_displayed(player: Player): return ((player.participant.vars["treatment"] == "CCG" and player.round_number == 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and player.round_number == C.half_NUM_ROUNDS + 2)) @staticmethod def vars_for_template(player: Player): return { "treatment": player.participant.vars["treatment"] } class AttentionCheckICG(Page): form_model = "player" form_fields = ["attention_check_ICG_1", "attention_check_ICG_2"] @staticmethod def is_displayed(player: Player): return ((player.participant.vars["treatment"] == "ICG" and player.round_number == 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and player.round_number == 1)) @staticmethod def before_next_page(player: Player, timeout_happened): all_correct = ( player.attention_check_ICG_1 and player.attention_check_ICG_2 ) if all_correct: player.participant.vars["passed_all_attention_checks"] = True class AttentionCheckCCG(Page): form_model = "player" form_fields = ["attention_check_CCG_1", "attention_check_CCG_2"] @staticmethod def is_displayed(player: Player): return ((player.participant.vars["treatment"] == "CCG" and player.round_number == 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and player.round_number == C.half_NUM_ROUNDS + 2)) @staticmethod def before_next_page(player: Player, timeout_happened): all_correct = ( player.attention_check_CCG_1 and player.attention_check_CCG_2 ) if all_correct: player.participant.vars["passed_all_attention_checks"] = True class FailedAttentionCheck(Page): @staticmethod def is_displayed(player: Player): return ( ((player.participant.vars["treatment"] == "ICG" and player.round_number == 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and player.round_number == 1)) and not player.participant.vars[ "passed_all_attention_checks"] and not C.let_participants_continue_experiment_after_failed_attention_check or ((player.participant.vars["treatment"] == "CCG" and player.round_number == 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and player.round_number == C.half_NUM_ROUNDS + 1)) and not player.participant.vars[ "passed_all_attention_checks"] and not C.let_participants_continue_experiment_after_failed_attention_check ) class ContributionAboutToBegin(Page): @staticmethod def is_displayed(player: Player): return ((player.participant.vars["treatment"] == "ICG" and player.round_number == 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and player.round_number == 1) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check) or (player.participant.vars["treatment"] == "CCG" and player.round_number == 1) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check) ) class SecondTreatmentAllocation(WaitPage): @staticmethod def is_displayed(player: Player): return ((player.round_number == 2) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) group_by_arrival_time = True def group_by_arrival_time_method(subsession, waiting_players): print('in group_by_arrival_time_method') icg_players = [p for p in waiting_players if p.participant.vars["treatment"] == "ICG"] ccg_players = [p for p in waiting_players if p.participant.vars["treatment"] == "CCG"] icg_ccg_players = [p for p in waiting_players if p.participant.vars["treatment"] == "ICG_then_CCG"] print(icg_players, ccg_players, icg_ccg_players) if len(icg_players) >= 3: print('about to create a group') return [icg_players[0], icg_players[1], icg_players[2]] if len(ccg_players) >= 3: print('about to create a group') return [ccg_players[0], ccg_players[1], ccg_players[2]] if len(icg_ccg_players) >= 3: print('about to create a group') return [icg_ccg_players[0], icg_ccg_players[1], icg_ccg_players[2]] print('not enough players yet to create a group') # PAGES class Contribution(Page): @staticmethod def is_displayed(player: Player): return ( (((player.participant.vars[ "treatment"] == "ICG" and 2 <= player.round_number <= C.half_NUM_ROUNDS + 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and C.one_quarter_NUM_ROUNDS + 2 <= player.round_number <= C.half_NUM_ROUNDS + 1)) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check) or ((player.participant.vars["treatment"] == "CCG" and player.round_number >= C.half_NUM_ROUNDS + 2) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and C.half_NUM_ROUNDS + 2 <= player.round_number <= C.three_quarters_NUM_ROUNDS + 1)) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) and not player.group.timeout ) form_model = "player" form_fields = ["contribution"] if C.timeout: timeout_seconds = C.timeout_seconds @staticmethod def before_next_page(player: Player, timeout_happened): if timeout_happened: print("timeout_happened") for i in range(player.round_number, C.NUM_ROUNDS + 1): player.group.in_round(i).timeout = True @staticmethod def vars_for_template(player: Player): if player.participant.vars["treatment"] == "CCG": round_number = player.round_number - C.half_NUM_ROUNDS elif player.participant.vars["treatment"] == "ICG_then_CCG": round_number = player.round_number - C.one_quarter_NUM_ROUNDS else: round_number = player.round_number return { "passed_attention_check": player.participant.vars["passed_all_attention_checks"], "treatment": player.participant.vars["treatment"], "round_number_new": round_number - 1, } def calculate_profits_icg(group): for p in group.get_players(): p.group.total_contribution += p.contribution group.total_profit = group.total_contribution * C.INVESTMENT_FACTOR for p in group.get_players(): p.profit = round((C.BUDGET - p.contribution) + p.group.total_profit / C.PLAYERS_PER_GROUP, 2) def calculate_profits_ccg(group): list_of_all_contributions = [] for p in group.get_players(): list_of_all_contributions.append(p.contribution) for p in group.get_players(): p.profit = ((C.BUDGET - min(list_of_all_contributions)) + min(list_of_all_contributions) * C.INVESTMENT_FACTOR) class ResultsWaitPageICG(WaitPage): @staticmethod def is_displayed(player: Player): return ( ((player.participant.vars[ "treatment"] == "ICG" and 2 <= player.round_number <= C.half_NUM_ROUNDS + 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and C.one_quarter_NUM_ROUNDS + 2 <= player.round_number <= C.half_NUM_ROUNDS + 1)) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check) and not player.group.timeout ) after_all_players_arrive = calculate_profits_icg class ResultsWaitPageCCG(WaitPage): @staticmethod def is_displayed(player: Player): return ( ((player.participant.vars["treatment"] == "CCG" and player.round_number >= C.half_NUM_ROUNDS + 2) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and C.half_NUM_ROUNDS + 2 <= player.round_number <= C.three_quarters_NUM_ROUNDS + 1)) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check) and not player.group.timeout ) after_all_players_arrive = calculate_profits_ccg class ResultsICG(Page): if C.timeout: timeout_seconds = C.timeout_seconds @staticmethod def is_displayed(player: Player): return ( ((player.participant.vars[ "treatment"] == "ICG" and 2 <= player.round_number <= C.half_NUM_ROUNDS + 1) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and C.one_quarter_NUM_ROUNDS + 2 <= player.round_number <= C.half_NUM_ROUNDS + 1)) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check) and not player.group.timeout ) @staticmethod def vars_for_template(player: Player): list_of_contributions_ICG = [] list_of_profits_ICG = [] for p in player.get_others_in_group(): if p.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check: list_of_contributions_ICG.append(p.contribution) list_of_profits_ICG.append(p.profit) return { "list_of_contributions": list_of_contributions_ICG, "total_contribution_times_two": player.group.total_contribution * C.INVESTMENT_FACTOR, "evenly_distributed_share": round( player.group.total_contribution * C.INVESTMENT_FACTOR / C.PLAYERS_PER_GROUP, 2), "group_total_contribution_ICG": player.group.total_contribution, "list_of_profits": list_of_profits_ICG, "treatment": player.participant.vars["treatment"], } class ResultsCCG(Page): if C.timeout: timeout_seconds = C.timeout_seconds @staticmethod def is_displayed(player: Player): return ( ((player.participant.vars["treatment"] == "CCG" and player.round_number >= C.half_NUM_ROUNDS + 2) or ( player.participant.vars[ "treatment"] == "ICG_then_CCG" and C.half_NUM_ROUNDS + 2 <= player.round_number <= C.three_quarters_NUM_ROUNDS + 1)) and (player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check) and not player.group.timeout ) @staticmethod def vars_for_template(player: Player): list_of_contributions = [] list_of_all_contributions = [player.contribution] list_of_profits = [] for p in player.get_others_in_group(): # if p.treatment == "CCG" and p.participant.vars["passed_all_attention_checks"]: list_of_contributions.append(p.contribution) list_of_all_contributions.append(p.contribution) list_of_profits.append(p.profit) return { "list_of_contributions": list_of_contributions, "minimum_amount": min(list_of_all_contributions), "minimum_amount_doubled": min(list_of_all_contributions) * C.INVESTMENT_FACTOR, "list_of_profits": list_of_profits, } class DictatorRestriction(Page): @staticmethod def is_displayed(player: Player): return ( ((( player.participant.vars["treatment"] == "ICG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) or ( player.participant.vars["treatment"] == "CCG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) and not player.group.timeout ) form_model = "player" form_fields = ["minimum_donation_amount"] @staticmethod def before_next_page(player: Player, timeout_happened): if player.participant.vars["treatment"] == "CCG": player.round_paid = random.randint(C.half_NUM_ROUNDS + 2, C.NUM_ROUNDS) elif player.participant.vars["treatment"] == "ICG": player.round_paid = random.randint(2, C.half_NUM_ROUNDS + 1) else: player.round_paid = random.randint(C.one_quarter_NUM_ROUNDS + 2, C.three_quarters_NUM_ROUNDS + 1) class DictatorQuestions(Page): @staticmethod def is_displayed(player: Player): return ( ((( player.participant.vars["treatment"] == "ICG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) or ( player.participant.vars["treatment"] == "CCG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) and not player.group.timeout ) form_model = "player" form_fields = ["expected_donation_amount", "free_text_field"] class HeterogeneityElicitationI(Page): form_model = "player" form_fields = ["WP13420", "WP13421", "WP13422"] @staticmethod def is_displayed(player: Player): return ( ((( player.participant.vars["treatment"] == "ICG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) or ( player.participant.vars["treatment"] == "CCG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) and not player.group.timeout ) class HeterogeneityElicitationII(Page): form_model = "player" form_fields = ["WP13458", "WP13458_yes", "WP13459"] @staticmethod def is_displayed(player: Player): return ( ((( player.participant.vars["treatment"] == "ICG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) or ( player.participant.vars["treatment"] == "CCG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) and not player.group.timeout ) class PayoffICG(Page): @staticmethod def is_displayed(player: Player): return ( (( player.participant.vars["treatment"] == "ICG" and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check))) and not player.group.timeout ) @staticmethod def vars_for_template(player: Player): payoff_relevant_round = player.in_round(player.round_paid).profit if player.in_round(C.NUM_ROUNDS).selected_for_payment: player.bonus = payoff_relevant_round return { "round_paid": player.round_paid, "payoff_relevant_round": payoff_relevant_round, } class PayoffCCG(Page): @staticmethod def is_displayed(player: Player): return ( (( player.participant.vars["treatment"] == "CCG" or player.participant.vars[ "treatment"] == "ICG_then_CCG") and player.round_number == C.NUM_ROUNDS and ( player.participant.vars[ "passed_all_attention_checks"] or C.let_participants_continue_experiment_after_failed_attention_check)) and not player.group.timeout ) @staticmethod def vars_for_template(player: Player): payoff_relevant_round = player.in_round(player.round_paid).profit if player.in_round(C.NUM_ROUNDS).selected_for_payment: player.bonus = payoff_relevant_round return { "round_paid": player.round_paid, "payoff_relevant_round": payoff_relevant_round, } class Timeout(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS and player.group.timeout class ReturnToProlific(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS page_sequence = [SecondTreatmentAllocation, Prolific, InstructionsICG, InstructionsCCG, AttentionCheckICG, AttentionCheckCCG, FailedAttentionCheck, ContributionAboutToBegin, Contribution, ResultsWaitPageICG, ResultsWaitPageCCG, ResultsICG, ResultsCCG, DictatorRestriction, DictatorQuestions, HeterogeneityElicitationI, HeterogeneityElicitationII, PayoffICG, PayoffCCG, Timeout, ReturnToProlific]