import random from collections import Counter from otree.api import * author = 'Nathaniel Burke' doc = """ Identity team based public goods game with switching. """ class Constants(BaseConstants): name_in_url = 'team_game' players_per_group = 4 num_rounds = 20 max_players_in_team = 6 total_players = 8 switching_rounds = [3, 6, 9, 12, 15, 18] multiplier = [ 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, ] # access multiplier like multiplier[group.team_size-1] ppr = [ 1.000, 0.625, 0.500, 0.438, 0.400, 0.375, 0.357, 0.344, ] # access ppr like ppr[group.team_size-1] team_ranges = [[220, 420], [100, 300]] # 1st is A, 2nd is B class Player(BasePlayer): team = models.IntegerField() # team 1 is Female, team 2 is Male desired_team = models.IntegerField( # 1 is stay, 2 is switch choices=[ [1, 'I want to stay with my team'], [2, 'I want to switch teams'], ], widget=widgets.RadioSelect, label="Would you like to switch teams?", ) votes_for_switch = models.IntegerField(initial=0) votes_against_switch = models.IntegerField(initial=0) switch_honored = models.BooleanField() label = models.StringField() voted_for_players = models.StringField() sex = models.IntegerField() endowment = models.CurrencyField() payout = models.CurrencyField() votingchannel = models.IntegerField() comprehension_1 = models.FloatField(label='What is your team multiplier if you have 4 members?') comprehension_2 = models.FloatField( label='If your team has 5 members and your team contributes 500 points in total to the Team account, how many points will be distributed to each team member?' ) comprehension_3 = models.IntegerField( choices=[ [1, 'Same'], [2, 'Different'], ], widget=widgets.RadioSelect, label="Do the two teams have the same endowment range or different ranges?", ) contribution = models.CurrencyField(min=0, label="How much will you contribute?") def make_field(fChoices, fLabel): return models.IntegerField( choices=fChoices, widget=widgets.RadioSelect, label=fLabel, ) class Subsession(BaseSubsession): pass class Group(BaseGroup): total_contribution = models.CurrencyField() individual_share = models.CurrencyField() team_size = models.IntegerField() average_share = models.CurrencyField() pass # FUNCTIONS def comprehension_1_error_message(player: Player, comprehension_1): print('value is', comprehension_1) if comprehension_1 != 1.75: return 'Try again. Check the table to see what your team multiplier is for different number of people on the team' def comprehension_2_error_message(player: Player, comprehension_2): print('value is', comprehension_2) if comprehension_2 != 200: return 'Try again. Check the table to see what the multiplier factor is for having 5 team members. Then, multiply the points by that factor. Last, divide that number by the number of members on the team.' def comprehension_3_error_message(player: Player, comprehension_3): print('value is', comprehension_3) if comprehension_3 != 2: return 'Try again. Look at the table above that lists the multipliers based on how many people are on the team' def get_average_contribution(player: Player): contributionSum = 0 rounds = player.in_rounds(1, player.round_number) for round in rounds: contributionSum += round.contribution return cu(contributionSum) / len(rounds) def get_average_contribution_pct(player: Player): contributionSum = 0 endowmentSum = 0 rounds = player.in_rounds(1, player.round_number) for round in rounds: contributionSum += float(round.contribution) endowmentSum += float(round.endowment) return (contributionSum / endowmentSum) * 100 def get_label(player: Player): return player.participant.label def contribution_max(player: Player): return player.endowment def get_team_contribution(player: Player): return player.group.total_contribution - player.contribution def get_team_size(player: Player): return len(player.group.get_players()) def set_payoffs(player: Player): rounds = player.in_rounds(1, player.round_number) for round in rounds: payoffSum += float(round.payoff) player.participant.vars['group_identity_switch_payoff'] = player.payoffSum def creating_session(subsession: Subsession): print('in creating_session', subsession.round_number) def set_player_endowment(subsession: Subsession): for p in subsession.get_players(): p.sex = p.participant.vars['player_sex'] if p.sex == 1: p.endowment = cu( random.randint(Constants.team_ranges[0][0], Constants.team_ranges[0][1]) ) elif p.sex == 2: p.endowment = cu( random.randint(Constants.team_ranges[1][0], Constants.team_ranges[1][1]) ) def set_player_endowment_2(subsession: Subsession): for p in subsession.get_players(): if p.team == 1: p.endowment = cu( random.randint(Constants.team_ranges[0][0], Constants.team_ranges[0][1]) ) elif p.team == 2: p.endowment = cu( random.randint(Constants.team_ranges[1][0], Constants.team_ranges[1][1]) ) def voting_channel(subsession: Subsession): for p in subsession.get_players(): if p.team == 1: p.votingchannel = 100 + subsession.round_number elif p.team == 2: p.votingchannel = 200 + subsession.round_number def group_players(subsession: Subsession): players = subsession.get_players() firstT = [p for p in players if p.sex == 1] secondT = [p for p in players if p.sex == 2] group_matrix = [] group_matrix.append(firstT) group_matrix.append(secondT) subsession.set_group_matrix(group_matrix) def group_players_new(subsession: Subsession): players = subsession.get_players() firstT = [p for p in players if p.team == 1] secondT = [p for p in players if p.team == 2] group_matrix = [] group_matrix.append(firstT) group_matrix.append(secondT) subsession.set_group_matrix(group_matrix) def sync_team_by_sex(subsession: Subsession): players = subsession.get_players() for p in players: p.team = p.sex def sync_team_by_previous(subsession: Subsession): players = subsession.get_players() for p in players: if ( p.in_round(subsession.round_number - 1).desired_team == 2 and p.in_round(subsession.round_number - 1).switch_honored == True ): if p.in_round(subsession.round_number - 1).team == 2: p.team = 1 else: p.team = 2 else: p.team = p.in_round(subsession.round_number - 1).team def carry_over_data(subsession: Subsession): for p in subsession.get_players(): # p.endowment = c(p.in_round(self.round_number - 1).payout) p.sex = p.in_round(subsession.round_number - 1).sex def switch_teams(subsession: Subsession): groups = subsession.get_groups() total_players = 0 for group in groups: total_players += len(group.get_players()) for group in groups: group_size = len(group.get_players()) opposite_group_size = total_players - group_size available_spots = Constants.max_players_in_team - opposite_group_size ordered = get_sorted_switch_players(group) # majority ordered maj_ordered = [] for player in ordered: if player.votes_for_switch > opposite_group_size / 2: maj_ordered.append(player) while available_spots > 0 and len(maj_ordered) > 0: maj_ordered[len(maj_ordered) - 1].switch_honored = True maj_ordered.pop() def count_votes(subsession: Subsession): # get all the players players = subsession.get_players() # use delimiter (,) to put votes in a list votes = [] for player in players: if player.voted_for_players != "-1": if not player.voted_for_players == None: votes += player.voted_for_players.split(",") # count votes in the list and store in a dictionary dictionary = Counter(votes) print(dictionary) # Find users based on keys and assign their votes for with dictionary value for key, value in dictionary.items(): # Find user based on key for targetplayer in players: if str(key).strip() == str(targetplayer.participant_id).strip(): targetplayer.votes_for_switch = value print("test") print(value) break def attempted(group: Group): players = group.get_players() attempted = [] for player in players: if player.desired_team == 2: attempted.append(player) return attempted def get_joined_team(group: Group): players = group.subsession.get_players() joined = [] for player in players: if player.switch_honored == True and player.team != group.id_in_subsession: joined.append(player) return joined def get_left_team(group: Group): players = group.subsession.get_players() left = [] for player in players: if player.switch_honored == True and player.team == group.id_in_subsession: left.append(player) return left def get_other_sdp(group: Group): players = group.subsession.get_players() otherPlayers = [] for player in players: if group.id_in_subsession != player.team and player.desired_team == 2: otherPlayers.append(player) return otherPlayers def get_other_sdp_size(group: Group): other = get_other_sdp(group) return len(other) def get_switch_desired_players(group: Group): players = group.get_players() switchers = [] for p in players: if p.desired_team == 2: switchers.append(p) return switchers def get_sorted_switch_players(group: Group): # call this after voting players = get_switch_desired_players(group) players_ordered = [] # lowest votes: idx = 0, highest votes: idx = len(players_ordered)-1 for p in players: idx = 0 inserted = False while idx < len(players_ordered): if p.votes_for_switch >= players_ordered[idx].votes_for_switch: idx += 1 else: players_ordered.insert(idx, p) inserted = True break if not inserted: players_ordered.append(p) return players_ordered def get_multiplier_value(group: Group): return Constants.multiplier[group.team_size - 1] def get_ppr_value(group: Group): return Constants.ppr[group.team_size - 1] def get_total_contribution_with_multiplier(group: Group): return group.total_contribution * get_multiplier_value(group) def get_ppr_value(group: Group): return Constants.ppr[group.team_size - 1] # PAGES # class WelcomePage(Page): # def is_displayed(self): # return self.round_number == 1 # form_model = 'player' # form_fields = ['sex'] # class Instructions1(Page): # def is_displayed(self): # return self.round_number == 1 # pass # # class Instructions2(Page): # def is_displayed(self): # return self.round_number == 1 # pass # class Instructions3(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 form_model = 'player' form_fields = ['comprehension_3'] class Instructions4(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 form_model = 'player' form_fields = ['comprehension_1', 'comprehension_2'] class Instructions5(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 pass class Instructions6(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 pass class InstructionsWaitPage(WaitPage): wait_for_all_groups = True @staticmethod def is_displayed(player: Player): return player.round_number == 1 pass class Instructions7(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 pass class ShuffleWaitPage(WaitPage): wait_for_all_groups = True @staticmethod def after_all_players_arrive(subsession: Subsession): if subsession.round_number == 1: set_player_endowment(subsession) sync_team_by_sex(subsession) group_players(subsession) else: carry_over_data(subsession) sync_team_by_previous(subsession) set_player_endowment_2(subsession) group_players_new(subsession) groups = subsession.get_groups() for idx, g in enumerate(groups): g.team_size = len(g.get_players()) print('There are ', len(g.get_players()), ' player(s) in group ', idx + 1) matrix = subsession.get_group_matrix() for row in matrix: print(row) class Contribute(Page): form_model = 'player' form_fields = ['contribution'] class ResultsWaitPage(WaitPage): wait_for_all_groups = True @staticmethod def after_all_players_arrive(subsession: Subsession): group = subsession.get_groups() for g in group: players = g.get_players() contributions = [cu(p.contribution) for p in players] g.total_contribution = sum(contributions) g.individual_share = ( cu(g.total_contribution) * Constants.multiplier[len(players) - 1] / len(players) ) g.average_share = g.individual_share for p in players: p.payout = cu(p.endowment) - cu(p.contribution) + cu(g.individual_share) p.payoff = p.payout print(p.payoff) @staticmethod def before_next_page(player: Player, timeout_happened): set_payoffs(player) class Results(Page): pass class SwitchingDecision(Page): @staticmethod def is_displayed(player: Player): for x in Constants.switching_rounds: if x == player.round_number: return True return False form_model = 'player' form_fields = ['desired_team'] class SwitchingWaitPage(WaitPage): @staticmethod def is_displayed(player: Player): for x in Constants.switching_rounds: if x == player.round_number: return True return False wait_for_all_groups = True class SwitchingVoting(Page): @staticmethod def is_displayed(player: Player): if len(player.group.get_players()) >= Constants.max_players_in_team: return False else: for x in Constants.switching_rounds: if x == player.round_number: group = player.subsession.get_groups() for g in group: players = g.get_players() for p in players: p.votingchannel = p.team * 100 + player.round_number return True return False form_model = 'player' form_fields = ['voted_for_players'] @staticmethod def error_message(player: Player, values): print('values is', values) class SwitchingGenerateResults(WaitPage): @staticmethod def is_displayed(player: Player): for x in Constants.switching_rounds: if x == player.round_number: return True return False wait_for_all_groups = True @staticmethod def after_all_players_arrive(subsession: Subsession): count_votes(subsession) switch_teams(subsession) class SwitchingResults(Page): @staticmethod def is_displayed(player: Player): for x in Constants.switching_rounds: if x == player.round_number: return True return False page_sequence = [ Instructions3, Instructions4, Instructions5, Instructions6, InstructionsWaitPage, Instructions7, ShuffleWaitPage, Contribute, ResultsWaitPage, Results, SwitchingDecision, SwitchingWaitPage, SwitchingVoting, SwitchingGenerateResults, SwitchingResults, ]