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 ==