from otree.api import * import pandas as pd doc = "Avatar Experiment" # CONSTANTS class C(BaseConstants): NAME_IN_URL = 'cabbages' PLAYERS_PER_GROUP = 4 NUM_ROUNDS = 32 ENDOWMENT = cu(20) MULTIPLIER = 2 REPEATED_ROUNDS = NUM_ROUNDS # Number of consecutive rounds repeated with the same group PARTICIPATION_FEE = 0 TOPTYPE_LIST = [ "NoHair", "Hat", "Hijab", "Turban", "WinterHat1", "WinterHat2", "WinterHat3", "WinterHat4", "LongHairBigHair", "LongHairBob", "LongHairBun", "LongHairCurly", "LongHairCurvy", "LongHairDreads", "LongHairFrida", "LongHairFro", "LongHairFroBand", "LongHairNotTooLong", "LongHairShavedSides", "LongHairMiaWallace", "LongHairStraight", "LongHairStraight2", "LongHairStraightStrand", "ShortHairDreads01", "ShortHairDreads02", "ShortHairFrizzle", "ShortHairShaggyMullet", "ShortHairShortCurly", "ShortHairShortFlat", "ShortHairShortRound", "ShortHairShortWaved", "ShortHairSides", "ShortHairTheCaesar", "ShortHairTheCaesarSidePart", ] HAIRCOLOR_LIST = [ "Auburn", "Black", "Blonde", "BlondeGolden", "Brown", "BrownDark", "PastelPink", "Blue", "Platinum", "Red", "SilverGray", ] ACCESSORIESTYPE_LIST = [ "Blank", "Kurt", "Prescription01", "Prescription02", "Round", "Sunglasses", "Wayfarers", ] HATCOLOR_LIST = [ "Blank", "Black", "Blue02", "Blue03", "Gray01", "Gray02", "Heather", "PastelBlue", "PastelGreen", "PastelOrange", "PastelRed", "PastelYellow", "Pink", "Red", "White", ] FACIALHAIRTYPE_LIST = [ "Blank", "BeardMedium", "BeardLight", "BeardMajestic", "MoustacheFancy", "MoustacheMagnum", ] FACIALHAIRCOLOR_LIST = [ "Auburn", "Black", "Blonde", "BlondeGolden", "Brown", "BrownDark", "Platinum", "Red", ] CLOTHETYPE_LIST = [ "BlazerShirt", "BlazerSweater", "CollarSweater", "GraphicShirt", "Hoodie", "Overall", "ShirtCrewNeck", "ShirtScoopNeck", "ShirtVNeck", ] CLOTHECOLOR_LIST = [ "Black", "Blue02", "Blue03", "Gray01", "Gray02", "Heather", "PastelBlue", "PastelGreen", "PastelOrange", "PastelRed", "PastelYellow", "Pink", "Red", "White", ] EYETYPE_LIST = [ "Default", "Close", "Cry", "Dizzy", "EyeRoll", "Happy", "Hearts", "Side", "Squint", "Surprised", "Wink", "WinkWacky", ] EYEBROWTYPE_LIST = [ "Default", "Angry", "AngryNatural", "DefaultNatural", "FlatNatural", "RaisedExcited", "RaisedExcitedNatural", "SadConcerned", "SadConcernedNatural", "UnibrowNatural", "UpDown", "UpDownNatural", ] MOUTHTYPE_LIST = [ "Default", "Concerned", "Disbelief", "Eating", "Grimace", "Sad", "ScreamOpen", "Serious", "Smile", "Tongue", "Twinkle", "Vomit", ] SKINCOLOR_LIST = [ "Light", "Tanned", "Yellow", "Pale", "Brown", "DarkBrown", "Black", ] class Subsession(BaseSubsession): pass class Group(BaseGroup): total_contribution = models.CurrencyField() individual_share = models.CurrencyField() class Player(BasePlayer): topType_Player = models.StringField(initial="NoHair") hairColor_Player = models.StringField(initial="Default") accessoriesType_Player = models.StringField(initial="Blank") hatColor_Player = models.StringField(initial="Default") facialHairType_Player = models.StringField(initial="Blank") facialHairColor_Player = models.StringField(initial="Default") clotheType_Player = models.StringField(initial="BlazerShirt") clotheColor_Player = models.StringField(initial="Default") eyeType_Player = models.StringField(initial="Default") eyebrowType_Player = models.StringField(initial="Default") mouthType_Player = models.StringField(initial="Default") skinColor_Player = models.StringField(initial="Light") imgtag_Player = models.StringField( initial="https://avataaars.io/?avatarStyle=Circle&topType=NoHair&hairColor_Player=Default&accessoriesType" "=Blank&facialHairType=Blank&clotheType=BlazerShirt&eyeType=Default&eyebrowType=Default&mouthType" "=Default&skinColor=Light") randomidnumber_Player = models.IntegerField(initial=-1) contribution = models.CurrencyField( min=0, max=C.ENDOWMENT, ) race = models.StringField( choices=['American Indian or Alaska Native', 'Asian', 'Black or African American', 'Native Hawaiian or Other Pacific Islander', 'White', 'Some Other Race', 'Prefer not to Answer'], ) ethnicity = models.StringField( choices=['Hispanic or Latino', 'Not Hispanic or Latino', 'Prefer not to Answer'], ) gender = models.StringField( choices=['Man', 'Nonbinary', 'Other', 'Woman', 'Prefer not to Answer'], ) representedByAvatar = models.IntegerField( initial=-1, choices=[ [1, 'Strongly Disagree'], [2, 'Disagree'], [3, 'Somewhat Disagree'], [4, 'Neutral'], [5, 'Somewhat Agree'], [6, 'Agree'], [7, 'Strongly Agree'] ], widget=widgets.RadioSelect, ) knowGroupMember1 = models.IntegerField( initial=-1, choices=[ [1, 'Strongly Disagree - I did not know them at all'], [2, 'Disagree'], [3, 'Somewhat Disagree'], [4, 'Neutral'], [5, 'Somewhat Agree'], [6, 'Agree'], [7, 'Strongly Agree - I knew them very well'], ], widget=widgets.RadioSelect, ) knowGroupMember2 = models.IntegerField( initial=-1, choices=[ [1, 'Strongly Disagree - I did not know them at all'], [2, 'Disagree'], [3, 'Somewhat Disagree'], [4, 'Neutral'], [5, 'Somewhat Agree'], [6, 'Agree'], [7, 'Strongly Agree - I knew them very well'], ], widget=widgets.RadioSelect, ) knowGroupMember3 = models.IntegerField( initial=-1, choices=[ [1, 'Strongly Disagree - I did not know them at all'], [2, 'Disagree'], [3, 'Somewhat Disagree'], [4, 'Neutral'], [5, 'Somewhat Agree'], [6, 'Agree'], [7, 'Strongly Agree - I knew them very well'], ], widget=widgets.RadioSelect, ) # WRITE THE HISTORY TABLE ROW FOR A PARTICULAR ROUND def write_history_row(self, player, round_number): # Initialize the row round_row = [] # Identify the player & group in the that round round_player = player.in_round(round_number) # round_group = player.group.in_round(round_number) # Append player's contributions in the most recent round round_row.append(round_player.contribution) # Append group members' contributions in most recent round for p in round_player.get_others_in_group(): round_row.append(p.contribution) # Shuffle row from largest to smallest if a no-history session if player.session.config['historyBigToSmall'] == 1: round_row.sort(reverse=True) # Append round number to the beginning of the row round_row.insert(0, round_number) return round_row def write_history_table(self, player): # Writes the history of contributions up until the current round data = [] for r in range(1, min(player.round_number, C.NUM_ROUNDS)): data.append(player.write_history_row(player, r)) return data # SCRIPTS def creating_session(subsession: Subsession): # ASSIGN LABELS labels = ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10', 'P11', 'P12', 'P13', 'P14', 'P15', 'P16', 'P17', 'P18', 'P19', 'P20', 'P21', 'P22', 'P23', 'P24'] for player, label in zip(subsession.get_players(), labels): player.participant.label = label # GENERATE ID NUMBERS FOR ID NUMBER TREATMENT if subsession.session.config['randomIDNumbers'] == 1 and subsession.round_number == 1: import random random_idnumber_list = random.sample(range(125, 999), 50) for p in subsession.get_players(): p.randomidnumber_Player = random_idnumber_list[p.id_in_subsession - 1] # Avatar Fields are Null p.topType_Player = "Null" p.hairColor_Player = "Null" p.accessoriesType_Player = "Null" p.hatColor_Player = "Null" p.facialHairType_Player = "Null" p.facialHairColor_Player = "Null" p.clotheType_Player = "Null" p.clotheColor_Player = "Null" p.eyeType_Player = "Null" p.eyebrowType_Player = "Null" p.mouthType_Player = "Null" p.skinColor_Player = "Null" p.imgtag_Player = "Null" # GENERATE RANDOM AVATAR FOR AvatarCreation == 0 sessions if subsession.session.config['randomIDNumbers'] == 0 and subsession.session.config[ 'avatarCreation'] == 0 and subsession.round_number == 1: import random for p in subsession.get_players(): p.topType_Player = random.choice(C.TOPTYPE_LIST) p.hairColor_Player = random.choice(C.HAIRCOLOR_LIST) p.accessoriesType_Player = random.choice(C.ACCESSORIESTYPE_LIST) p.hatColor_Player = random.choice(C.HATCOLOR_LIST) p.facialHairType_Player = random.choice(C.FACIALHAIRTYPE_LIST) p.facialHairColor_Player = random.choice(C.FACIALHAIRCOLOR_LIST) p.clotheType_Player = random.choice(C.CLOTHETYPE_LIST) p.clotheColor_Player = random.choice(C.CLOTHECOLOR_LIST) p.eyeType_Player = random.choice(C.EYETYPE_LIST) p.eyebrowType_Player = random.choice(C.EYEBROWTYPE_LIST) p.mouthType_Player = random.choice(C.MOUTHTYPE_LIST) p.skinColor_Player = random.choice(C.SKINCOLOR_LIST) p.imgtag_Player = ("https://avataaars.io/?avatarStyle=Circle" + "&topType=" + p.topType_Player + "&accessoriesType=" + p.accessoriesType_Player + "&hatColor=" + p.hatColor_Player + "&hairColor=" + p.hairColor_Player + "&facialHairType=" + p.facialHairType_Player + "&facialHairColor=" + p.facialHairColor_Player + "&clotheType=" + p.clotheType_Player + "&clotheColor=" + p.clotheColor_Player + "&eyeType=" + p.eyeType_Player + "&eyebrowType=" + p.eyebrowType_Player + "&mouthType=" + p.mouthType_Player + "&skinColor=" + p.skinColor_Player) # Random ID number is Zero p.randomidnumber_Player = 0 # SET RANDOM ID NUMBER EQUAL TO ZERO FOR AvatarCreation == 1 SESSIONS if subsession.session.config['avatarCreation'] == 1 and subsession.round_number == 1: for p in subsession.get_players(): p.randomidnumber_Player = 0 # NULL ALL FIELDS OTHER THAN IMGTAG FOR photograph == 1 SESSIONS if subsession.session.config['photograph'] == 1 and subsession.round_number == 1: for p in subsession.get_players(): p.randomidnumber_Player = 0 p.topType_Player = "Null" p.hairColor_Player = "Null" p.accessoriesType_Player = "Null" p.hatColor_Player = "Null" p.facialHairType_Player = "Null" p.facialHairColor_Player = "Null" p.clotheType_Player = "Null" p.clotheColor_Player = "Null" p.eyeType_Player = "Null" p.eyebrowType_Player = "Null" p.mouthType_Player = "Null" p.skinColor_Player = "Null" # Player imgtag is in the app's static folder and filename is the participant label p.imgtag_Player = "\"/static/avatar/" + p.participant.label + ".JPG\"" # REGROUP AFTER REPEATED_ROUNDS ROUNDS FOR REPEATED, OR AFTER EACH ROUND FOR NOT REPEATED if subsession.session.config['repeatedGame'] == 1: if (subsession.round_number % C.REPEATED_ROUNDS) == 1: subsession.group_randomly() else: subsession.group_like_round(subsession.round_number - ((subsession.round_number - 1) % C.REPEATED_ROUNDS)) elif subsession.session.config['repeatedGame'] == 0: subsession.group_randomly() def set_avatar(group: Group): players = group.get_players() for p in players: prev_player = p.in_round(1) # RANDOM ID TREATMENT p.randomidnumber_Player = prev_player.randomidnumber_Player # AVATAR TREATMENT p.topType_Player = prev_player.topType_Player p.hairColor_Player = prev_player.hairColor_Player p.accessoriesType_Player = prev_player.accessoriesType_Player p.hatColor_Player = prev_player.hatColor_Player p.facialHairType_Player = prev_player.facialHairType_Player p.facialHairColor_Player = prev_player.facialHairColor_Player p.clotheType_Player = prev_player.clotheType_Player p.clotheColor_Player = prev_player.clotheColor_Player p.eyeType_Player = prev_player.eyeType_Player p.eyebrowType_Player = prev_player.eyebrowType_Player p.mouthType_Player = prev_player.mouthType_Player p.skinColor_Player = prev_player.skinColor_Player p.imgtag_Player = prev_player.imgtag_Player def set_payoffs(group: Group): players = group.get_players() contributions = [p.contribution for p in players] group.total_contribution = sum(contributions) group.individual_share = ( group.total_contribution * C.MULTIPLIER / C.PLAYERS_PER_GROUP ) for p in players: p.payoff = C.ENDOWMENT - p.contribution + group.individual_share # PAGES class WaitPage0(WaitPage): pass class Consent(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class CreateAvatar(Page): form_model = 'player' form_fields = ['topType_Player', 'hairColor_Player', 'accessoriesType_Player', 'hatColor_Player', 'facialHairType_Player', 'facialHairColor_Player', 'clotheType_Player', 'clotheColor_Player', 'eyeType_Player', 'eyebrowType_Player', 'mouthType_Player', 'skinColor_Player', 'imgtag_Player', ] @staticmethod def vars_for_template(player: Player): return dict( source_default='https://avataaars.io/?avatarStyle=Circle&topType=NoHair&accessoriesType=Blank ' '&facialHairType=Blank&clotheType=BlazerShirt&eyeType=Default&eyebrowType=Default' '&mouthType=Default&skinColor=Light', ) @staticmethod def is_displayed(player: Player): return player.session.config['randomIDNumbers'] == 0 and player.session.config[ 'avatarCreation'] == 1 and player.round_number == 1 class MeetRandomNumber(Page): @staticmethod def vars_for_template(player: Player): return dict( randomidnumber_Player=player.randomidnumber_Player ) @staticmethod def is_displayed(player: Player): return player.session.config['randomIDNumbers'] == 1 and player.round_number == 1 class MeetRandomAvatar(Page): @staticmethod def vars_for_template(player: Player): return dict( imgtag_player_random=player.imgtag_Player ) @staticmethod def is_displayed(player: Player): return player.session.config['randomIDNumbers'] == 0 and player.session.config[ 'avatarCreation'] == 0 and player.round_number == 1 class VerifyPicture(Page): @staticmethod def vars_for_template(player: Player): return dict( own_imgtag=player.imgtag_Player, ) @staticmethod def is_displayed(player: Player): return player.session.config['photograph'] == 1 and player.round_number == 1 class WaitPageSetAvatar(WaitPage): after_all_players_arrive = set_avatar class WaitPageBeforeR1Contribute(WaitPage): wait_for_all_groups = True @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Contribute(Page): form_model = 'player' form_fields = ['contribution'] @staticmethod def vars_for_template(player: Player): other_players = player.get_others_in_group() contribution_history_rows = range(1, player.round_number) if player.session.config['historyBigToSmall'] == 1: contribution_history_columns = ['Round', '', '', '', ''] elif player.session.config['historyBigToSmall'] == 0 and player.session.config['randomIDNumbers'] == 1: contribution_history_columns = ['Round', 'Player ' + str(player.randomidnumber_Player)] for op in other_players: contribution_history_columns.append('Player ' + str(op.randomidnumber_Player)) elif player.session.config['historyBigToSmall'] == 0 and player.session.config['photograph'] == 1: contribution_history_columns = ['Round', ""] for op in other_players: contribution_history_columns.append("") elif player.session.config['historyBigToSmall'] == 0 and player.session.config['photograph'] == 0 \ and player.session.config['randomIDNumbers'] == 0: contribution_history_columns = ['Round', ""] for op in other_players: contribution_history_columns.append("") contribution_history = pd.DataFrame(data=player.write_history_table(player), index=contribution_history_rows, columns=contribution_history_columns) contribution_history_html = contribution_history.to_html(index=False, escape=False) return dict( own_imgtag=player.imgtag_Player, own_id=player.randomidnumber_Player, other_players=player.get_others_in_group(), other_players_prevperiod=player.in_round(max(1, player.round_number - 1)).get_others_in_group(), round=((player.round_number - 1) % C.REPEATED_ROUNDS) + 1, last_round=((player.round_number - 1) % C.REPEATED_ROUNDS) + 1 - 1, previous_rounds=range(1, min(player.round_number, C.NUM_ROUNDS)), contribution_history_html=contribution_history_html, own_contribution_prevperiod=player.in_round(max(1, player.round_number - 1)).field_maybe_none( 'contribution'), seeOtherAvatars=player.session.config['seeOtherAvatars'], hideOwnAvatar=player.session.config['hideOwnAvatar'], seeIndivContribs=player.session.config['seeIndivContribs'], seeLastContribOnContribScreen=player.session.config['seeLastContribOnContribScreen'], seeHistoryTable=player.session.config['seeHistoryTable'], historyBigToSmall=player.session.config['historyBigToSmall'], randomIDNumbers=player.session.config['randomIDNumbers'], photograph=player.session.config['photograph'], ) class WaitPageSetPayoffs(WaitPage): after_all_players_arrive = set_payoffs class RoundResults(Page): @staticmethod def vars_for_template(player: Player): round_contribs = pd.DataFrame(player.write_history_row(player, player.round_number)).tail(C.PLAYERS_PER_GROUP) # round_contribs_string = round_contribs.to_string(index=False, header=False).replace(" 1 point", " 1 point  ").replace("points", "points  ") round_contribs_string = round_contribs.to_string(index=False, header=False).replace("\n", ",  ") return dict( own_imgtag=player.imgtag_Player, own_id=player.randomidnumber_Player, other_players=player.get_others_in_group(), all_group_players=player.group.get_players(), own_contribution=player.contribution, own_kept=C.ENDOWMENT - player.contribution, group_total=player.group.total_contribution, own_share=player.group.individual_share, own_payoff=player.payoff, seeIndivContribs=player.session.config['seeIndivContribs'], round_contribs=round_contribs_string, round=((player.round_number - 1) % C.REPEATED_ROUNDS) + 1, seeOtherAvatars=player.session.config['seeOtherAvatars'], hideOwnAvatar=player.session.config['hideOwnAvatar'], historyBigToSmall=player.session.config['historyBigToSmall'], randomIDNumbers=player.session.config['randomIDNumbers'], photograph=player.session.config['photograph'], ) class InstructionsBeforeQuestionnaire(Page): @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS class Questionnaire(Page): form_model = 'player' @staticmethod def vars_for_template(player: Player): return dict( hideOwnAvatar=player.session.config['hideOwnAvatar'], randomIDNumbers=player.session.config['randomIDNumbers'], avatarCreation=player.session.config['avatarCreation'], photograph=player.session.config['photograph'], other_players=player.get_others_in_group(), ) @staticmethod def get_form_fields(player: Player): if player.session.config['hideOwnAvatar'] == 1: return ['race', 'ethnicity', 'gender', ] elif player.session.config['randomIDNumbers'] == 1 or ( player.session.config['randomIDNumbers'] == 0 and player.session.config['photograph'] == 0): return ['race', 'ethnicity', 'gender', 'representedByAvatar', ] elif player.session.config['photograph'] == 1: return ['race', 'ethnicity', 'gender', 'knowGroupMember1', 'knowGroupMember2', 'knowGroupMember3'] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS class FinalResults(Page): @staticmethod def vars_for_template(player: Player): return dict( total_payoff="{:.2f}".format(max(player.participant.payoff_plus_participation_fee(), 5)), exp_earnings="{:.2f}".format(max(player.participant.payoff_plus_participation_fee() - 5, 0)), ) @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS page_sequence = [ WaitPage0, Consent, WaitPage0, CreateAvatar, MeetRandomNumber, # MeetRandomAvatar, VerifyPicture, WaitPageSetAvatar, WaitPageBeforeR1Contribute, Contribute, WaitPageSetPayoffs, RoundResults, WaitPage0, InstructionsBeforeQuestionnaire, Questionnaire, WaitPage0, FinalResults, ] # UNUSED CODE # class PayToReplaceRandomAvatar(Page): # form_model = 'player' # form_fields = ['replaceRandomPayment'] # # def before_next_page(player: Player, timeout_happened): # player.payoff -= player.replaceRandomPayment # # # class ReplaceRandomAvatar(Page): # form_model = 'player' # form_fields = ['topType_Player', # 'hairColor_Player', # 'accessoriesType_Player', # 'hatColor_Player', # 'facialHairType_Player', # 'facialHairColor_Player', # 'clotheType_Player', # 'clotheColor_Player', # 'eyeType_Player', # 'eyebrowType_Player', # 'mouthType_Player', # 'skinColor_Player', # 'imgtag_Player', # ] # # @staticmethod # def vars_for_template(player: Player): # return dict( # source_initial=player.imgtag_Player, # ) # # #@staticmethod # #def is_displayed(player: Player): # #return player.session.config['avatarCreation'] == 0 and player.round_number ==