from otree.api import * import random doc = """ Ultimatum game AI vs. Human Fading mediation NO Moderation This game doesn't use oTree groups. Rather, it stores the partner's ID in a player field. """ class C(BaseConstants): NAME_IN_URL = 'Ultimatum' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 ENDOWMENT = cu(79) TREATMENTS = ['ai', 'human'] class Subsession(BaseSubsession): pass def creating_session(subsession: Subsession): players = subsession.get_players() last_condition = None for player in players: # Check if the previous player was a dropout prev_player = player.in_all_rounds()[-2] if len(player.in_all_rounds()) > 1 else None if prev_player and prev_player.is_dropout: # Assign this player the same condition as the previous dropout player player.condition = prev_player.condition else: # Alternate between 'ai' and 'human' conditions if last_condition == 'ai': player.condition = 'human' else: player.condition = 'ai' print(f"Assigning player {player.id_in_group} to condition {player.condition}") # Update last_condition last_condition = player.condition # print(f"Assigning player {player.id_in_group} to condition {player.condition}") # num_players = len(players) # last_player = players[-1] if players else None # for i, player in enumerate(players, start=1): # if last_player and last_player.is_dropout: # # Assign this player as a makeup for the last dropout # player.condition = last_player.condition # else: # # Alternate between the two treatments for each player # if i % 2 == 0: # player.condition = 'human' # else: # player.condition = 'ai' # print(f"Assigning player {player.id_in_group} to condition {player.condition}") class Group(BaseGroup): pass class Player(BasePlayer): condition = models.StringField() is_p1 = models.BooleanField() offer = models.CurrencyField( min=0, max=C.ENDOWMENT, doc="This field is only used if the player is P1", ) potsize = models.CurrencyField( min=5, max=90, doc="This field is only used if the player is P1", ) partner_id = models.IntegerField( doc="This field is only used if the player is P2. It stores the ID of P1", ) accepted = models.BooleanField( label="Do you accept Player 1's offer?", doc="This field is only used if the player is P2", ) bot_accepted = models.BooleanField() quiz1 = models.IntegerField(label='The size of the pot is $____.') quiz2a = models.StringField( label="The AI bot is told that the pot size ranges between _____.", choices=['$5 - $100', '$0 - $70', '$5 - $90'],) quiz2h = models.StringField( label="The other participant is told that the pot size ranges between _____.", choices=['$5 - $100', '$0 - $70', '$5 - $90'],) quiz3 = models.StringField( label="As the first participant, I will receive ____.", choices=['All of the pot money', 'The pot money minus my offer amount', 'None of the pot money'],) quiz4a = models.BooleanField(label="The AI bot knows the actual size of the pot.") quiz4h = models.BooleanField(label="The other participant knows the actual size of the pot.") is_dropout = models.BooleanField(initial=False) def set_payoff(player: Player): if player.bot_accepted: player.payoff = C.ENDOWMENT - player.offer else: player.payoff = cu(0) ### AI PARTNERSHIP VERSION ### class U1Intro(Page): form_model = 'player' # form_fields = ['quiz1', 'quiz2a', 'quiz3', 'quiz4a'] @staticmethod def is_displayed(player): return player.condition == 'ai' @staticmethod def before_next_page(player: Player, timeout_happened): session = player.session class U1Instruct(Page): timeout_seconds = 180 form_model = 'player' form_fields = ['quiz1', 'quiz2a', 'quiz3', 'quiz4a'] @staticmethod def before_next_page(player: Player, timeout_happened): # If timeout happens, mark as dropout if timeout_happened: player.is_dropout = True return @staticmethod def is_displayed(player): return player.condition == 'ai' and not player.is_dropout @staticmethod def error_message(player: Player, values): solutions = dict(quiz1=C.ENDOWMENT, quiz2a='$5 - $90', quiz3='The pot money minus my offer amount', quiz4a=False) if values != solutions: return "One or more answers were incorrect. Please reread the instructions and try again." class U1Offer(Page): timeout_seconds = 180 form_model = 'player' form_fields = ['offer', 'potsize'] @staticmethod def is_displayed(player): return player.condition == 'ai' and not player.is_dropout @staticmethod def vars_for_template(player: Player): offer_label = "What is your offer to the AI bot? (Amount between $0 and $79)" potsize_label = "How much will you tell the AI bot the pot size is? (Amount between $5-$90)" return { 'offer_label': offer_label, 'potsize_label': potsize_label } @staticmethod def before_next_page(player: Player, timeout_happened): # If timeout happens, mark as dropout if timeout_happened: player.is_dropout = True return session = player.session # Rest of your code for the offer... half_pot_size = player.potsize / 2 half_endowment = (C.ENDOWMENT - 1) / 2 # Condition 0: Accept if offer is equal to or greater than 1 if player.offer >= 1: player.bot_accepted = True # Condition 1: Accept if offer is equal to or greater than one less than half of the endowment elif player.offer >= half_endowment: player.bot_accepted = True # Condition 2: Reject if offer is less than half of the stated pot size elif player.offer < half_pot_size: player.bot_accepted = False # Condition 3: Randomly accept or reject if in between else: player.bot_accepted = random.choice([True, False]) # Ensure the payoff is calculated based on this decision set_payoff(player) class U1Results(Page): @staticmethod def is_displayed(player): return player.condition == 'ai' and not player.is_dropout @staticmethod def vars_for_template(player: Player): bot_decision = "Accepted" if player.bot_accepted else "Rejected" bot_payoff = player.offer if player.bot_accepted else cu(0) player_payoff=player.payoff return dict( bot_decision=bot_decision, bot_payoff=bot_payoff ) ### HUMAN PARTNERSHIP VERSION ### class U2Intro(Page): form_model = 'player' # form_fields = ['quiz1', 'quiz2h', 'quiz3', 'quiz4h'] @staticmethod def is_displayed(player): return player.condition == 'human' @staticmethod def before_next_page(player: Player, timeout_happened): session = player.session class U2Instruct(Page): timeout_seconds = 180 form_model = 'player' form_fields = ['quiz1', 'quiz2h', 'quiz3', 'quiz4h'] @staticmethod def is_displayed(player): return player.condition == 'human' and not player.is_dropout @staticmethod def before_next_page(player: Player, timeout_happened): # If timeout happens, mark as dropout if timeout_happened: player.is_dropout = True return @staticmethod def error_message(player: Player, values): solutions = dict(quiz1=C.ENDOWMENT, quiz2h='$5 - $90', quiz3='The pot money minus my offer amount', quiz4h=False) if values != solutions: return "One or more answers were incorrect. Please reread the instructions and try again." class U2Offer(Page): timeout_seconds = 180 form_model = 'player' form_fields = ['offer', 'potsize'] @staticmethod def is_displayed(player): return player.condition == 'human' and not player.is_dropout @staticmethod def vars_for_template(player: Player): offer_label = "What is your offer to the other participant? (Amount between $0 and $79)" potsize_label = "How much will you tell the other participant the pot size is? (Amount between $5-$90)" return { 'offer_label': offer_label, 'potsize_label': potsize_label } @staticmethod def before_next_page(player: Player, timeout_happened): if timeout_happened: player.is_dropout = True return session = player.session half_pot_size = player.potsize / 2 half_endowment = (C.ENDOWMENT - 1) / 2 # Condition 0: Accept if offer is equal to or greater than 1 # HARD CODED TO ACCEPT EVERYTHING! if player.offer >= 1: player.bot_accepted = True # Condition 1: Accept if offer is equal to or greater than one less than half of the endowment elif player.offer >= half_endowment: player.bot_accepted = True # Condition 2: Reject if offer is less than half of the stated pot size elif player.offer < half_pot_size: player.bot_accepted = False # Condition 3: Randomly accept or reject if in between else: player.bot_accepted = random.choice([True, False]) # Ensure the payoff is calculated based on this decision set_payoff(player) class U2Results(Page): @staticmethod def is_displayed(player): return player.condition == 'human' and not player.is_dropout @staticmethod def vars_for_template(player: Player): bot_decision = "Accepted" if player.bot_accepted else "Rejected" bot_payoff = player.offer if player.bot_accepted else cu(0) player_payoff=player.payoff return dict( bot_decision=bot_decision, bot_payoff=bot_payoff ) class UTimeout(Page): @staticmethod def is_displayed(player: Player): return player.is_dropout @staticmethod def error_message(player: Player, values): return "Cannot proceed past this page" page_sequence = [U1Intro, U2Intro, U1Instruct, U2Instruct, U1Offer, U2Offer, UTimeout, U1Results, U2Results]