from otree.api import * import random doc = """ a.k.a. Keynesian beauty contest. Players all guess a number; whoever guesses closest to 2/3 of the average wins. See https://en.wikipedia.org/wiki/Guess_2/3_of_the_average """ class C(BaseConstants): PLAYERS_PER_GROUP = 4 NUM_ROUNDS = 1 NAME_IN_URL = 'guess_two_thirds' JACKPOT = cu(200) GUESS_MAX = 100 class Subsession(BaseSubsession): pass class Group(BaseGroup): two_thirds_avg = models.FloatField() best_guess = models.IntegerField() num_winners = models.IntegerField() class Player(BasePlayer): payment = models.IntegerField(initial=100) is_bid_winner = models.BooleanField(initial=False) selected_bid = models.IntegerField(min=0, max=100) final_euro_amount = models.FloatField(initial=4.00) guess = models.IntegerField( min=0, max=C.GUESS_MAX, label="Please pick a number from 0 to 100:" ) is_winner = models.BooleanField(initial=False) q1 = models.StringField( choices=[['True', 'True'], ['False', 'False']], label='Question 1: In the BDM mechanism, if my bid is lower than the randomly chosen price, I will still receive the advice but will not have to pay for it.', widget=widgets.RadioSelectHorizontal, ) q2 = models.StringField( choices=[['True', 'True'], ['False', 'False']], label='Question 2: In the Guessing game, will you be informed of the other players’ chosen numbers before making your own decision?', widget=widgets.RadioSelectHorizontal, ) q3 = models.StringField( choices=[['True', 'True'], ['False', 'False']], label='Question 3: In the Guessing game, is the winner the person whose guess is closest to twice the average of all chosen numbers?', widget=widgets.RadioSelectHorizontal, ) q4 = models.StringField( choices=[['50 ECU', '50 ECU'], ['100 ECU', '100 ECU'], ['150 ECU', '150 ECU'], ['200 ECU', '200 ECU']], label='Question 4: What is the maximum amount you can bid for the advice in the BDM mechanism?', widget=widgets.RadioSelectHorizontal, ) bid = models.IntegerField(label='What is bid for the Advice?', min=0, max=100) educational_level = models.StringField( choices=[['Bachelors Degree', 'Bachelors Degree'], ['Masters Degree', 'Masters Degree'], ['Doctorate Degree', 'Doctorate Degree'], ['Other', 'Other'], ], label='What is your current education program type?', ) age = models.IntegerField(label='What is your age?', min=13, max=125) gender = models.StringField( choices=[['Male', 'Male'], ['Female', 'Female'], ['Other', 'Other'], ['Prefer Not To Say', 'Prefer Not To Say']], label='What is your Gender?', ) knowledge = models.StringField( choices=[ ['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree'] ], label='I was well aware of the game and its rules before starting the experiment.', widget=widgets.RadioSelectHorizontal, ) influenced = models.StringField( choices=[ ['1', 'Strongly disagree'], ['2', 'Disagree'], ['3', 'Neutral'], ['4', 'Agree'], ['5', 'Strongly agree'] ], label='Without the advice, my decision would have probably been different. (Select Neutral, in case you did not get the advice)', widget=widgets.RadioSelectHorizontal, ) feedback = models.StringField( choices=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], label='How do you see yourself: are you generally a person who is fully prepared to take risks or do you try to avoid taking risks? Please tick a box on the scale, where the value 0 means “not at all willing to take risks” and the value 10 means “very willing to take risks”', widget=widgets.RadioSelectHorizontal, ) # FUNCTIONS def set_payoffs(group: Group): players = group.get_players() guesses = [p.guess for p in players] two_thirds_avg = (2 / 3) * sum(guesses) / len(players) group.two_thirds_avg = round(two_thirds_avg, 2) group.best_guess = min(guesses, key=lambda guess: abs(guess - group.two_thirds_avg)) winners = [p for p in players if p.guess == group.best_guess] group.num_winners = len(winners) for p in winners: p.is_winner = True p.payoff = C.JACKPOT / group.num_winners p.payment += int(p.payoff) p.final_euro_amount += (p.payment / 100) * 2.5 def decide_bid_winner(group: Group): players = group.get_players() the_selected_bid = random.randint(1, 100) for p in players: p.is_bid_winner = True if p.bid >= the_selected_bid else False p.selected_bid = the_selected_bid if p.is_bid_winner == True: p.payment -= the_selected_bid def two_thirds_avg_history(group: Group): return [g.two_thirds_avg for g in group.in_previous_rounds()] # PAGES class Introduction_s(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class payment(Page): pass class Introduction_bdm(Page): @staticmethod def vars_for_template(player: Player): if player.id_in_group % 2 == 0: return {'custom_text': 'a human advice from an expert economics professor'} else: return {'custom_text': 'an AI advice generated by ChatGPT'} class Evaluation(Page): form_model = 'player' form_fields = ['q1','q2','q3','q4'] def error_message(self, values): # Custom validation logic goes here error_messages = [] # Check if answers are correct if values['q1'] != 'False': error_messages.append("Answer to Question 1 is incorrect.") if values['q2'] != 'False': error_messages.append("Answer to Question 2 is incorrect.") if values['q3'] != 'False': error_messages.append("Answer to Question 3 is incorrect.") if values['q4'] != '100 ECU': error_messages.append("Answer to Question 4 is incorrect.") # Display retry warning if any answers are incorrect if error_messages: return '\n'.join(error_messages) class Bidding(Page): form_model = 'player' form_fields = ['bid'] @staticmethod def vars_for_template(player: Player): if player.id_in_group % 2 == 0: return {'custom_text': 'a human advice from an expert economics professor'} else: return {'custom_text': 'an AI advice generated by ChatGPT'} class Guess(Page): form_model = 'player' form_fields = ['guess'] @staticmethod def vars_for_template(player: Player): if player.id_in_group % 2 == 0: return { 'custom_text': '

Human Advice for Playing Prisoner\'s Dilemma Game:

In the single-round "2/3 of the average" guessing game with participants choosing from 0 to 100, I would advise you to choose a number between 20 and 30. This range takes into account a best response to the number of thinking steps participants in this game frequently undertake. If possible take a non-integer number to avoid ties with other participants.

'} else: return { 'custom_text': '

AI Advice for Playing Prisoner\'s Dilemma Game:

In the single-round "2/3 of the average" guessing game with participants choosing from 0 to 100, I would advise you to choose a number between 25 and 35. This range strikes a balance between strategic anchoring and avoiding common clustering tendencies.

'} class ResultsWaitPage(WaitPage): after_all_players_arrive = set_payoffs class ResultsWaitPageBDM(WaitPage): after_all_players_arrive = decide_bid_winner class Results(Page): @staticmethod def vars_for_template(player: Player): group = player.group sorted_guesses = sorted(p.guess for p in group.get_players()) return dict(sorted_guesses=sorted_guesses) class Questions(Page): form_model = 'player' form_fields = ['educational_level', 'age', 'gender', 'knowledge', 'influenced', 'feedback'] class ThankYou(Page): form_model = 'player' page_sequence = [Introduction_s, payment, Introduction_bdm, Evaluation, Bidding, ResultsWaitPageBDM, Guess, ResultsWaitPage, Results, Questions, ThankYou]