from otree.api import * import pandas as pd import random import time import json doc = """ Your app description """ def open_CSV(filename): """ :param filename: opinions.csv :return: a list of statements for the session """ temp = pd.read_csv(filename, sep=',') return temp # MODELS class C(BaseConstants): NAME_IN_URL = 'voting' PLAYERS_PER_GROUP = None N_STRATEGIES = 4 N_GOVERNANCE = 2 # PAYOFFS = open_CSV('voting/staircase_scores.csv') PAYOFFS = open_CSV('voting/staircase_scores.csv') # We want to dynamically set groups for filling as we can N_GROUP = 4 BRIBED_SIM_COM = 1 BRIBED_COM_SIM = 1 NOT_BRIBED_SIM_COM = 1 NOT_BRIBED_COM_SIM = 1 LONG_WAIT = 10 WAIT_PAY = 1.5 MAX_FORCED = 3 ######################### NUM_ROUNDS = 60 EPOCH = 15 ######################### BUTTON_LABELS = ["Robot K3M", "Robot N7P", "Robot V2X", "Robot Z5B"] LEADER_BUTTON_LABELS = ["Research Team", "Mission Commander"] TIMEOUT_VALUE_ACTIVE = 60 TIMEOUT_VALUE_INACTIVE = 10 # First MSG for missing a question, Second MSG after resurrection ADMONISH_MSG = ["WARNING: You gave no response on the previous page. If this happens too often, you will be marked inactive. This may lead to not getting paid.", "You were previously marked inactive. Too many periods of inactivity may lead to not getting paid"] EARLY_ROUNDS = 3 EXTRA_TIME = 60 FEEDBACK_OUTCOMES = ["The Research Team decided to send", "The Research Team decided after one revote to send", "The Research Team decided after two revotes to send", "After two revotes the Research Team could not reach a majoriy. A random robot type was sent:", "The Mission Commander decided to send"] QUESTIONS_TRIPLE = ["Question 1: What robot do you personally think would be best for this round?", "🤖 Question 2: What robot would you choose as part of the Research Team for this round?", "🧑‍🔬🎖️ Question 3: Who should decide which type of robot to send for this round?"] class Subsession(BaseSubsession): pass def long_wait(player): participant = player.participant return time.time() - participant.wait_page_arrival > C.LONG_WAIT * 60 # in mins def group_by_arrival_time_method(subsession, waiting_players): session = subsession.session # players waiting for more than longwait long_waiting = [p for p in waiting_players if long_wait(p)] if len(long_waiting) >= 1: for player in long_waiting: print('Ready to let participant go, waiting for too long') player.participant.is_single = True # Giving this person payment for waiting player.payoff += C.WAIT_PAY print("Here is their total calculated payoff: ", player.participant.payoff_plus_participation_fee()) return [player] # BLOCK 0: CREATE A GROUP WHEN 10 ARE AVAILABLE if len(waiting_players) >= C.N_GROUP: print('Ready to create a group of 4') return waiting_players[:C.N_GROUP] def vote_field(label_text="Below are the options you may choose from. To vote, select one of the options.", num_options=C.N_STRATEGIES): return models.IntegerField( label=label_text, widget=widgets.RadioSelect(), choices=[[i, str(i)] for i in range(1, num_options + 1)] ) class Group(BaseGroup): representative_democracy=models.BooleanField() # this is perhaps a misleading name governance_direct_tally=models.IntegerField() governance_repres_tally=models.IntegerField() governance_no_majority=models.BooleanField(initial=False) strategy_no_majority=models.BooleanField(initial=False) winner_strategy=models.IntegerField(initial=-1) failed_majority = models.BooleanField(initial=False) majority_message = models.StringField(initial="TEST: NO MAJ MSG") treatment_bribed_leader = models.BooleanField() treatment_simple_complex = models.BooleanField() shuffle_list = models.StringField() # To save the shuffled list different per group current_shuffle = models.IntegerField() # What index do you have now? has_N_GROUP_players = models.BooleanField() current_payoffs = models.LongStringField() def get_option_label(option_number): if 1 <= option_number <= len(C.BUTTON_LABELS): return C.BUTTON_LABELS[option_number - 1] else: return "Invalid option number" def count_treatment(group:Group): if group.treatment_bribed_leader and group.treatment_simple_complex: group.subsession.session.bribed_sim_com += 1 if not group.treatment_bribed_leader and group.treatment_simple_complex: group.subsession.session.not_bribed_sim_com += 1 if group.treatment_bribed_leader and not group.treatment_simple_complex: group.subsession.session.bribed_com_sim += 1 if not group.treatment_bribed_leader and not group.treatment_simple_complex: group.subsession.session.not_bribed_com_sim += 1 # These functions refer to Group variables to count sessions so I have placed them under Group class # Add a print function for what they are checking def bribe_simpcomp(group: Group): group.treatment_bribed_leader = True group.treatment_simple_complex = True count_treatment(group) def bribe_compsimp(group: Group): group.treatment_bribed_leader = True group.treatment_simple_complex = False count_treatment(group) def not_bribe_simpcomp(group: Group): group.treatment_bribed_leader = False group.treatment_simple_complex = True count_treatment(group) def not_bribe_compsimp(group: Group): group.treatment_bribed_leader = False group.treatment_simple_complex = False count_treatment(group) def random_treatment(group:Group): group.treatment_bribed_leader = random.choice([True,False]) group.treatment_simple_complex = random.choice([True,False]) count_treatment(group) class Player(BasePlayer): strategy_vote_private = vote_field() # TK: not broken do not fix it strategy_vote_0= vote_field() strategy_vote_1= vote_field() strategy_vote_2= vote_field() strategy_leader= vote_field() # Deprecated representative_democracy= models.BooleanField() resurrect_me = models.BooleanField(initial=False) # True False for being resurrected next page forced_submit = models.BooleanField(initial=False) adm_msg_idx = models.IntegerField(initial=-1) # Used to index C.ADMONISH_MSG score = models.FloatField(initial=-1.0) # What was class average? speed = models.IntegerField(initial=-1) # How many rounds of voting did the group take? round_complexity = models.StringField() # For data wrangle # Recording which question they did not answer which_inactive = models.IntegerField() ## FUNCTIONS ## def increment_votes(player:Player): player.participant.votes += 1 def increment_forced(player:Player): player.participant.local_forced_submits += 1 player.participant.global_forced_submits += 1 player.participant.admonish = True # Tell them off next page player.adm_msg_idx = 0 # They missed the last question if player.participant.local_forced_submits >= C.MAX_FORCED: player.participant.active = False player.participant.admonish = False # No point warning them if there is the inactivity screen print("Function incremented their forced choices!") def handle_resurrections(player:Player): # Do nothing if not to be resurrected if not player.resurrect_me: print("Resurrect me is False.") return player.participant.active = True player.participant.resurrections += 1 player.participant.local_forced_submits = 0 print("I will be resurrected next page!") player.resurrect_me = False player.participant.admonish = True player.adm_msg_idx = 1 # They were marked inactive def calculate_speed(player): """ Calculate the speed value based on voting outcomes. Speed represents how many voting rounds occurred: - 1: Decision made immediately (professor or first vote) - 2: Decision after second vote - 3: Decision after third vote - 4: Failed majority, professor intervened Returns: int: The calculated speed value (1-4) """ # Determine number of student votes that occurred if player.field_maybe_none('strategy_vote_2') is not None: num_student_votes = 3 elif player.field_maybe_none('strategy_vote_1') is not None: num_student_votes = 2 else: num_student_votes = 1 # Determine which outcome occurred (0-4) if player.group.failed_majority == True: which_maj_outcome_idx = 4 elif player.group.representative_democracy == True: which_maj_outcome_idx = 0 else: which_maj_outcome_idx = num_student_votes # Convert outcome index to speed value if which_maj_outcome_idx == 0: # Professor decided speed = 1 elif which_maj_outcome_idx == 1: # First student vote majority speed = 1 elif which_maj_outcome_idx == 2: # Second student vote majority speed = 2 elif which_maj_outcome_idx == 3: # Third student vote majority speed = 3 elif which_maj_outcome_idx == 4: # Failed majority, professor chose speed = 4 else: speed = 4 # Fallback # Set the speed on player and participant player.speed = speed player.participant.speed = speed def speed_to_penalty(speed): # What was the speed? 0? speed_bonus_map = { 1: 0, 2: -5, 3: -10, 4: -15 } return speed_bonus_map.get(speed,-999) # Default to -100 for debugging # PAGES class Grouping_WaitPage(WaitPage): template_name = 'voting/Grouping_WaitPage.html' group_by_arrival_time = True @staticmethod def is_displayed(player): return player.round_number == 1 and player.participant.active and player.participant.voting_APP class Experiment_WaitPage(WaitPage): template_name = 'voting/Experiment_WaitPage.html' @staticmethod def after_all_players_arrive(group: Group): session = group.subsession.session subsession = group.subsession # Checking that we have filled the treatments how we wanted to print("BRIBED_SIM_COM:", session.bribed_sim_com < C.BRIBED_SIM_COM, '\n', "BRIBED_COM_SIM:", session.bribed_com_sim <=C.BRIBED_COM_SIM, '\n', "NOT BRIBED_SIM_COM:",session.not_bribed_sim_com < C.NOT_BRIBED_SIM_COM, '\n', "NOT BRIBED_COM_SIM:",session.not_bribed_com_sim < C.NOT_BRIBED_COM_SIM) if subsession.round_number == 1: # Shuffle for experiment # Making the shuffle list shuffle_list = list(range(1, 31)) # TK: Magic number for max index for payoffs random.shuffle(shuffle_list) print("Full shuffled list:", shuffle_list) # Checking it gets shuffled group.shuffle_list = json.dumps(shuffle_list) # Make string for storing # check if group has more than 1 member group_players = group.get_players() if len(group_players) == 1: group.has_N_GROUP_players = False for player in group.get_players(): player.participant.is_single = True player.participant.pay_APP = True else: group.has_N_GROUP_players = True # assign treatment conditions = [ (session.bribed_sim_com < C.BRIBED_SIM_COM,bribe_simpcomp), (session.bribed_com_sim direct else: group.governance_no_majority = True group.representative_democracy = random.choice([False, True]) # STRATEGY TALLY strategy_tally = {i: 0 for i in range(1, C.N_STRATEGIES + 1)} for player in group.get_players(): strategy_tally[player.strategy_vote_0] += 1 # Randomly pick one of the votes (weighted by number of votes per option) options = list(strategy_tally.keys()) votes = list(strategy_tally.values()) random_cast = random.choices(options, weights=votes, k=1)[0] # Reset the votes from the previous rounds for player in group.get_players(): player.participant.vars['strategy_tally'] = {'V0': {}, 'V1': {}, 'V2': {}} # Record the votes of the players for player in group.get_players(): player.participant.vars['strategy_tally']['V0'] = strategy_tally # Super-majority threshold: more than half the group majority_threshold = (C.N_GROUP / 2) + 1 # Find, if any, which strategy has supermajority strategy_with_majority = [k for k, v in strategy_tally.items() if v >= majority_threshold] if len(strategy_with_majority) == 1: group.winner_strategy = strategy_with_majority[0] group.strategy_no_majority = False else: group.strategy_no_majority = True # Handling professor decisions if group.representative_democracy == True: group.majority_message = "Leader did not make this decision, and this text should not be displayed." # Get current complexity and epoch (same for all players in group) any_player = group.get_players()[0] current_complexity = any_player.participant.vars['current_complexity'] epoch_index = any_player.participant.vars['epoch_index'] shuffle_list = json.loads(group.shuffle_list) shuffle_idx = shuffle_list[epoch_index - 1] # If the leader is bribed they choose one of the worst two options (3rd best or worst) if group.treatment_bribed_leader: group.majority_message = "The Professor made the decision." # Filter by complexity and index filtered_payoffs = C.PAYOFFS[ (C.PAYOFFS['Complexity'] == current_complexity) & (C.PAYOFFS['Index'] == shuffle_idx) ] if not filtered_payoffs.empty: # Calculate average score per option across all PlayerIDs avg_scores = filtered_payoffs.groupby('Option')['Score'].mean().reset_index() avg_scores.columns = ['Option', 'AvgScore'] # Sort by average score in descending order (best to worst) sorted_options = avg_scores.sort_values(by='AvgScore', ascending=False).reset_index(drop=True) print(f"\n=== BRIBED LEADER DECISION (Complexity: {current_complexity}, Index: {epoch_index}) ===") print("Average scores per option (sorted best to worst):") for idx, row in sorted_options.iterrows(): rank_name = ["Best", "2nd Best", "3rd Best", "Worst (4th)"][idx] if idx < 4 else f"{idx+1}th" print(f" Option {int(row['Option'])}: AvgScore = {row['AvgScore']:.3f} ({rank_name})") # Choose 3rd best (index 2) with 70% probability, worst (index 3) with 30% probability if len(sorted_options) >= 4: third_best = sorted_options.iloc[2]['Option'] worst = sorted_options.iloc[3]['Option'] chosen_option = random.choices( [third_best, worst], weights=[0.7, 0.3], k=1 )[0] choice_type = "3rd Best (70%)" if chosen_option == third_best else "Worst/4th (30%)" print(f">>> SELECTED: Option {int(chosen_option)} ({choice_type})") else: # Fallback if fewer than 4 options chosen_option = sorted_options.iloc[-1]['Option'] print(f"!!! WARNING: Only {len(sorted_options)} options available. Selected worst: {int(chosen_option)}") group.winner_strategy = int(chosen_option) print(f"Final winner_strategy: {group.winner_strategy}\n") else: print("!!! ERROR: No filtered options found!") # If the leader is NOT bribed they choose the best two options elif not group.treatment_bribed_leader: group.majority_message = "The Professor made the decision." # Filter by complexity and index filtered_payoffs = C.PAYOFFS[ (C.PAYOFFS['Complexity'] == current_complexity) & (C.PAYOFFS['Index'] == shuffle_idx) ] if not filtered_payoffs.empty: # Calculate average score per option across all PlayerIDs avg_scores = filtered_payoffs.groupby('Option')['Score'].mean().reset_index() avg_scores.columns = ['Option', 'AvgScore'] # Sort by average score in descending order (best to worst) sorted_options = avg_scores.sort_values(by='AvgScore', ascending=False).reset_index(drop=True) print(f"\n=== NON-BRIBED LEADER DECISION (Complexity: {current_complexity}, Index: {epoch_index}) ===") print("Average scores per option (sorted best to worst):") for idx, row in sorted_options.iterrows(): rank_name = ["Best", "2nd Best", "3rd Best", "Worst (4th)"][idx] if idx < 4 else f"{idx+1}th" print(f" Option {int(row['Option'])}: AvgScore = {row['AvgScore']:.3f} ({rank_name})") # Choose best (index 0) with 30% probability, 2nd best (index 1) with 70% probability if len(sorted_options) >= 2: best = sorted_options.iloc[0]['Option'] second_best = sorted_options.iloc[1]['Option'] chosen_option = random.choices( [best, second_best], weights=[0.3, 0.7], k=1 )[0] choice_type = "Best (30%)" if chosen_option == best else "2nd Best (70%)" print(f">>> SELECTED: Option {int(chosen_option)} ({choice_type})") else: # Fallback if fewer than 2 options chosen_option = sorted_options.iloc[0]['Option'] print(f"!!! WARNING: Only {len(sorted_options)} options available. Selected best: {int(chosen_option)}") group.winner_strategy = int(chosen_option) print(f"Final winner_strategy: {group.winner_strategy}\n") else: print("!!! ERROR: No filtered options found!") elif group.representative_democracy == False: print("Majority decision to be made") group.majority_message = "the Teaching Assistants decided on the rule." @staticmethod def is_displayed(player): return player.participant.voting_APP and not player.participant.is_single class V1(Page): def get_timeout_seconds(player): base = C.TIMEOUT_VALUE_ACTIVE if player.participant.active else C.TIMEOUT_VALUE_INACTIVE extra = C.EXTRA_TIME if player.round_number < C.EARLY_ROUNDS else 0 return base + extra form_model = 'player' form_fields = ['strategy_vote_1', 'resurrect_me'] @staticmethod def vars_for_template(player): # Get the whole groups previous vote prior_vote = player.participant.vars.get('strategy_tally',{}).get('V0',{}) return dict(prior_vote = prior_vote, admonish_msg = C.ADMONISH_MSG[player.adm_msg_idx] ) @staticmethod def before_next_page(player: Player, timeout_happened): increment_votes(player) # This will toggle old warning off but may get toggled back on if consecutive player.participant.admonish = False # If an active or inactive player timed out if timeout_happened: # Select random strategy player.strategy_vote_1 = random.choice(list(range(1, C.N_STRATEGIES + 1))) # Record they were a force submit and increment counters player.forced_submit = True player.which_inactive = 2 # They did not answer the second questions increment_forced(player) handle_resurrections(player) @staticmethod def is_displayed(player: Player): # if player.participant.active and player.participant.voting_APP: if player.participant.voting_APP and not player.participant.is_single: return not player.group.representative_democracy and player.group.strategy_no_majority class V1_WaitPage(WaitPage): template_name = 'voting/V1_WaitPage.html' @staticmethod def after_all_players_arrive(group: Group): # STRATEGY TALLY strategy_tally = {} # for the current voting stage of this round for i in range(1, C.N_STRATEGIES + 1): strategy_tally[i] = 0 for player in group.get_players(): strategy_tally[player.strategy_vote_1] += 1 # Majority threshold: more than half the group majority_threshold = (C.N_GROUP / 2) + 1 # Find all strategies that meet or exceed the majority threshold strategies_with_majority = [k for k, v in strategy_tally.items() if v >= majority_threshold] if len(strategies_with_majority) == 1: group.winner_strategy = strategies_with_majority[0] group.strategy_no_majority = False group.majority_message = C.TA_MAJ_MSG[1] else: group.strategy_no_majority = True group.majority_message = C.TA_MAJ_MSG[0] for player in group.get_players(): player.participant.vars['strategy_tally']['V1'] = strategy_tally @staticmethod def is_displayed(player: Player): if player.participant.voting_APP and not player.participant.is_single: # if player.participant.active and player.participant.voting_APP: return not player.group.representative_democracy and player.group.strategy_no_majority class V2(Page): def get_timeout_seconds(player): base = C.TIMEOUT_VALUE_ACTIVE if player.participant.active else C.TIMEOUT_VALUE_INACTIVE extra = C.EXTRA_TIME if player.round_number < C.EARLY_ROUNDS else 0 return base + extra form_model = 'player' form_fields = ['strategy_vote_2','resurrect_me'] @staticmethod def vars_for_template(player): # Get the whole groups previous vote prior_vote = player.participant.vars.get('strategy_tally',{}).get('V1',{}) return dict(prior_vote = prior_vote, button_1 = C.BUTTON_LABELS[0], button_2 = C.BUTTON_LABELS[1], button_3 = C.BUTTON_LABELS[2], button_4 = C.BUTTON_LABELS[3], admonish_msg = C.ADMONISH_MSG[player.adm_msg_idx] ) @staticmethod def before_next_page(player: Player, timeout_happened): increment_votes(player) # This will toggle old warning off but may get toggled back on if consecutive player.participant.admonish = False # If an active player timed out if timeout_happened: # Select random strategy and governance player.strategy_vote_2 = random.choice(list(range(1, C.N_STRATEGIES + 1))) # Record they were a force submit and increment counters player.forced_submit = True player.which_inactive = 3 # They did not answer the third question increment_forced(player) handle_resurrections(player) @staticmethod def is_displayed(player: Player): if player.participant.voting_APP and not player.participant.is_single: #if player.participant.active and player.participant.voting_APP: return not player.group.representative_democracy and player.group.strategy_no_majority class V2_WaitPage(WaitPage): template_name = 'voting/V2_WaitPage.html' @staticmethod def after_all_players_arrive(group: Group): # STRATEGY TALLY strategy_tally = {} for i in range(1, C.N_STRATEGIES + 1): strategy_tally[i] = 0 for player in group.get_players(): strategy_tally[player.strategy_vote_2] += 1 # Define majority threshold majority_threshold = (C.N_GROUP / 2) + 1 # Find strategies with majority strategies_with_majority = [k for k, v in strategy_tally.items() if v >= majority_threshold] if len(strategies_with_majority) == 1: group.strategy_no_majority = False group.winner_strategy = strategies_with_majority[0] group.majority_message = C.TA_MAJ_MSG[1] else: # If there is no single majority strategy I will choose randomly for them group.winner_strategy = random.choice(list(range(1, C.N_STRATEGIES + 1))) # Want to record for Outcome purposes # Dumb work around add failed Majority group.failed_majority = True # They failed to reach a majority after three votes group.strategy_no_majority = False # This was actually true at first, false to show Feedback group.majority_message = "After three failed majorities, the Professor chose." print("I chose for them") for player in group.get_players(): player.participant.vars['strategy_tally']['V2'] = strategy_tally @staticmethod def is_displayed(player: Player): # if player.participant.active and player.participant.voting_APP: if player.participant.voting_APP and not player.participant.is_single: return not player.group.representative_democracy and player.group.strategy_no_majority # TK: Used to be called Outback class Feedback(Page): form_model = 'player' form_fields = ['resurrect_me'] @staticmethod def get_timeout_seconds(player): if not player.participant.active: return 5 return 20 if player.round_number < C.EARLY_ROUNDS else 10 @staticmethod def vars_for_template(player: Player): # Finding payoff current_complexity = player.participant.vars['current_complexity'] epoch_index = player.participant.vars['epoch_index'] print("Epoch index:", epoch_index) # Casting back and forth from string to list shuffle_list = json.loads(player.group.shuffle_list) shuffle_idx = shuffle_list[epoch_index - 1] print("Shuffled index:", shuffle_idx) winner_strategy = player.group.winner_strategy filtered_payoffs = C.PAYOFFS[ (C.PAYOFFS['Complexity'] == current_complexity) & (C.PAYOFFS['Index'] == shuffle_idx) & (C.PAYOFFS['Option'] == winner_strategy) & (C.PAYOFFS['PlayerID'] == player.id_in_group) ] # TK: Now saving correct counterfactual given shuffle idx current_payoffs = C.PAYOFFS[ (C.PAYOFFS['Complexity'] == current_complexity) & (C.PAYOFFS['Index'] == shuffle_idx) ] player.group.current_payoffs = current_payoffs.to_json(orient='records') # Extract original score from payoff table original_score = round(float(filtered_payoffs['Score'].iloc[0]), 2) player.score = original_score # Calculate speed and penalty calculate_speed(player) print("Function speed: ", player.speed) penalty = speed_to_penalty(player.speed) # e.g., -5, -10, -15 # Calculate final score (original + penalty) final_score = original_score + penalty # Calculate cumulative score cumm_score = sum([p.score for p in player.in_previous_rounds()]) Feedback.winner_strategy_text = get_option_label(player.group.winner_strategy) # Visual the most recent round of voting for key in ['V2', 'V1', 'V0']: # Check from latest to earliest if player.participant.vars['strategy_tally'][key]: prior_vote = player.participant.vars['strategy_tally'][key] break # Determine leader choice based on conditions if player.group.representative_democracy: # Mission Commander made the choice leader_choice = player.group.winner_strategy elif player.group.failed_majority: # Random selection after failed votes leader_choice = "random" else: # No leader choice leader_choice = "no_leader_choice" # Determine outcome index based on speed and leader_choice # IMPORTANT: Check speed == 4 FIRST to avoid incorrect outcome display if player.speed == 4: outcome_idx = 3 # Failed majority after three votes, random selection elif player.group.representative_democracy: outcome_idx = 4 # Mission Commander decided elif player.speed in [1, 2, 3]: outcome_idx = player.speed - 1 # 0, 1, or 2 based on speed else: outcome_idx = 0 # Fallback Feedback.winner_strategy_text = get_option_label(player.group.winner_strategy) return dict( winner_strategy=Feedback.winner_strategy_text, class_avg=int(original_score), # Original score speed_penalty=penalty, # Penalty (e.g., -5) total_earned=int(final_score), # Final score after penalty speed=player.speed, prior_vote=prior_vote, leader_choice=leader_choice, admonish_msg=C.ADMONISH_MSG[player.adm_msg_idx], outcome=C.FEEDBACK_OUTCOMES[outcome_idx], time_limit = Feedback.get_timeout_seconds(player) ) @staticmethod def is_displayed(player: Player): # This page should always display at the end of the round if player.participant.voting_APP and not player.participant.is_single: return True @staticmethod def before_next_page(player, timeout_happened): # This will toggle old warning off but may get toggled back on if consecutive player.participant.admonish = False # Increment the epoch index player.participant.vars['epoch_index'] += 1 # If you reached the end of an epoch then swap complexities if player.participant.epoch_index > C.EPOCH: # TK: Round index error from pilot corrected player.participant.vars['current_complexity'] = "Simple" if player.participant.vars['current_complexity'] == "Complex" else "Complex" player.participant.vars['epoch_index'] = 1 print("We have swapped complexities:))") # Reset the local_forced_submits every 10 rounds if player.round_number % 10 == 0: player.participant.local_forced_submits = 0 handle_resurrections(player) # Calculate monetary payoff: final_score * conversion_rate # Post-pilot TK: Save final_score as player variable as correct the payoff saving penalty = speed_to_penalty(player.speed) final_score = player.score + penalty player.payoff = final_score * 0.0025 player.round_complexity = player.participant.current_complexity print("Here is the player payoff: ", player.payoff) print("Here is their total calculated payoff: ", player.participant.payoff_plus_participation_fee()) print("Current complexity: ", player.round_complexity) class Debrief(Page): @staticmethod def is_displayed(player: Player): # Show only if final round and committed is True and in group committed = player.participant.vars.get('committed', False) if player.participant.is_single == False: return player.round_number == C.NUM_ROUNDS and committed is True @staticmethod def vars_for_template(player): # Not paying people with more than 40% inactivtyDoes i p = player.participant if p.voting_APP: if p.global_forced_submits > p.votes * 0.4: p.pay_APP = False INACTIVITY = "You voted less than 40% of the time, and have been deemed ineligble for experimental payment. You will be paid for the training and any waiting. If you are certain this incorrect, please contact the experimenters through Prolific." else: INACTIVITY = "TEST: They were active so no need to warn" p.pay_APP = True print("Votes and forced submit for paid:", p.votes, p.global_forced_submits) # return dict( pay_explanation = INACTIVITY ) page_sequence = [Grouping_WaitPage, Experiment_WaitPage, ThreeVotes, ThreeVotes_WaitPage, V1, V1_WaitPage, V2, V2_WaitPage, Feedback, Debrief]