import random from datetime import datetime from otree.api import * from settings import STRATEGIES from oneapp import Constants as ConstantsOneApp doc = """ This app contains the strategy following task. This app is identical to oneapp. oneapp_two exists to help randomize the order of apps played in the experiment. Because of the way otree is implemented, we cannot simply use inheritance to avoid repetition, but unfortunately have to copy the code. Just including whole templates also does not seem to work, so they need to be duplicated as well. """ class Constants(ConstantsOneApp): name_in_url = "oneapp_two" class Player(BasePlayer): choice = models.IntegerField( label="""Make your choice""", choices=[[1, "X"], [0, "Y"]] ) computer_choice = models.IntegerField(initial=0) ar_choice = models.IntegerField( blank=True ) # field to record original choice in AR treatment attention_check = models.IntegerField(label="""What is 7-5?""") class Group(BaseGroup): pass class Subsession(BaseSubsession): pass class AttentionCheck(Page): timeout_seconds = 600 form_model = "player" form_fields = ["attention_check"] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): participant = player.participant participant.attention_check_sft = player.attention_check if timeout_happened: participant.timeout = True if player.attention_check != Constants.A_CHECK_SFT_CORRECT: participant.attention_check_failed = True @staticmethod def app_after_this_page(player, upcoming_apps): participant = player.participant if participant.timeout: return "kick" class GeneralIntroduction(Page): timeout_seconds = 600 @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): if timeout_happened: player.participant.timeout = True @staticmethod def app_after_this_page(player, upcoming_apps): participant = player.participant if participant.timeout: return "kick" class Introduction(Page): """ Specific strategy instructions """ timeout_seconds = 600 @staticmethod def before_next_page(player: Player, timeout_happened): participant = player.participant participant.last_timestamp = datetime.now() if timeout_happened: participant.timeout = True @staticmethod def app_after_this_page(player, upcoming_apps): participant = player.participant if participant.timeout: return "kick" @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): strat = player.participant.treatment_rule return dict(strat=strat) class Decision(Page): """ Repeated page, where the player chooses X or Y """ timeout_seconds = 600 @staticmethod def app_after_this_page(player, upcoming_apps): participant = player.participant if participant.timeout: return "kick" form_model = "player" form_fields = ["choice", "ar_choice"] @staticmethod def before_next_page(player, timeout_happened): participant = player.participant current_timestamp = datetime.now() time_dif = (current_timestamp - participant.last_timestamp).total_seconds() exec(f"participant.seconds_round_{player.round_number} = {time_dif}") if timeout_happened: participant.timeout = True if player.round_number == 1: participant.sft_pay = 0 player.computer_choice = random.choice([0, 1]) # Determine whether a player followed a strategy correctly current_strategy = participant.treatment_rule # Always defect if current_strategy == "AD": raise NotImplementedError("This part of legacy code has not been tested.") if player.round_number == 1: participant.payment = True if player.choice != 0: participant.payment = False # Grim Trigger elif current_strategy == "GT": raise NotImplementedError("This part of legacy code has not been tested.") if player.round_number == 1: participant.gt_triggered = False participant.payment = True if player.choice != 1: participant.payment = False else: prev_player = player.in_round(player.round_number - 1) if prev_player.computer_choice == 0: participant.gt_triggered = True if (participant.gt_triggered and player.choice == 1) or ( not participant.gt_triggered and player.choice == 0 ): participant.payment = False # Tit-for-tat elif current_strategy == "TfT": if player.round_number == 1: if player.choice == 1: participant.payment = True else: participant.payment = False exec(f"participant.mistake_round_{player.round_number} = True") else: prev_player = player.in_round(player.round_number - 1) if (prev_player.computer_choice == 0 and player.choice == 1) or ( prev_player.computer_choice == 1 and player.choice == 0 ): participant.payment = False exec(f"participant.mistake_round_{player.round_number} = True") # 2 Tit for Tat elif current_strategy == "TTfT": raise NotImplementedError("This part of legacy code has not been tested.") if player.round_number <= 2: if player.choice == 1: if player.round_number == 1: participant.payment = True else: participant.payment = False else: prev_player = player.in_round(player.round_number - 1) prev_player2 = player.in_round(player.round_number - 2) if ( prev_player.computer_choice == 0 and prev_player2.computer_choice == 0 ): if player.choice == 1: participant.payment = False else: if player.choice == 0: participant.payment = False # Tf3T elif current_strategy == "Tf3T": participant = player.participant if player.round_number == 1: participant.dtriggerTf3T = False participant.dcountTf3T = 0 if player.choice == 1: participant.payment = True else: participant.payment = False exec(f"participant.mistake_round_{player.round_number} = True") else: if (player.choice == 0 and not participant.dtriggerTf3T) or ( player.choice == 1 and participant.dtriggerTf3T ): participant.payment = False exec(f"participant.mistake_round_{player.round_number} = True") if player.computer_choice == 0 and not participant.dtriggerTf3T: participant.dcountTf3T += 1 if participant.dcountTf3T == 3: participant.dtriggerTf3T = True participant.dcountTf3T = 0 if player.computer_choice == 1 and participant.dtriggerTf3T: participant.dtriggerTf3T = False # DTf3T elif current_strategy == "DTf3T": raise NotImplementedError("This part of legacy code has not been tested.") participant = player.participant if player.round_number == 1: participant.dtriggerTf3T = False participant.dcountTf3T = 0 if player.choice == 0: participant.payment = True else: participant.payment = False exec(f"participant.mistake_round_{player.round_number} = True") else: if (player.choice == 0 and not participant.dtriggerTf3T) or ( player.choice == 1 and participant.dtriggerTf3T ): participant.payment = False exec(f"participant.mistake_round_{player.round_number} = True") if player.computer_choice == 0 and not participant.dtriggerTf3T: participant.dcountTf3T += 1 if participant.dcountTf3T == 3: participant.dtriggerTf3T = True participant.dcountTf3T = 0 if player.computer_choice == 1 and participant.dtriggerTf3T: participant.dtriggerTf3T = False else: raise ValueError(f"Unknown strategy '{current_strategy}'") if player.round_number == Constants.num_rounds: if participant.payment: participant.sft_pay += Constants.stratpay @staticmethod def vars_for_template(player: Player): strat = player.participant.treatment_rule choice_history = [] if player.round_number == Constants.num_rounds: upper = Constants.num_rounds else: upper = player.round_number % Constants.num_rounds for i in range(1, upper): prev_player = player.in_round(player.round_number - i) choice = "You chose: " + Constants.CHOICE_NAMES[prev_player.choice] + "." computer_choice = ( "The computer chose: " + Constants.CHOICE_NAMES[prev_player.computer_choice] + "." + "
" ) round_ = f"Round {player.round_number-i}:" choice_history.extend([round_, choice, computer_choice]) return dict( choice_history=" ".join(choice_history), treatment_reminder=player.participant.treatment_reminder, strat=strat, ) class Results(Page): timeout_seconds = 600 @staticmethod def before_next_page(player: Player, timeout_happened): participant = player.participant if timeout_happened: participant.timeout = True participant.last_timestamp = datetime.now() @staticmethod def app_after_this_page(player, upcoming_apps): participant = player.participant if participant.timeout: return "kick" @staticmethod def vars_for_template(player: Player): return dict( choice=Constants.CHOICE_NAMES[player.choice], computer_choice=Constants.CHOICE_NAMES[player.computer_choice], ) page_sequence = [GeneralIntroduction, AttentionCheck, Introduction, Decision, Results]