import random from otree.api import ( Page, WaitPage, models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) doc = """ This is a six-period public goods game with 2 players. """ class Constants(BaseConstants): name_in_url = 'clamp_public_goods' players_per_group = 2 num_rounds = 10 instructions_template = 'clamp_public_goods/instructions.html' # """Amount allocated to each player""" There are 3 endowment treatments. This endowment treatment is the unequal and known endowment. # This is a public good variable needed for payoff. total_endowment = 20 multiplier = 1.5 # This will be randomized for use within sessions. tax = 3 tax_profit = 6 class Subsession(BaseSubsession): pass class Group(BaseGroup): total_contribution = models.CurrencyField() individual_share = models.CurrencyField() # The fields that each player will have to enter to contribute to. class Player(BasePlayer): endowment = models.IntegerField() other_endowment = models.IntegerField() answer = models.BooleanField() correct_answers = models.IntegerField() total_correct_answers = models.IntegerField() total_allotted = models.IntegerField() #final_payoff = models.IntegerField() currency_payoff = models.CurrencyField() total_payoff = models.CurrencyField() #selected_round = models.IntegerField() contribution = models.IntegerField( min=0, #max= endowment, doc="""The amount contributed by the player""", #Unsure if wording of 'projects' is what I want to use in this question. label="How much will you contribute to the public account?", ) # Including private currency field so subjects have to actively choose how much to contribute to each. private_keep = models.IntegerField( min = 0, doc="""The amount the player will keep in their private account.""", label="How much will you keep in your personal account?", ) # Variable used to make sure people do not over (or under) contribute since they will be actively putting money into one fund or the other. #probably could have used just the endowment will adjust later maybe. # Questions after each round. how_much = models.IntegerField( min = 0, max = 14, doc="""How much the player thinks his/her partner contributed""", label= "How many tokens do you think the other person contributed to the public account?", ) should_have = models.IntegerField( min = 0, max =14, #max = Constants.total_endowment - player.endowment, doc="""How much the player believes that the other person should have contributed.""", label= "How many tokens do you think the other person should have contributed to the public account?", ) # FUNCTIONS def vars_for_admin_report(subsession: Subsession): contributions = [p.contribution for p in subsession.get_players() if p.contribution != None] if contributions: return dict( avg_contribution=sum(contributions) / len(contributions), min_contribution=min(contributions), max_contribution=max(contributions), ) else: return dict( avg_contribution='(no data)', min_contribution='(no data)', max_contribution='(no data)', ) def creating_session(subsession): print("in creating session") # Group pairs randomly subsession.group_randomly() # Assign endowment for g in subsession.get_groups(): #set all of player ones for p in g.get_players(): if p.round_number <= 5: e1 = random.choice([6, 7, 8, 9, 10, 11, 12, 13, 14]) if p.id_in_group == 1: p.endowment = e1 else: e1 = random.choice([3, 4, 5, 6, 7, 8, 9, 10, 11]) if p.id_in_group == 1: p.endowment = e1 for g in subsession.get_groups(): for p in g.get_players(): if p.round_number <= 5: if p.id_in_group == 2: p.endowment = Constants.total_endowment - g.get_player_by_id(1).endowment else: if p.id_in_group == 2: p.endowment = Constants.total_endowment - 6 - g.get_player_by_id(1).endowment #define other endowment for g in subsession.get_groups(): for p in g.get_players(): if p.round_number <= 5: p.other_endowment = 20 - p.endowment else: p.other_endowment = 14 - p.endowment # create list of payoffs #is this a tax round or a non tax round #Are the questions correct? #for g in subsession.get_groups(): #for p in g.get_players(): #p.total_allotted = p.endowment #no tax set_payoffs def set_payoffs(group: Group): for p in group.get_players(): if p.round_number <=5: group.total_contribution = sum([p.contribution for p in group.get_players()]) # Function that defines the portion of payoff for individuals that comes from . group.individual_share = ( group.total_contribution * Constants.multiplier / Constants.players_per_group ) else: group.total_contribution = sum([p.contribution for p in group.get_players()]) + Constants.tax_profit # Function that defines the portion of payoff for individuals that comes from . group.individual_share = ( group.total_contribution * Constants.multiplier / Constants.players_per_group ) # Function that defines the individual payoff (private amount + public fund individual share) for p in group.get_players(): p.payoff = p.private_keep + group.individual_share #for p in group.get_players(): #if p.round_number == 1: #p.correct_answers = 0 #else: for p in group.get_players(): if p.round_number <= 5: if p.how_much == group.total_contribution - p.contribution: p.answer = True p.correct_answers = 1 else: p.answer = False p.correct_answers = 0 else: if p.how_much == group.total_contribution - 6 - p.contribution: p.answer = True p.correct_answers = 1 else: p.answer = False p.correct_answers = 0 for p in group.get_players(): if p.round_number == 10: p.total_correct_answers = sum(p.correct_answers for p in p.in_all_rounds()) p.currency_payoff = p.payoff * .75 p.total_payoff = p.currency_payoff + p.total_correct_answers + 5 #PAGES class Introduction(Page): """Description of the game: How to play and what to expect""" def is_displayed(player): return player.round_number == 1 pass class IntroQuestions(Page): """Wait page for the first round. This is for questions and for lab person to emphasize points.""" def is_displayed(player): return player.round_number == 1 class Contribute(Page): def is_displayed(player): return player.round_number <= 5 """Player: Choose how much to contribute to the public fund and how much to keep in your personal account""" form_model = 'player' form_fields = ['contribution', 'private_keep'] @staticmethod def contribution_error_message(player, value): print('Contribution is', value) if value > player.endowment: return 'You cannot contribute more than your endowment to the public account.' def private_keep_error_message(player, value): print('Private account is', value) if value > player.endowment: return 'You cannot contribute more than your endowment to your personal account.' print('Private and public account combined is') def error_message(player, values): print('Private and public account combined is', values) if values['private_keep'] + values['contribution'] > player.endowment: return 'You cannot contribute more than your endowment to both accounts. Please decrease your contributions to at least one account.' elif values['private_keep'] + values['contribution'] < player.endowment: return 'You have not used all of your endowment yet! You can add more to either account!' @staticmethod def get_timeout_seconds(player: Player): return 120 def before_next_page(player, timeout_happened): if timeout_happened: player.contribution = player.endowment player.private_keep = 0 #Contribute page with tax class ContributeTax(Page): def is_displayed(player): return player.round_number > 5 """Player: Choose how much to contribute to the public fund and how much to keep in your personal account""" form_model = 'player' form_fields = ['contribution', 'private_keep'] @staticmethod def contribution_error_message(player, value): print('Contribution is', value) if value > player.endowment: return 'You cannot contribute more than your endowment to the public account.' def private_keep_error_message(player, value): print('Private account is', value) if value > player.endowment: return 'You cannot contribute more than your endowment to your personal account.' print('Private and public account combined is') def error_message(player, values): print('Private and public account combined is', values) if values['private_keep'] + values['contribution'] > player.endowment: return 'You cannot contribute more than your endowment to both accounts. Please decrease your contributions to at least one account.' elif values['private_keep'] + values['contribution'] < player.endowment: return 'You have not used all of your endowment yet! You can add more to either account!' @staticmethod def get_timeout_seconds(player: Player): return 120 def before_next_page(player, timeout_happened): if timeout_happened: player.contribution = player.endowment player.private_keep = 0 class Questions(Page): """Page to elicit beliefs about the other players.""" form_model = 'player' body_text = "You have made the following decision in this round. Please answer the following questions, while you wait for other players to finish." form_fields = ['how_much', 'should_have'] #@staticmethod #def how_much_error_message(player, value): #print('How much is', value) #if value > 20: #return 'You cannot guess an amount that the other player did not have.' #def should_have_error_message(player, value): #print('How much should have', value) #if value > 20: #return 'You cannot expect an amount that the other player did not have.' @staticmethod def get_timeout_seconds(player: Player): return 120 class ResultsWaitPage(WaitPage): after_all_players_arrive = 'set_payoffs' body_text = "Waiting for other participants to contribute." class Results(Page): """Players payoff: How much each has earned""" def is_displayed(player): return player.round_number <= 5 @staticmethod def vars_for_template(player: Player): group = player.group return dict(total_earnings=group.total_contribution * Constants.multiplier) #results for tax rounds class ResultsTax(Page): """Players payoff: How much each has earned""" def is_displayed(player): return player.round_number > 5 @staticmethod def vars_for_template(player: Player): group = player.group return dict(total_earnings=group.total_contribution * Constants.multiplier) class FinalResultsWaitPage(WaitPage): def is_displayed(player): return player.round_number == Constants.num_rounds after_all_players_arrive = 'final_payoff' class End(Page): """Last page. Tells player final payoff and how to collect their earnings.""" def is_displayed(player): return player.round_number == Constants.num_rounds page_sequence = [Introduction, IntroQuestions, Contribute, ContributeTax, Questions, ResultsWaitPage, Results, ResultsTax, End]