import random from otree.api import * import numpy as np import re from collections import Counter class C(BaseConstants): NAME_IN_URL = 'UCSB_T3' PLAYERS_PER_GROUP = 3 NUM_ROUNDS = 10 ENDOWMENT = cu(100) MULTIPLIER = 1.8 Treatment = 3 AlertON = 1 GIDS = [1,2] treatment_orders = [[1, 2, 3, 4], [4, 3, 2, 1], [2, 4, 1, 3], [3, 1, 4, 2]] Total_Treatments = 4 # GIDS = [1, 2] # treatment_orders = [[1, 2, 3, 4], [4, 3, 2, 1], [2, 4, 1, 3], [3, 1, 4, 2]] class Subsession(BaseSubsession): team_ids = models.IntegerField() tid_count = models.StringField() ### Notes: ### 0 = control: inconsistent groups [] if treatment == 0: randomize groups ### 1 = T1: consistent groups [] if treatment == 1: hide feedback and team page ### 2 = T2: consistent minimal groups competition [] if treatment == 2: hide team page ### 3 = T3: consistent induced groups [] if treatment == 3: hide feedback ### 4 = T4: consistent induced groups competition [] if treatment == 4: show feedback and team pages class Group(BaseGroup): total_contribution = models.CurrencyField(initial=0) individual_share = models.CurrencyField(initial=0) hi_cont = models.IntegerField(initial=0) cont_cgp = models.IntegerField() highest_contibutors = models.StringField() players_in_round = models.StringField() round_contributions = models.CurrencyField() summed_round_contributions = models.CurrencyField() current_treatment = models.IntegerField() g_number = models.IntegerField(initial=0) self_group = models.IntegerField() paired_group = models.IntegerField() class Player(BasePlayer): contribution = models.CurrencyField( min=0, max=C.ENDOWMENT, label="How much will you contribute?" ) total_payout = models.CurrencyField() treatment_progress = models.IntegerField() per_round_contribution = models.CurrencyField() round_hi_contributor_rank = models.IntegerField(initial=0) segment_hi_contributor = models.IntegerField() highest_cont = models.IntegerField() ss_id = models.IntegerField() player_identifier = models.StringField() g_number = models.IntegerField(label='Enter your group number:') group_number = models.IntegerField(label='Enter your group number:') cur_treatment = models.IntegerField() passcode = models.IntegerField(label='Enter continuation code:') number_in_group = models.IntegerField() motivation_team = models.IntegerField(choices=list(range(1, 11)), label='How motivated were you to contribute to the public good to benefit your team? Please rate your motivation from 1 (not at all motivated) to 10 (extremely motivated).', widget=widgets.RadioSelect) motivation_payment = models.IntegerField(choices=list(range(1, 11)), label='How motivated were you to contribute to the public good because of the hopes of a higher payout? Please rate your motivation from 1 (not at all motivated) to 10 (extremely motivated).', widget=widgets.RadioSelect) feedback_presence = models.LongStringField(label='Did the presence of the feedback during the competition rounds affect your decision to contribute to the public good? If so, how?') general_feedback = models.LongStringField(label = 'Is there anything else that you would like to share about your experience in the experiment?') ### Functions ### def creating_session(subsession: Subsession): labels = [] num_players = subsession.get_players() for i in range(len(subsession.get_players())): lbl_str = "T.{}|S.{}".format(C.Treatment, i) labels.append(lbl_str) for player, label in zip(subsession.get_players(), labels): player.participant.label = label if subsession.round_number == 1: subsession.group_randomly() else: subsession.group_like_round(1) def treatment_sequence_number(player): participant = player.participant try: participant.vars['treatment_progress'] += 1 player.treatment_progress = participant.treatment_progress except AttributeError: participant.vars['treatment_progress'] = 1 player.treatment_progress = 1 except KeyError: participant.vars['treatment_progress'] = 1 player.treatment_progress = 1 def total_payments(player): participant = player.participant try: participant.vars['total_payout'] += player.payoff except AttributeError: participant.vars['total_payout'] = player.payoff except KeyError: participant.vars['total_payout'] = player.payoff player.total_payout = participant.total_payout def re_remover(arg, reg_ex): arg = str(arg) a = re.sub(reg_ex, '', arg) return a def formatting_remover(player, beginning, end): op = 0 beginning = int(beginning) # Adding 2 to the end because range creates a list up to but not including the end and another because python starts at 0 and rounds begin at 1 end = int(end) + 2 list_of_rounds = list(range(beginning, end)) llor = len(list_of_rounds) for round in range(1, llor): rc = str(player.in_round(round).contribution) rc = int(re_remover(rc, "\s\D*")) op = op + rc return op def reverse_dict(lst): reversed_lst = lst[::-1] return dict(zip(lst, reversed_lst)) def contributions_adder_for_HC(p): round_contributions = p.contribution # if p.round_number == 1: # round_contributions = p.contribution # elif p.round_number == 4: # round_contributions = formatting_remover(p, 2, 4) # elif p.round_number == 7: # round_contributions = formatting_remover(p, 5, 7) # elif p.round_number == 10: # round_contributions = formatting_remover(p, 8, 10) # else: # round_contributions = 999 return round_contributions def round_contributions_func(players, roundstart, roundend): op = list() if roundend: for i in range(roundstart, roundend + 1): op.append([p.in_round(i).contribution for p in players]) return sum(op) else: return [p.in_round(roundstart).contribution for p in players] def wrong_team(lst): counter = {} lonesome = None new_group = None for player in lst: if player not in counter: counter[player] = 0 counter[player] += 1 print(counter) for player, count in counter.items(): if count == 1: lonesome = player elif count > 3: lonesome = player if lonesome is None: return ['W', 'W'] for player, count in counter.items(): if count == 2 and player != lonesome: new_group = player break if new_group is None: return ['W', 'W'] op = [lonesome, new_group] if op is not None: return op if op is None: return ['W', 'W'] # def highest_contributor(group): # all_players = group.get_players() # round_contributions = [p.contribution for p in all_players] # round_contributions.sort() # ranked_contributions = round_contributions # for p in all_players: # if p.contribution == ranked_contributions[0]: # p.round_hi_contributor_rank = 1 # elif p.contribution == ranked_contributions[1]: # p.round_hi_contributor_rank = 2 # else: # p.round_hi_contributor_rank = 3 # if group.round_number == 1: # round_contributions = round_contributions_func(all_players,1) # summed_round_contributions = sum(round_contributions) # round_contributions.sort() # ranked_contributions = round_contributions # highest_cont = max(round_contributions) # for p in all_players: # if p.in_round(1).contribution == ranked_contributions[0]: # p.round_hi_contributor_rank = 1 # elif p.in_round(1).contribution == ranked_contributions[1]: # p.round_hi_contributor_rank = 2 # else: # p.round_hi_contributor_rank = 3 # if group.round_number == 4: # round_contributions = round_contributions_func(all_players, 2,4) # summed_round_contributions = sum(round_contributions) # round_contributions.sort() # ranked_contributions = round_contributions # highest_cont = max(round_contributions) # for p in all_players: # if p.in_round(1).contribution == ranked_contributions[0]: # p.round_hi_contributor_rank = 1 # elif p.in_round(1).contribution == ranked_contributions[1]: # p.round_hi_contributor_rank = 2 # else: # p.round_hi_contributor_rank = 3 # if group.round_number == 7: # round_contributions = round_contributions_func(all_players, 5,7) # summed_round_contributions = sum(round_contributions) # round_contributions.sort() # ranked_contributions = round_contributions # highest_cont = max(round_contributions) # for p in all_players: # if p.in_round(1).contribution == ranked_contributions[0]: # p.round_hi_contributor_rank = 1 # elif p.in_round(1).contribution == ranked_contributions[1]: # p.round_hi_contributor_rank = 2 # else: # p.round_hi_contributor_rank = 3 # if group.round_number == 10: # round_contributions = round_contributions_func(all_players, 8,10) # summed_round_contributions = sum(round_contributions) # round_contributions.sort() # ranked_contributions = round_contributions # highest_cont = max(round_contributions) # for p in all_players: # if p.in_round(1).contribution == ranked_contributions[0]: # p.round_hi_contributor_rank = 1 # elif p.in_round(1).contribution == ranked_contributions[1]: # p.round_hi_contributor_rank = 2 # else: # p.round_hi_contributor_rank = 3 # if round_contributions == highest_cont: # return 1 # else: # return 0 def contributions_total(g): players = g.get_players() op = 0 for p in players: ind_contribution = p.contribution print('cont func individual contribution', ind_contribution) op = sum([op, ind_contribution]) print('==============================================') print('cont func summed contribution', op) return op # PAGES ### Shuffling teams between rounds ### # class ShuffleWaitPage(WaitPage): # wait_for_all_groups = True # # def after_all_players_arrive(self): # if some_condition: # self.subsession.group_randomly() class InteractiveAlert(Page): form_model = 'player' form_fields = ['passcode'] @staticmethod def is_displayed(self): valid_treatments = [3, 4] return self.round_number == 1 and C.Treatment in valid_treatments and C.AlertON == 1 @staticmethod def error_message(player: Player, values): solution = dict(passcode=1891) if values != solution: return "Please see experimentor for continuation code." class GroupAssigner(Page): form_model = 'player' form_fields = ['g_number'] @staticmethod def is_displayed(self): valid_treatments = [3, 4] return self.round_number == 1 and C.Treatment in valid_treatments # @staticmethod # def before_next_page(player: Player, timeout_happened): @staticmethod def error_message(player, values): cur_player_group = str(values['g_number']) list_of_group_numbers = [p.field_maybe_none('g_number') for p in player.subsession.get_players()] print('list of group numbers', list_of_group_numbers) full_count = Counter(list_of_group_numbers) print('full -count dict:', full_count) player.number_in_group = full_count[values['g_number']] print('full_count[player.g_number]: ', full_count[values['g_number']]) if player.field_maybe_none('number_in_group') is None: pass elif player.number_in_group >= 3: return 'This group is full, please make sure that you have entered the correct group.' else: pass class SetGroups(WaitPage): wait_for_all_groups = True @staticmethod def after_all_players_arrive(subsession): players = subsession.get_players() print('all players: ', players) len_players = len(players) print('len_players: ', len_players) groups = subsession.get_groups() if subsession.round_number == 1: if C.Treatment in [1, 2]: for g in range(len(groups)): groups[g].g_number = groups[g].id_in_subsession print('group.g_number = ', groups[g].g_number) g_players = groups[g].get_players() for gp in g_players: gp.g_number = groups[g].g_number list_of_tokens = list() list_of_player_defined_g_numbers = [p.g_number for p in players] any_wrong_teams = wrong_team(list_of_player_defined_g_numbers) print('any_wrong_teams:',any_wrong_teams) print('original group matrix: ', subsession.get_group_matrix()) for i in range(len(players)): participant = players[i].participant players[i].ss_id = i + 1 players[i].cur_treatment = C.Treatment if C.Treatment in [1, 2]: participant.group_identity = players[i].group.in_round(1).g_number elif C.Treatment in [3, 4]: if players[i].g_number is None: print("player.g_number: ", players[i].g_number) pass elif any_wrong_teams[0] == 'W': print("wrongtemas op: ", any_wrong_teams) pass elif players[i].g_number == any_wrong_teams[0]: players[i].g_number = int(any_wrong_teams[1]) print("players.g_number: ",players[i].g_number) participant.group_identity = players[i].in_round(1).g_number print('participant.group_identity', participant.group_identity) one_group_id = participant.group_identity treatment_sequence_number(players[i]) if one_group_id not in list_of_tokens: list_of_tokens.append(participant.group_identity) list_of_teams = dict() num_of_groups = int(len(players) / 3 + 1) for g in range(1, num_of_groups): g_name = "Group_{}".format(g) tok_index = int(g - 1) tokens = list_of_tokens[tok_index] list_of_teams[g_name] = [p for p in players if p.participant.vars['group_identity'] == tokens] group_matrix = [] for g in list_of_teams.values(): if g == None: pass while g: new_group = [ g.pop(), g.pop(), g.pop(), ] group_matrix.append(new_group) print(f'group matrix = {group_matrix}') subsession.set_group_matrix(group_matrix) if C.Treatment in [3, 4]: for g in groups: g.g_number = players[0].g_number print('group.g_number = ', g.g_number) elif subsession.round_number > 1: for i in range(len(players)): players[i].ss_id = players[i].in_round(1).ss_id players[i].g_number = players[i].participant.group_identity if type(players[i].group.g_number) == int: pass else: players[i].group.g_number = players[i].in_round(1).g_number # auto advances to next page timeout_seconds = .01 class Landing_Page(WaitPage): # @staticmethod # def is_displayed(self): # return self.round_number == 1 @staticmethod def after_all_players_arrive(group: Group): players = group.in_round(1).get_players() print(group.g_number) print('landing page all players = ', players) p_group_num = [p.participant.group_identity for p in players] print('landing page all players g numbers = ', p_group_num) print('landing page player g number = ', players[0].g_number) if group.g_number == 0: print('landing page group_gnumber pre intervention: ', group.g_number) group.g_number = players[0].g_number print('landing page group_gnumber post intervention: ', group.g_number) if group.round_number == 1: for i in range(len(players)): ##participant = players[i].participant players[i].ss_id = i + 1 ##participant.vars['subject_identifier'] = players[i].ss_id else: for i in range(len(players)): ##participant = players[i].participant players[i].ss_id = players[i].in_round(1).ss_id ##participant.vars['subject_identifier'] = players[i].ss_id players[i].g_number = players[i].in_round(1).g_number identifier_string = str(f"G.{group.g_number}") for p in players: p.player_identifier = str(f"{identifier_string}.P.{p.ss_id}") # print(f"completed player identifier{p.player_identifier}") # auto advances to next page timeout_seconds = .01 class Contribute(Page): form_model = 'player' form_fields = ['contribution'] class ResultsWaitPage(WaitPage): wait_for_all_groups = True def after_all_players_arrive(subsession: Subsession): cont_ogp_int = 0 groups = subsession.get_groups() players = subsession.get_players() list_of_g_numbers = [] if subsession.round_number == 1: for team in groups: print('group.g_number = ', team.g_number) if team.g_number not in list_of_g_numbers: list_of_g_numbers.append(team.g_number) print('list of g numbers = ', list_of_g_numbers) team_list_len = len(list_of_g_numbers) randomized_teams = random.sample(list_of_g_numbers, team_list_len) print('randomized teams = ', randomized_teams) k_teams = list(randomized_teams) team_dict_new = reverse_dict(k_teams) print('team_dict = ', team_dict_new) for g in groups: for dict_key_index in team_dict_new.keys(): if g.g_number == dict_key_index: print('g number from g.gnumber', g.g_number) g.self_group = dict_key_index print('g number from dict_key_index', dict_key_index) g.paired_group = team_dict_new[dict_key_index] print('other g number from team_dict_new[dict_key_index]', team_dict_new[dict_key_index]) else: groups = subsession.get_groups() for g in groups: g.self_group = g.in_round(1).self_group g.paired_group = g.in_round(1).paired_group for g in groups: players = g.get_players() print('results waitpage players', players, '| group: ', g) contributions = contributions_total(g) g.total_contribution = contributions print("{} contribution = {}".format(g, g.total_contribution)) g.individual_share = ( g.total_contribution * C.MULTIPLIER / C.PLAYERS_PER_GROUP) h_contributor_litmus = [] g_lvl_players = g.get_players() for p in g_lvl_players: p.payoff = C.ENDOWMENT - p.contribution + g.individual_share total_payments(p) i_contribution = contributions_adder_for_HC(p) print( f'contribution per_player = {i_contribution} | player_id = {p.ss_id} | group number = {p.g_number}') h_contributor_litmus.append(i_contribution) h_contributor_litmus.sort() sorted_h_cont = h_contributor_litmus contributions = [p.contribution for p in g_lvl_players] contributions.sort() sorted_contributions = contributions for p in g_lvl_players: participant = p.participant if p.contribution == sorted_contributions[0]: p.round_hi_contributor_rank = 1 elif p.contribution == sorted_contributions[1]: p.round_hi_contributor_rank = 2 else: p.round_hi_contributor_rank = 3 # if contributions_adder_for_HC(p) == sorted_h_cont[0]: # p.segment_hi_contributor = 1 # elif contributions_adder_for_HC(p) == sorted_h_cont[1]: # p.segment_hi_contributor = 2 # else: # p.segment_hi_contributor = 3 for g in groups: cgp = g.get_players() cg_litmus = [] og_litmus = [] for p in cgp: i_contribution = contributions_adder_for_HC(p) cg_litmus.append(i_contribution) contributions_cgp = sum(cg_litmus) cont_cgp_int = int(re_remover(contributions_cgp, "\s\D*")) comparison_group = g.in_round(1).paired_group for og in range(len(groups)): if groups[og].self_group == comparison_group: ogp = groups[og].get_players() for p in ogp: i_contribution = contributions_adder_for_HC(p) og_litmus.append(i_contribution) contributions_ogp = sum(og_litmus) cont_ogp_int = int(re_remover(contributions_ogp, "\s\D*")) g.cont_cgp = cont_cgp_int if cont_cgp_int > cont_ogp_int: g.hi_cont = 1 print(f'Group {g} wins') elif cont_cgp_int == cont_ogp_int: g.hi_cont = 101 print(f'Group {g} ties') else: g.hi_cont = 0 print(f'Group {g} loses') for player in g.get_players(): player.highest_cont = cont_cgp_int class Results(Page): @staticmethod def vars_for_template(player: Player): from otree.settings import POINTS_CUSTOM_NAME participant = player.participant contributions = [p.contribution for p in player.group.get_players()] round_num = str(player.group.round_number) valid_treatments = [1, 3] if C.Treatment in valid_treatments: return dict(hc=999, progress=(participant.treatment_progress - 1) + (player.round_number * .1), prog_perc=round( 25 * ((participant.treatment_progress - 1) + (player.round_number / C.NUM_ROUNDS)), 2)) else: return dict(hc=player.group.hi_cont, progress=(participant.treatment_progress - 1) + (player.round_number * .1), prog_perc=round( 25 * ((participant.treatment_progress - 1) + (player.round_number / C.NUM_ROUNDS)), 2)) class FinalSurvey(Page): @staticmethod def is_displayed(player: Player): participant = player.participant return player.round_number == C.NUM_ROUNDS and participant.treatment_progress == C.Total_Treatments form_model = 'player' form_fields = [ 'motivation_team','motivation_payment', 'feedback_presence', 'general_feedback'] class EndOfTreatment(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS def vars_for_template(player: Player): participant = player.participant if participant.treatment_progress == 4: return dict(is_final=1) else: return dict(is_final=0) page_sequence = [InteractiveAlert, GroupAssigner, SetGroups, Landing_Page, Contribute, ResultsWaitPage, Results, FinalSurvey, EndOfTreatment]