from otree.api import * import random class C(BaseConstants): NAME_IN_URL = 'one_player_gg' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 alpha = 3/4 beta = 15 R = 1000 c_payoff = 0.05 class Group(BaseGroup): pass class Subsession(BaseSubsession): def set_payoffs(self): players = self.get_players() for player in players: # Calculate bin_shares for payoff_first bin_shares = calculate_bin_shares(players, player) player.payoff_first = compute_payoff_first(player, bin_shares) # Calculate other_bfirst_values for payoff_second player.id_partner, other_bfirst_values = get_random_participant_bfirst_values(player) player.payoff_second = compute_payoff_second(player, other_bfirst_values) # Add payoffs player.payoff = player.payoff_first + player.payoff_second class Player(BasePlayer): a1 = models.FloatField( label='What number do you choose for A?', min=0, max=100 ) a2 = models.FloatField( label='What number do you choose for B?', min=0, max=100 ) bfirst_1 = models.IntegerField(min=0, max=100, label='0-4', blank=True) bfirst_2 = models.IntegerField(min=0, max=100, label='5-9', blank=True) bfirst_3 = models.IntegerField(min=0, max=100, label='10-14', blank=True) bfirst_4 = models.IntegerField(min=0, max=100, label='15-19', blank=True) bfirst_5 = models.IntegerField(min=0, max=100, label='20-24', blank=True) bfirst_6 = models.IntegerField(min=0, max=100, label='25-29', blank=True) bfirst_7 = models.IntegerField(min=0, max=100, label='30-34', blank=True) bfirst_8 = models.IntegerField(min=0, max=100, label='35-39', blank=True) bfirst_9 = models.IntegerField(min=0, max=100, label='40-44', blank=True) bfirst_10 = models.IntegerField(min=0, max=100, label='45-49', blank=True) bfirst_11 = models.IntegerField(min=0, max=100, label='50-54', blank=True) bfirst_12 = models.IntegerField(min=0, max=100, label='55-59', blank=True) bfirst_13 = models.IntegerField(min=0, max=100, label='60-64', blank=True) bfirst_14 = models.IntegerField(min=0, max=100, label='65-69', blank=True) bfirst_15 = models.IntegerField(min=0, max=100, label='70-74', blank=True) bfirst_16 = models.IntegerField(min=0, max=100, label='75-79', blank=True) bfirst_17 = models.IntegerField(min=0, max=100, label='80-84', blank=True) bfirst_18 = models.IntegerField(min=0, max=100, label='85-89', blank=True) bfirst_19 = models.IntegerField(min=0, max=100, label='90-94', blank=True) bfirst_20 = models.IntegerField(min=0, max=100, label='95-100', blank=True) bsecond_1 = models.IntegerField(min=0, max=100, label='0-4', blank=True) bsecond_2 = models.IntegerField(min=0, max=100, label='5-9', blank=True) bsecond_3 = models.IntegerField(min=0, max=100, label='10-14', blank=True) bsecond_4 = models.IntegerField(min=0, max=100, label='15-19', blank=True) bsecond_5 = models.IntegerField(min=0, max=100, label='20-24', blank=True) bsecond_6 = models.IntegerField(min=0, max=100, label='25-29', blank=True) bsecond_7 = models.IntegerField(min=0, max=100, label='30-34', blank=True) bsecond_8 = models.IntegerField(min=0, max=100, label='35-39', blank=True) bsecond_9 = models.IntegerField(min=0, max=100, label='40-44', blank=True) bsecond_10 = models.IntegerField(min=0, max=100, label='45-49', blank=True) bsecond_11 = models.IntegerField(min=0, max=100, label='50-54', blank=True) bsecond_12 = models.IntegerField(min=0, max=100, label='55-59', blank=True) bsecond_13 = models.IntegerField(min=0, max=100, label='60-64', blank=True) bsecond_14 = models.IntegerField(min=0, max=100, label='65-69', blank=True) bsecond_15 = models.IntegerField(min=0, max=100, label='70-74', blank=True) bsecond_16 = models.IntegerField(min=0, max=100, label='75-79', blank=True) bsecond_17 = models.IntegerField(min=0, max=100, label='80-84', blank=True) bsecond_18 = models.IntegerField(min=0, max=100, label='85-89', blank=True) bsecond_19 = models.IntegerField(min=0, max=100, label='90-94', blank=True) bsecond_20 = models.IntegerField(min=0, max=100, label='95-100', blank=True) # for i in range(1, 21): # lower_bound = (i - 1) * 5 # upper_bound = i * 5 - 1 # if i == 20: # upper_bound = 100 # label = f'{lower_bound}-{upper_bound}' # setattr(BasePlayer, f'bfirst_{i}', models.IntegerField(min=0, max=100, label=label, blank=True)) target1 = models.FloatField() target2 = models.FloatField() payoff1 = models.FloatField() payoff2 = models.FloatField() payoff_guessing = models.FloatField() payoff_first = models.FloatField(default=0) payoff_second = models.FloatField(default=0) id_partner = models.IntegerField() tot_payoff_1pg = models.FloatField() def compute_payoffs_and_targets(self): self.target1 = C.alpha * self.a2 + C.beta self.target2 = C.alpha * self.a1 + C.beta self.payoff1 = max((1 - C.c_payoff * abs(self.a1 - self.target1))*1000,0) self.payoff2 = max((1 - C.c_payoff * abs(self.a2 - self.target2))*1000,0) self.payoff_guessing = round(random.choice([self.payoff1, self.payoff2])) #self.total_payoff = self.payoff1 + self.payoff2 def compute_total_payoffs(self): self.tot_payoff_1pg = round(self.payoff_guessing + self.payoff_first + self.payoff_second) self.participant.vars['one_player_gg'] = self.tot_payoff_1pg def get_bin_index(a1_value): if a1_value == 100: return 20 return (a1_value // 5) + 1 def calculate_bin_shares(players, player): a1_values = [p.a1 for p in players] total_players = len(players) - 1 # Subtract 1 to exclude the player's own choice bin_shares = [0] * 20 for i, a1_value in enumerate(a1_values): bin_index = int(get_bin_index(a1_value)) - 1 bin_shares[bin_index] += 1 # Remove the player's own choice player_bin_index = int(get_bin_index(player.a1)) - 1 bin_shares[player_bin_index] -= 1 bin_shares = [x / total_players for x in bin_shares] return bin_shares def compute_payoff_first(player, bin_shares): bfirst_values = [player.field_maybe_none(f'bfirst_{i}') or 0 for i in range(1, 21)] payoff_first = 2 for i, p_i in enumerate(bin_shares): penalty = (p_i * sum((bfirst_values[j]/100 - (1 if j == i else 0)) ** 2 for j in range(20))) #print(f'Penalty for index {i}: {penalty}') # Display the penalty value payoff_first -= penalty payoff_first *= 500 # Multiply payoff by 500 to make max payoff equal to 1000 return round(payoff_first) # Round the result and return it def get_random_participant_bfirst_values(current_player): # all_players = [] # for subsession in current_player.session.get_subsessions(): # all_players.extend(subsession.get_players()) # other_players = [player for player in all_players if player.id != current_player.id] # above was working before mergin apps all_players = current_player.subsession.get_players() other_players = [player for player in all_players if player.participant.id != current_player.participant.id] selected_player = random.choice(other_players) bfirst_values = [selected_player.field_maybe_none(f'bfirst_{i}') or 0 for i in range(1, 21)] return selected_player.id_in_group, bfirst_values def compute_payoff_second(player, other_bfirst_values): bsecond_values = [player.field_maybe_none(f'bsecond_{i}') or 0 for i in range(1, 21)] payoff_second = 2 for i, other_p_i in enumerate(other_bfirst_values): penalty = (other_p_i/100 * sum((bsecond_values[j]/100 - (1 if j == i else 0)) ** 2 for j in range(20))) payoff_second -= penalty payoff_second *= 500 # Multiply payoff by 500 return round(payoff_second) # Round the result and return it class PartTwoBegins(Page): pass class CombinedPage(Page): form_model = 'player' form_fields = ['a1', 'a2'] + [f'bfirst_{i}' for i in range(1, 21)] + [f'bsecond_{i}' for i in range(1, 21)] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = context['form'] context['a_fields'] = [form['a1'], form['a2']] context['bfirst_fields'] = [form[f'bfirst_{i}'] for i in range(1, 21)] context['bsecond_fields'] = [form[f'bsecond_{i}'] for i in range(1, 21)] return context @staticmethod def error_message(player, values): print('values is', values) total1 = sum((values.get(f'bfirst_{i}') or 0) for i in range(1, 21)) total2 = sum((values.get(f'bsecond_{i}') or 0) for i in range(1, 21)) errors = {} if total1 != 100: for i in range(1, 21): errors[f'bfirst_{i}'] = 'The numbers must add up to 100' if total2 != 100: for i in range(1, 21): errors[f'bsecond_{i}'] = 'The numbers must add up to 100' return errors class GuessingGame(Page): form_model = 'player' form_fields = ['a1', 'a2'] class Beliefs(Page): form_model = 'player' form_fields = [f'bfirst_{i}' for i in range(1, 21)] template_name = "one_player_gg/Beliefs.html" @staticmethod def error_message(player, values): print('values is', values) total = ( (values.get('bfirst_1') or 0) + (values.get('bfirst_2') or 0) + (values.get('bfirst_3') or 0) + (values.get('bfirst_4') or 0) + (values.get('bfirst_5') or 0) + (values.get('bfirst_6') or 0) + (values.get('bfirst_7') or 0) + (values.get('bfirst_8') or 0) + (values.get('bfirst_9') or 0) + (values.get('bfirst_10') or 0) + (values.get('bfirst_11') or 0) + (values.get('bfirst_12') or 0) + (values.get('bfirst_13') or 0) + (values.get('bfirst_14') or 0) + (values.get('bfirst_15') or 0) + (values.get('bfirst_16') or 0) + (values.get('bfirst_17') or 0) + (values.get('bfirst_18') or 0) + (values.get('bfirst_19') or 0) + (values.get('bfirst_20') or 0) ) if total != 100: return 'The numbers must add up to 100' class BeliefsTwo(Page): form_model = 'player' form_fields = [f'bsecond_{i}' for i in range(1, 21)] template_name = "one_player_gg/BeliefsTwo.html" @staticmethod def error_message(player, values): print('values is', values) total = ( (values.get('bsecond_1') or 0) + (values.get('bsecond_2') or 0) + (values.get('bsecond_3') or 0) + (values.get('bsecond_4') or 0) + (values.get('bsecond_5') or 0) + (values.get('bsecond_6') or 0) + (values.get('bsecond_7') or 0) + (values.get('bsecond_8') or 0) + (values.get('bsecond_9') or 0) + (values.get('bsecond_10') or 0) + (values.get('bsecond_11') or 0) + (values.get('bsecond_12') or 0) + (values.get('bsecond_13') or 0) + (values.get('bsecond_14') or 0) + (values.get('bsecond_15') or 0) + (values.get('bsecond_16') or 0) + (values.get('bsecond_17') or 0) + (values.get('bsecond_18') or 0) + (values.get('bsecond_19') or 0) + (values.get('bsecond_20') or 0) ) if total != 100: return 'The numbers must add up to 100' class SessionResultsWaitPage(WaitPage): def is_displayed(self): return self.round_number == 1 # Display the wait page only in the first round def after_all_players_arrive(self): subsession = self.subsession subsession.set_payoffs() # Add this line for player in subsession.get_players(): player.compute_payoffs_and_targets() player.compute_total_payoffs() class ShowResults(Page): def vars_for_template(player: Player): return { 'payoff_guessing': player.payoff_guessing, 'payoff_first': player.payoff_first, 'payoff_second': player.payoff_second, 'tot_payoff_1pg': player.tot_payoff_1pg } class PartTwoEnded(Page): pass page_sequence = [ # PartTwoBegins, CombinedPage, #GuessingGame, #Beliefs, #BeliefsTwo, SessionResultsWaitPage, #ShowResults, PartTwoEnded ]