from otree.api import * import random doc = """ School admission game """ class Constants(BaseConstants): name_in_url = 'school_game' players_per_group = None num_rounds = 10 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): player_id = models.IntegerField(label="Your id:") treatment = models.IntegerField() answer = models.StringField(label="Your reported preferences:") player_tentative = models.StringField() comp1_tentative = models.StringField() comp2_tentative = models.StringField() comp3_tentative = models.StringField() comp1_priors = models.StringField() comp2_priors = models.StringField() comp3_priors = models.StringField() lottery = models.StringField() final_allocation = models.StringField() # FUNCTIONS def find_allocation(user_priors_g, user_tentative_g, comp1_priors_g, comp1_tentative_g, comp2_priors_g, comp2_tentative_g, comp3_priors_g, comp3_tentative_g, lottery_g): if (set(list(user_priors_g.upper())) != set(["A", "B", "C", "D"])) or (len(user_priors_g) != 4): return "Incorrect format of preferences", 0 user_priors, user_init = list(user_priors_g.upper()), user_tentative_g comp1_priors, comp1_init = list(comp1_priors_g), comp1_tentative_g comp2_priors, comp2_init = list(comp2_priors_g), comp2_tentative_g comp3_priors, comp3_init = list(comp3_priors_g), comp3_tentative_g school_list = ["A", "B", "C", "D"] lottery = list(map(int, list(lottery_g))) priorities = {0: user_priors, 1: comp1_priors, 2: comp2_priors, 3: comp3_priors} tentative_assignment = {0: user_init, 1: comp1_init, 2: comp2_init, 3: comp3_init} final_allocation = {0: 0, 1: 0, 2: 0, 3: 0} cycle_length = 0 while len(school_list) > 0: cycle_length += 1 # берем первого в очереди cur_queue_top = lottery[0] if len(school_list) == 1: final_allocation[cur_queue_top] = school_list[0] break # берем его последний приоритет cur_prior = priorities[cur_queue_top][-1] # берем школу, в которую он заассайнен изначально cur_init = tentative_assignment[cur_queue_top] # если его последний приоритет совпадает со школой, # в которую он уже заассайнен if (cur_init == cur_prior) and (cur_init in school_list): # убираем эту школу из списка школ school_list.remove(cur_init) # ассайним его в эту школу окончательно final_allocation[cur_queue_top] = cur_init # убираем школу из предпочтений всех остальных for i in range(4): priorities[i].remove(cur_init) # выкидываем его из очереди lottery = [s for s in lottery if s != cur_queue_top] # уменьшаем длину цикла на 1 cycle_length += -1 continue # если он хочет в школу, в которую кто-то уже заассайнен else: # id студента, кто уже заассайнен в эту школу assigned = list(tentative_assignment.keys())[list(tentative_assignment.values()).index(cur_prior)] # кладем его в верх очереди lottery.insert(0, assigned) # увеличивает длину цикла cycle_length += 1 # проверяем, что цикл не замкнулся # студенты в текущей цикле cur_cycle_students = lottery[:cycle_length] # их предпочтения cur_cycle_prefs = [priorities[i][-1] for i in cur_cycle_students] cur_cycle_tentatives = [tentative_assignment[i] for i in cur_cycle_students] # если цикл замкунулся for c_i in range(2, cycle_length + 1): if set(cur_cycle_prefs[:c_i]) == set(cur_cycle_tentatives[:c_i]): # удаляем распределенных студентов из лоттереи lottery = [s for s in lottery if s not in cur_cycle_students[:c_i]] # обнуляем цикл cycle_length = 0 # ассайним каждому его школу из предпочтений for i, student in enumerate(cur_cycle_students[:c_i]): final_allocation[student] = cur_cycle_prefs[i] # удаляем школы из списка школ и из списка предпочтений for school in cur_cycle_prefs[:c_i]: school_list.remove(school) # если не все школы распределены, то удаляем распределенные из списка предпочтений if school_list: for school in cur_cycle_prefs[:c_i]: for n in range(4): priorities[n].remove(school) break else: continue else: cycle_length -= 1 user_final_allocation = final_allocation[0] payoff_dict = {"A": 100, "B": 70, "C": 50, "D": 20} return user_final_allocation, payoff_dict[user_final_allocation] # PAGES class VeryFirst(Page): form_model = "player" form_fields = ["player_id"] @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Introduction(Page): timeout_seconds = 600 form_model = "player" @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Priorities(Page): form_model = "player" form_fields = ["answer"] timeout_seconds = 180 @staticmethod def vars_for_template(player: Player): prev_player = player.in_round(1) player.player_id = prev_player.player_id tentatives = ["A", "B", "C", "D"] schools = ["A", "B", "C", "D"] player.player_tentative = random.choice(tentatives) tentatives.remove(player.player_tentative) player.comp1_tentative, player.comp2_tentative, player.comp3_tentative = random.sample(tentatives, 3) player.comp1_priors, player.comp2_priors, player.comp3_priors = ["".join(random.sample(schools, 4)) for i in range(3)] player.lottery = "".join(random.sample(["0", "1", "2", "3"], 4)) lottery = "-".join(list(player.lottery)) return { "player_tentative": player.player_tentative, "comp1_tentative": player.comp1_tentative, "comp2_tentative": player.comp2_tentative, "comp3_tentative": player.comp3_tentative, "lottery": lottery, } class IntResults(Page): @staticmethod def vars_for_template(player: Player): player.final_allocation, player.payoff = find_allocation(player.answer, player.player_tentative, player.comp1_priors, player.comp1_tentative, player.comp2_priors, player.comp2_tentative, player.comp3_priors, player.comp3_tentative, player.lottery) return { "final_allocation": player.final_allocation, "payoff": player.payoff } class FinResults(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 10 page_sequence = [VeryFirst, Introduction, Priorities, IntResults, FinResults]