# Code: Public Goods Game - Three subjects version # Data: 1/9/2023 # Creator: Lien Lin # Change: # TO-DO: 1) fix contribution first # 2) basic payoff changed to 120 from otree.api import * doc = """ Public Project Game """ class C(BaseConstants): NAME_IN_URL = 'public_project_game' PLAYERS_PER_GROUP = 3 NUM_ROUNDS = 66 ROUNDS_IN_SESSION = 33 NUM_BLOCKS = 2 NUM_SESSIONS = 2 ROUNDS_IN_BLOCK = 33 # Timeout timeout_sec_showpayoff = 10 timeout_sec_decision = 20 # 20 seconds for a round timeout_sec_results = 30 timeout_sec_robot = 1 timeout_sec_instruction = 300 timeout_sec_quiz = 300 timeout_sec_coordinate = 300 # Payoff payoff_hete_low = '6-8-10-12' # use string is because otree can't save list payoff_hete_high = '2-4-6-8' payoff_homo_low = '9-9-9-9' payoff_homo_high ='5-5-5-5' payoff_fail = 5 payoff_noncoop = 10 NUM_PAYOFF = 3 # Number of scenarios for payoff basic_payoff = 120 # Quiz answer ans1 = '成功' ans2 = '失敗' ans3 = '8' ans4 = '10' ans5 = '4' ans6 = '5' class Subsession(BaseSubsession): pass class Group(BaseGroup): # Decision of the group voting_result = models.IntegerField() # the voting result is_pass = models.BooleanField() # Whether the project pass or not (voting_result >= threshold) # Scenario threshold = models.IntegerField() scenario = models.StringField() # save the payoff for the payoff and threshold in each group noncoop = models.StringField() # Blocks and Session block = models.StringField() class Player(BasePlayer): # Decision cooperate = models.BooleanField( choices=[[True, 'Cooperate'], [False, 'Not cooperate']], doc="""This player's decision""", widget=widgets.RadioSelect,) # Whether subject choose to cooperate or not is_no_decision = models.BooleanField() # whether the subject decide within the time # Reaction time reaction_time_showpayoff = models.FloatField(initial = 0) reaction_time_decision = models.FloatField(initial=0) reaction_time_results = models.FloatField(initial=0) # Scenario fail_payoff = models.IntegerField(initial=C.payoff_fail) #payoff of failing the project noncoop_payoff = models.IntegerField(initial=C.payoff_noncoop) #payoff of not cooperate and the project pass coop_payoff = models.IntegerField() #payoff of cooperate and the project pass coop_fail_payoff = models.IntegerField() # payoff that cooperate of the proect fail # Payoff actual_payoff = models.IntegerField() # actual payoff that the subject receive final_payoff = models.IntegerField() # save the final payoff for a player real_payoff = models.IntegerField() # what subject actually get # Identity name = models.StringField() # The name used in the game identity = models.IntegerField() # The identity for each round is_robot = models.BooleanField(choices=[[True, 'Robot'], [False, 'Subject']], widget=widgets.RadioSelect,) # Whether the subject is robot or not robot_type = models.StringField() # The type of robot # Quiz quiz_1 = models.StringField(label="若 3 人決定合作,則專案結果為(回答成功或失敗): ") quiz_2 = models.StringField(label="若只有 A 決定合作,則專案結果為(回答成功或失敗): ") quiz_3 = models.StringField(label="若 A 決定合作,且最後專案成功,則 A 的報酬為: ") quiz_4 = models.StringField(label="若 B 決定不合作,且最後專案成功,則 B 的報酬為: ") quiz_5 = models.StringField(label="若 C 決定合作,且最後專案失敗,則 C 的報酬為: ") quiz_6 = models.StringField(label="若 C 決定不合作,且最後專案失敗,則 C 的報酬為: ") # Coordination coor_1 = models.StringField(label="[dog, monkey, fox, Schizophrenia] ") coor_2 = models.StringField(label="[芬蘭, 愛爾蘭, 蘇格蘭, 英格蘭] ") coor_3 = models.StringField(label="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ") coor_4 = models.StringField(label="[3, 44, 55, 66, 77, 88, 99, 1010] ") coor_5 = models.StringField(label="[網球, 排球, 足球, 籃球] ") # Picking pick_1 = models.StringField(label="[seating, walking, jogging, running] ") pick_2 = models.StringField(label="[coke, cream, cheese, cake] ") pick_3 = models.StringField(label="[白飯, 雞排, 豬腳, 牛排] ") pick_4 = models.StringField(label="[chicken, duck, swan, bat] ") pick_5 = models.StringField(label="[123, 231, 312, 213] ") # Finish questionnaire actual_name = models.StringField(label="請問您的名字是:") idnumber = models.StringField(label="請問您的身分證字號是(字母請大寫):") sex = models.IntegerField(label= "請問您的性別是:" , choices=[ [1, '男'], [2, '女'], [3, '其他'],]) age = models.IntegerField(label="請問您的年齡:") studentid = models.StringField(label="請問您的學號是:") address = models.LongStringField(label="請問您的戶籍地址是(請輸入身分證後完整的戶籍地址):") how_to_decide = models.LongStringField(label="在這些問題中,請大致描述您是如何做決策的:") suggestion = models.LongStringField(label="若您有任何問題或是建議,請在這邊讓我們知道:") risk_pref_1 = models.IntegerField(label= "一個擲硬幣的遊戲,若您擲出正面/反面,則可贏得 X/Y 金額,例如:$50/$100 表示您有一半的機率得到 50 元,一半的機率得到 100元,以下 X/Y 的組合,您想參與哪一個?" , choices=[ [1, '$100/$100'], [2, '$80/$140'], [3, '$60/$180'], [4, '$40/$220'], [5, '$20/$260'], [6, '$0/$300'], ]) risk_pref_2 = models.IntegerField( label="一個抽獎,您有 X 的機率得到 Y 元,例如:10%, $1000 表示您有10%的機率得到 1000 元,以下 X/Y 的組合,您想參與哪一個?", choices=[ [1, '80%, $50'], [2, '70%, $100'], [3, '50%, $200'], [4, '30%, $400'], [5, '20%, $750'], ]) ultimatum = models.IntegerField(label="在此遊戲中,您可以將 100 元分配給自己和另外一位參與者,請問您想分配多少給自己?(請填 0 到 100 的數字)") class Scenarios_list(ExtraModel): coop_success1 = models.StringField() threshold1 = models.FloatField() coop_fail1 = models.StringField() block1 = models.StringField() coop_success2 = models.StringField() threshold2 = models.FloatField() coop_fail2 = models.StringField() block2 = models.StringField() coop_success3 = models.StringField() threshold3 = models.FloatField() coop_fail3 = models.StringField() block3 = models.StringField() coop_success4 = models.StringField() threshold4 = models.FloatField() coop_fail4 = models.StringField() block4 = models.StringField() coop_success5 = models.StringField() threshold5 = models.FloatField() coop_fail5 = models.StringField() block5 = models.StringField() coop_success6 = models.StringField() threshold6 = models.FloatField() coop_fail6 = models.StringField() block6 = models.StringField() coop_success7 = models.StringField() threshold7 = models.FloatField() coop_fail7 = models.StringField() block7 = models.StringField() coop_success8 = models.StringField() threshold8 = models.FloatField() coop_fail8 = models.StringField() block8 = models.StringField() coop_success9 = models.StringField() threshold9 = models.FloatField() coop_fail9 = models.StringField() block9 = models.StringField() coop_success10 = models.StringField() threshold10 = models.FloatField() coop_fail10 = models.StringField() block10 = models.StringField() # Function def place_robot(player: Player): # Whether this participant can be assigned as robot player_list = player.subsession.get_players() # get all the player in this session subject_grouping_list = [] for p in player_list: subject_grouping_list.append([int(p.participant.id_in_session), int(p.in_round(1).group.id_in_subsession),int(p.in_round(C.ROUNDS_IN_SESSION + 1).group.id_in_subsession)]) sorted(subject_grouping_list, key=lambda x: (x[1], x[2])) return subject_grouping_list def is_robot(player: Player, page): # whether the player is robot and decide the timeout if player.is_robot == False: if page == "Instructions": timeout_sec = C.timeout_sec_instruction elif page == "Show_payoff": timeout_sec = C.timeout_sec_showpayoff elif page == "Decision": timeout_sec = C.timeout_sec_decision elif page == "Results": timeout_sec = C.timeout_sec_results elif page == "Quiz": timeout_sec = C.timeout_sec_quiz elif player.is_robot == True: timeout_sec = C.timeout_sec_robot return timeout_sec def robot_choice(player: Player): # The choice robot will choose given its type import random player.is_robot = player.in_round(1).is_robot player.robot_type = player.in_round(1).robot_type if player.robot_type == "cooperative": cooperative_choice = random.choices([True, False], weights = [90,10])[0] elif player.robot_type == "selfish": cooperative_choice = random.choices([True, False], weights = [10,90])[0] return cooperative_choice def decode_payoff_list(scenario): # decode the payoff list I saved payoff_list = scenario.split('_') payoff_list = [int(s.strip()) for s in payoff_list] return payoff_list def assign_name(group: Group): # assign each player his/her name in the session, change in different session import random import string names = [i for i in string.ascii_uppercase[:C.PLAYERS_PER_GROUP]] random.shuffle(names) # shuffle the sequence of name for p, i in zip(group.get_players(), range(C.PLAYERS_PER_GROUP)): p.name = names[i] p.participant.name = names[i] def assign_identity_payoff(group: Group): # function that is used to assgin the identiy to each player in each round under different scenairos import random players = group.get_players() # return a list of all the players in the group identities = list(range(1,C.PLAYERS_PER_GROUP+1)) # generate the list of identity random.shuffle(identities) scenario_decoded = decode_payoff_list(group.scenario) noncoop_decoded = decode_payoff_list(group.noncoop) for p, i in zip(players, range(C.PLAYERS_PER_GROUP)): p.identity = identities[i] # identity will change in every round p.coop_payoff = scenario_decoded[int(p.identity - 1)] # assign the corresponding identity's payoff for each player p.noncoop_payoff = C.payoff_noncoop p.coop_fail_payoff = noncoop_decoded[int(p.identity - 1)] p.fail_payoff = C.payoff_fail def creating_session(subsession): # creating session for unknown and known scenarios = read_csv('public_goods_Yuping/scenarios_blocks_0105_fix_contribution.csv', Scenarios_list) if subsession.round_number % C.ROUNDS_IN_SESSION == 1 : # If the round number can be divided by Rounds in a block now is round 1 and 37 subsession.group_randomly() # group randomly groups = subsession.get_groups() # assign name in the game for each group for g in groups: assign_name(g) g.scenario = scenarios[subsession.round_number - 1]['coop_success'+str(g.id_in_subsession)] g.threshold = int(scenarios[subsession.round_number - 1]['threshold'+str(g.id_in_subsession)]) g.noncoop = scenarios[subsession.round_number - 1]['coop_fail'+str(g.id_in_subsession)] g.block = scenarios[subsession.round_number - 1]['block' + str(g.id_in_subsession)] assign_identity_payoff(g) # assign identity for each player # randomly assign the scenario for each group else: subsession.group_like_round(((subsession.round_number-1)//C.ROUNDS_IN_SESSION)*C.ROUNDS_IN_SESSION+1) groups = subsession.get_groups() for p in subsession.get_players(): p.name = p.participant.name # assign the scenario for each group for g in groups: g.scenario = scenarios[subsession.round_number - 1]['coop_success' + str(g.id_in_subsession)] g.threshold = int(scenarios[subsession.round_number - 1]['threshold' + str(g.id_in_subsession)]) g.noncoop = scenarios[subsession.round_number - 1]['coop_fail' + str(g.id_in_subsession)] g.block = scenarios[subsession.round_number - 1]['block' + str(g.id_in_subsession)] assign_identity_payoff(g) # assign identity for each player def set_payoffs(group:Group): # set the payoff of the decision made players = group.get_players() voting_result = [p.cooperate for p in players] # there's an error group.voting_result = sum(voting_result) for p in players: if group.voting_result >= group.threshold: group.is_pass = True if p.cooperate == True: p.actual_payoff = p.coop_payoff else: p.actual_payoff = p.noncoop_payoff else: group.is_pass = False if p.cooperate == True: p.actual_payoff = p.coop_fail_payoff else: p.actual_payoff = p.fail_payoff # PAGES class Is_robot(Page): form_model = 'player' form_fields = ['is_robot'] @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 # only round 1 need experiment instruction @staticmethod def vars_for_template(player: Player): # Use this to pass variables to the template. player_grouping_list = place_robot(player) return dict(player_group = player_grouping_list,) class Welcome(Page): @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 # only round 1 need experiment instruction class Introduction(Page): @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods import random # deciding the robot feature if player.round_number == 1: if player.is_robot == True: player.robot_type = random.choice(["cooperative", "selfish"]) else: player.robot_type = "Not robot" for p in player.in_rounds(1, C.NUM_ROUNDS): # assign the same participant's player the same whether he/she is robot p.is_robot = player.is_robot p.robot_type = player.robot_type return player.round_number == 1 # only round 1 need experiment instruction class Introduction2(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == C.ROUNDS_IN_SESSION + 1 # Start of new session class Instructions_overall(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 class Instructions_showpayoff(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 # only round 1 need experiment instruction class Instructions_decision(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 # only round 1 need experiment instruction class Instructions_results(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 # only round 1 need experiment instruction class Instructions_payoff(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 # only round 1 need experiment instruction class quiz(Page): form_model = 'player' form_fields = ['quiz_1', 'quiz_2', 'quiz_3', 'quiz_4', 'quiz_5', 'quiz_6'] # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Quiz") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == 1 # only round 1 need experiment instruction @staticmethod def error_message(player, values): player_response= [values['quiz_1'], values['quiz_2'], values['quiz_3'], values['quiz_4'], values['quiz_5'], values['quiz_6']] ans_list = [C.ans1, C.ans2, C.ans3, C.ans4, C.ans5, C.ans6] if player_response != ans_list: return '答案有錯誤,請再重新確認' class InstructionsWaitPage(WaitPage): # Wait for everyone in the experiment to finish instructions wait_for_all_groups = True # make sure it will wait for all the participant class Instructions_fix_payoff(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.group.block == 'fix_payoff' and player.round_number % C.ROUNDS_IN_BLOCK == 1 class Instructions_fix_contribution(Page): # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Instructions") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.group.block == 'fix_contribution' and player.round_number % C.ROUNDS_IN_BLOCK == 1 class Show_payoff_known(Page): form_model = 'player' form_fields = ['reaction_time_showpayoff'] # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Show_payoff") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.group.block == "fix_contribution" or player.group.block == "fix_payoff" @staticmethod def vars_for_template(player: Player): # Use this to pass variables to the template. scenario_list = decode_payoff_list(player.group.scenario) nooncop_list = decode_payoff_list(player.group.noncoop) return dict( scenario_payoff1=scenario_list[0], scenario_payoff2=scenario_list[1], scenario_payoff3=scenario_list[2], scenario_fail_payoff1 = nooncop_list[0], scenario_fail_payoff2 = nooncop_list[1], scenario_fail_payoff3 = nooncop_list[2], ) class Decision_known(Page): #knowing other people's identity and their payoff form_model = 'player' form_fields = ['cooperate', 'reaction_time_decision'] threshold = Group.threshold # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Decision") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.group.block == "fix_contribution" or player.group.block == "fix_payoff" @staticmethod def before_next_page(player, timeout_happened): # built-in methods import random if timeout_happened: if player.is_robot == True: player.cooperate = robot_choice(player) else: player.is_no_decision = True # if the time is up, set player as making no decision player.cooperate = random.choice([True, False]) # set the decision of not choice as random @staticmethod def vars_for_template(player: Player): # Use this to pass variables to the template. player_name_list = ['?', '?', '?'] for p in player.group.get_players(): # show the player's identity on page player_name_list[p.identity-1] = p.name scenario_list = decode_payoff_list(player.group.scenario) nooncop_list = decode_payoff_list(player.group.noncoop) return dict( player_name = player_name_list, scenario_payoff1 = scenario_list[0], scenario_payoff2 = scenario_list[1], scenario_payoff3 = scenario_list[2], scenario_fail_payoff1=nooncop_list[0], scenario_fail_payoff2=nooncop_list[1], scenario_fail_payoff3=nooncop_list[2], name1 = player_name_list[0], name2 = player_name_list[1], name3 = player_name_list[2], ) class ResultsWaitPage(WaitPage): wait_for_all_groups = False # make sure it won't wait for all the participant after_all_players_arrive = set_payoffs # built-in methods. After every subjects decided, activate set_payoffs class Results_known(Page): form_model = 'player' form_fields = ['reaction_time_results'] # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = is_robot(player, "Results") return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.group.block == "fix_contribution" or player.group.block == "fix_payoff" @staticmethod def vars_for_template(player: Player): # Use this to pass variables to the template. player_name_list = ['?', '?', '?'] player_actual_payoff = [0, 0, 0] player_decisions = ['?', '?', '?'] for p in player.group.get_players(): # show the player's identity on page player_name_list[p.identity-1] = p.name player_actual_payoff[p.identity-1] = p.actual_payoff if p.cooperate == True: player_decisions[p.identity-1] = '合作' else: player_decisions[p.identity-1] = '不合作' scenario_list = decode_payoff_list(player.group.scenario) nooncop_list = decode_payoff_list(player.group.noncoop) if player.group.block == "same_payoff": # decide the fail payoff of cooperate scenario_fail_list = [3, 3, 3] elif player.group.block == "same_difference": scenario_fail_list = scenario_list - [10, 10, 10] + [5, 5, 5] return dict( scenario_payoff1=scenario_list[0], scenario_payoff2=scenario_list[1], scenario_payoff3=scenario_list[2], name1=player_name_list[0], name2=player_name_list[1], name3=player_name_list[2], decision1 = player_decisions[0], decision2 = player_decisions[1], decision3 = player_decisions[2], scenario_fail_payoff1=nooncop_list[0], scenario_fail_payoff2=nooncop_list[1], scenario_fail_payoff3=nooncop_list[2], actual_payoff1 = player_actual_payoff[0], actual_payoff2 = player_actual_payoff[1], actual_payoff3 = player_actual_payoff[2], ) class Coordination(Page): form_model = 'player' form_fields = ["coor_1", "coor_2", "coor_3", "coor_4", "coor_5"] # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = C.timeout_sec_coordinate return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == C.NUM_ROUNDS class Picking(Page): form_model = 'player' form_fields = ["pick_1", "pick_2", "pick_3", "pick_4", "pick_5"] # Deciding timeout - robot or subject @staticmethod def get_timeout_seconds(player): timeout_sec = C.timeout_sec_coordinate return timeout_sec @staticmethod def is_displayed(player): # built-in methods return player.round_number == C.NUM_ROUNDS class Finish(Page): form_model = 'player' form_fields = ['actual_name', 'sex', 'age', 'idnumber','address' , "studentid", "how_to_decide", "suggestion", "risk_pref_1", "risk_pref_2", "ultimatum"] @staticmethod def is_displayed(player): # display if the condition satisfy return player.round_number == C.NUM_ROUNDS @staticmethod def vars_for_template(player: Player): # built-in methods,將 total_payoff 的值傳到 html 頁面 player.final_payoff = sum([p.actual_payoff for p in player.in_all_rounds()]) player.real_payoff = C.basic_payoff + (1+ (player.final_payoff-1)//40)*10 return { "total_payoff": player.final_payoff } class Finish_2(Page): @staticmethod def is_displayed(player): # display if the condition satisfy return player.round_number == C.NUM_ROUNDS @staticmethod def vars_for_template(player: Player): # built-in methods return { "real_payoff": player.real_payoff } page_sequence = [Is_robot, Welcome, Introduction, Introduction2, Instructions_overall, Instructions_showpayoff, Instructions_decision, Instructions_results, Instructions_payoff, quiz, InstructionsWaitPage, Instructions_fix_payoff, Instructions_fix_contribution, Show_payoff_known, Decision_known, ResultsWaitPage, Results_known, Picking, Coordination, Finish, Finish_2]