import random from datetime import datetime from otree.api import * from settings import ROUNDS_PER_STRATEGY, STRATEGIES doc = """ This app contains the strategy following task (sft). This app is identical to oneapp_two. oneapp_two exists to help randomize the order of apps played in the experiment. There does not seem to be a more elegant solution (cf. https://www.otreehub.com/forum/502/). """ class Constants(BaseConstants): name_in_url = "oneapp" players_per_group = None if not set(STRATEGIES).issubset(["TfT", "Tf3T", "AD", "GT", "TTfT"]): raise ValueError( f"For at least one of the STRATEGIES in '{STRATEGIES}' the " f"implementation is missing." ) num_rounds = ROUNDS_PER_STRATEGY stratpay = 2 A_CHECK_SFT_CORRECT = 2 CHOICE_NAMES = {0: "Y", 1: "X"} class Group(BaseGroup): pass class Subsession(BaseSubsession): pass 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 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]