from otree.api import ( models, widgets, BaseConstants, BaseSubsession, BaseGroup, BasePlayer, Currency as c, currency_range, ) author = 'Christian König gen. Kersting' doc = """ Public Good Games in standard and persistent form with variable number of players and 30 + x rounds. """ class Constants(BaseConstants): name_in_url = 'pgg_instructions' players_per_group = None num_rounds = 1 SURE_ROUNDS = 30 # make sure to update this in the pgg models.py as well! MAX_ROUNDS = 40 # make sure to update this in the pgg models.py as well! STANDARD_DEPRECIATION = 1 PERSISTENT_DEPRECIATION = 1/2 CONTINUATION_PROBABILITY = 1/3 EUR_PER_ORANGE_TOKEN = round(1/40, 3) ORANGE_TOKENS_PER_BLUE_TOKEN = 1 # from converting private account blue tokens to orange ones STANDARD_ENDOWMENT = 10 PERSISTENT_ENDOWMENT = 10 MPCR_TOTAL = 0.6 # total marginal per capita return (over all rounds) class Subsession(BaseSubsession): persistent = models.BooleanField(initial=False, doc="Indicator for persistent variant of pgg") group_size = models.IntegerField(initial=3, doc="Number of members per group") player_endowment = models.IntegerField(doc="Actual blue token endowment") def creating_session(self): self.group_size = int(self.session.config.get('group_size', 3)) self.persistent = True if self.session.config.get('variant', 'standard') == 'persistent' else False self.player_endowment = Constants.PERSISTENT_ENDOWMENT if self.persistent else Constants.STANDARD_ENDOWMENT # set session vars: self.session.vars["STANDARD_DEPRECIATION"] = Constants.STANDARD_DEPRECIATION self.session.vars["PERSISTENT_DEPRECIATION"] = Constants.PERSISTENT_DEPRECIATION self.session.vars["CONTINUATION_PROBABILITY"] = Constants.CONTINUATION_PROBABILITY self.session.vars["EUR_PER_ORANGE_TOKEN"] = Constants.EUR_PER_ORANGE_TOKEN self.session.vars["ORANGE_TOKENS_PER_BLUE_TOKEN"] = Constants.ORANGE_TOKENS_PER_BLUE_TOKEN self.session.vars["STANDARD_ENDOWMENT"] = Constants.STANDARD_ENDOWMENT self.session.vars["PERSISTENT_ENDOWMENT"] = Constants.PERSISTENT_ENDOWMENT self.session.vars["MPCR_TOTAL"] = Constants.MPCR_TOTAL class Group(BaseGroup): pass class Player(BasePlayer): c_rounds = models.IntegerField( verbose_name="Runden:", choices=[ (1, "Die Anzahl der Runden ist festgelegt. Das Spiel wird sofort nach Runde 30 enden."), (2, "Die Anzahl der Runden ist variabel, es werden aber maximal 30 Runden gespielt."), (3, "Die Anzahl der Runden ist variabel, es werden aber mindestens 30 Runden gespielt.") ], widget=widgets.RadioSelect(), doc="Comprehension question on rounds, correct answer is 3, minimum of 30." ) def c_rounds_error_message(self, value): if value != 3: return "Ihre Antwort ist falsch. Bitte lesen Sie die Instruktionen erneut und korrigieren Sie dann Ihre Antwort." c_tokens = models.IntegerField( verbose_name="Token:", choices=[ (1, "Orange Token können in den gemeinsamen Topf gelegt werden. Blaue Token können nicht verwendet werden, sondern bestimmen die Auszahlung."), (2, "Blaue Token können in den gemeinsamen Topf gelegt werden. Orange Token können nicht verwendet werden, sondern bestimmen die Auszahlung.") ], widget=widgets.RadioSelect(), doc="Comprehension question on tokens, correct answer is 2, orange tokens cannot be used." ) def c_tokens_error_message(self, value): if value != 2: return "Ihre Antwort ist falsch. Bitte lesen Sie die Instruktionen erneut und korrigieren Sie dann Ihre Antwort." c_remaining_tokens = models.IntegerField( verbose_name="Verbleibende Token:", choices=[ (1, "Jeder blaue Token, der in einer Runde nicht in den gemeinsamen Topf gelegt wird, wird am Ende der Runde in einen orangen Token umgewandelt."), (2, "Jeder blaue Token, der in einer Runde nicht in den gemeinsamen Topf gelegt wird, geht verloren.") ], widget=widgets.RadioSelect(), doc="Comprehension question on remaining tokens, correct answer is 1, blue tokens convert one-to-one" ) def c_remaining_tokens_error_message(self, value): if value != 1: return "Ihre Antwort ist falsch. Bitte lesen Sie die Instruktionen erneut und korrigieren Sie dann Ihre Antwort." c_shared_pot = models.IntegerField( verbose_name="Gemeinsamer Topf:", choices=[ (1, "Token im gemeinsamen Topf werden nicht in die folgenden Runden übertragen. Zu Beginn jeder neuen Runde ist der gemeinsame Topf immer leer."), (2, "Token im gemeinsamen Topf können in die folgenden Runden übertragen werden. Zu Beginn jeder neuen Runde, kann der gemeinsame Topf übrige Token aus der Vorrunde enthalten.") ], widget=widgets.RadioSelect(), doc="Comprehension question on shared pot, correct answer is 1 if standard, 2 if persistent" ) def c_shared_pot_error_message(self, value): correct_value = 2 if self.subsession.persistent else 1 if value != correct_value: return "Ihre Antwort ist falsch. Bitte lesen Sie die Instruktionen erneut und korrigieren Sie dann Ihre Antwort." c_shared_pot_end_of_game = models.IntegerField( verbose_name="Gemeinsamer Topf am Ende des Spiels:", choices=[ (1, "Sind am Ende der letzten Runde noch blaue Token im gemeinsamen Topf, wird er so lange orange Token generieren, bis er leer ist. Es ist aber trotzdem möglich, weitere blaue Token in den gemeinsamen Topf zu legen."), (2, "Sind am Ende der letzten Runde noch blaue Token im gemeinsamen Topf, wird er so lange orange Token generieren, bis er leer ist. Es ist aber nicht möglich, weitere blaue Token in den gemeinsamen Topf zu legen."), (3, "Sind am Ende der letzten Runde noch blaue Token im gemeinsamen Topf, erhalten Sie keine zusätzlichen orangenen Token und alle verbleibenden blaue Token gehen verloren.") ], widget=widgets.RadioSelect(), doc="Comprehension question on shared pot behavior at end of game, correct answer is 2 if persistent; 3 if standard" ) def c_shared_pot_end_of_game_error_message(self, value): correct_value = 2 if self.subsession.persistent else 3 if value != correct_value: return "Ihre Antwort ist falsch. Bitte lesen Sie die Instruktionen erneut und korrigieren Sie dann Ihre Antwort."