from otree.api import * import random, json, requests from datetime import datetime doc = """ a.k.a. Keynesian beauty contest. Players all guess a number; whoever guesses closest to 2/3 of the average wins. See https://en.wikipedia.org/wiki/Guess_2/3_of_the_average """ class Constants(BaseConstants): name_in_url = 'beauty' players_per_group = None num_rounds = 60 instructions_template = 'network_guess/instructions.html' def creating_session(subsession): # Initializing session parameters: # Initializing parameters in subsessions: if subsession.round_number == 1: # games_list session variable if subsession.session.config['dropbox_games_url'] != "": games_url = 'https://www.dropbox.com/s/bqgrnew0o1fnbg7/games_list.json?dl=1' res = requests.get(games_url)#, headers={"Authorization": "Bearer " + access_token}) subsession.session.games_list = json.loads(res.text) else: games_file = open('network_games_list/games_list.json') subsession.session.games_list = json.load(games_file) # Initialize round variables. This must be done in order of rounds 1,2,3,4,5...... stage_durations = eval(subsession.session.config['stage_durations']) max_submissions = eval(subsession.session.config['max_submissions_per_stage']) game_sequence = eval(subsession.session.config['game_sequence']) game_labels = eval(subsession.session.config['game_labels']) deltas = eval(subsession.session.config['deltas']) initial_points = eval(subsession.session.config['initial_points']) round_num = 1 #for i, stages in enumerate(stage_durations): for i, gstr in enumerate(game_sequence): stages = stage_durations[i] feedback_round_selection_type = subsession.session.config['feedback_round_selection_type'] if feedback_round_selection_type == 'random': selected_rounds = [random.randrange(1, len(stages)+1)] elif feedback_round_selection_type == 'all': selected_rounds = list(range(1, len(stages)+1)) for j, stg_dur in enumerate(stages): curr_subsession = subsession.in_round(round_num) curr_subsession.is_played = True curr_subsession.delta = deltas[i] curr_subsession.initial_points = initial_points[i] curr_subsession.game_label = game_labels[i] curr_subsession.game_index = i + 1 curr_subsession.num_inner_rounds = len(stages) curr_subsession.inner_round = j + 1 curr_subsession.duration = int(stg_dur) curr_subsession.curr_game = game_sequence[i] curr_subsession.max_submissions = max_submissions[i] curr_subsession.num_players = len(get_games_list(curr_subsession)[curr_subsession.curr_game]['weights']) if curr_subsession.inner_round in selected_rounds: curr_subsession.feedback_selected = True # shuffle players in this round. # curr_subsession.shuffle() round_num += 1 # The last round in the game curr_subsession.is_last_round_in_game = True # The last round in the sessionn curr_subsession.is_last_round_in_session = True # Store number of valid rounds for ss in curr_subsession.in_rounds(1, round_num - 1): ss.num_rounds_in_session = round_num - 1 class Subsession(BaseSubsession): is_played = models.BooleanField(initial=False) delta = models.FloatField() initial_points = models.FloatField() duration = models.IntegerField() curr_game = models.StringField() max_submissions = models.IntegerField() game_label = models.StringField(initial="") game_index = models.IntegerField() num_players = models.IntegerField() num_inner_rounds = models.IntegerField() num_rounds_in_session = models.IntegerField() inner_round = models.IntegerField() is_last_round_in_game = models.BooleanField(initial=False) is_last_round_in_session = models.BooleanField(initial=False) feedback_selected = models.BooleanField(initial=False) def image_path(subsession): games = get_games_list(subsession) return "network_guess/" + games[subsession.curr_game]['image_path'] def num_games(subsession): return len(eval(subsession.session.config['game_sequence'])) # GROUP SHUFFLING FUNCTIONS def shuffle_by_player_type(subsession, player_types, query_type_func, random_shuffle=True): shuffled_players = [] for player_type in player_types: players = [player.id_in_subsession for player in subsession.get_players() if query_type_func(player) == player_type] if random_shuffle: random.shuffle(players) shuffled_players.extend(players) return shuffled_players def shuffle(subsession): shuffle_type = subsession.session.config['shuffle_type'] if shuffle_type == 'session': subsession.session_shuffle() elif shuffle_type == 'game': subsession.game_shuffle() else: subsession.game_shuffle() def session_shuffle(subsession): if subsession.inner_round == 1: if subsession.round_number > 1: subsession.group_like_round(1) else: # We assume all games have the same size as round 1 # players = subsession.get_players() # shuffled_players = list(range(1, len(players)+1)) # random.shuffle(shuffled_players) shuffled_players = subsession.shuffle_by_player_type([True, False], player_passed_control, True) matrix = chunks(shuffled_players, subsession.num_players) subsession.set_group_matrix(matrix) else: subsession.group_like_round(subsession.round_number - 1) for player in subsession.get_players(): player.label_num = player.id_in_group player.update_label() def game_shuffle(subsession: BaseSubsession): if subsession.inner_round == 1: # players = subsession.get_players() # shuffled_players = list(range(1, len(players)+1)) # random.shuffle(shuffled_players) shuffled_players = subsession.shuffle_by_player_type([True, False], player_passed_control, True) matrix = chunks(shuffled_players, subsession.num_players) subsession.set_group_matrix(matrix) else: subsession.group_like_round(subsession.round_number - 1) for player in subsession.get_players(): player.label_num = player.id_in_group player.update_label() class Group(BaseGroup): pass class Player(BasePlayer): # INTRODUCTION AND CONTROL TEST # age = models.IntegerField(label='What is your age?', min=13, max=125) # gender = models.StringField( # choices=[['M', 'Hombre'], ['F', 'Mujer'], ['NB', 'No binario']], # label='¿Cuál es tu género?', # widget=widgets.RadioSelect, # ) # crt_bat = models.IntegerField( # label=''' # Un bate y una pelota de beisbol cuestan 22 euros en total. # El bate cuesta 20 euros más que la pelota. # ¿Cuántos euros cuesta la pelota?''' # ) # crt_widget = models.IntegerField( # label=''' # Si 5 personas necesitan 5 minutos para fabricar 5 platos, # ¿cuánto tardarán 100 personas en fabricar 100 platos? # ''' # ) # crt_lake = models.IntegerField( # label=''' # Imagina en un lago con nenúfares. Cada día la formación de nenúfares crece el doble de su tamaño. # Si el lago se acabará llenando de nenúfares en 48 días, # ¿cuánto tardarán los nenúfares en cubrir la mitad de la superficie del lago? # ''' # ) # GAME FIELDS curr_guess = models.FloatField() curr_guess_type = models.StringField(initial="") guesses_types = models.StringField(inital="") guesses_times = models.StringField(initial="") guesses = models.StringField(initial="") target = models.FloatField() distance = models.FloatField() penalty = models.FloatField() points = models.FloatField() label_num = models.IntegerField() label = models.StringField(inital="") def update_label(player): player.label = get_games_list(player)[player.subsession.curr_game]['labels'][player.label_num - 1] class PlayerGuess(ExtraModel): player = models.Link(Player) guess = models.FloatField() guess_type = models.StringField(initial="") guess_time = models.StringField(initial="") def custom_export(players): # header row yield ['session', 'participant_code', 'game', 'group_id', 'player_label', 'player_label_num', 'num_periods', 'period', 'duration', 'feedback_selected', 'round_number', 'id_in_group', 'time', 'guess', 'guess_type', 'current_guess', 'target', 'distance', 'points'] if players[0].subsession.is_played: for p in players: participant = p.participant session = p.session guesses = PlayerGuess.filter(player=p) for g in guesses: yield [session.code, participant.code, p.subsession.curr_game, p.group.id_in_subsession ,p.label, p.label_num, p.subsession.num_inner_rounds, p.subsession.inner_round, p.subsession.duration, p.subsession.feedback_selected, p.round_number, p.id_in_group, g.guess_time, g.guess, g.guess_type, p.curr_guess, p.target, p.distance, p.points] # FUNCTIONS # Players who are not dropped off def player_passed_control(player: Player): return (player.session.config['ignore_dropout_conditions'] or (not player.participant.message['demographics']['absent'] and player.participant.message['introduction']['num_correct_answers'] >= player.session.config['control_threshold'] ) ) # Here "thing" can be Player, Group or Subsession def get_games_list(thing): return thing.session.games_list; def guess_decimals(thing): if thing.session.config['only_integers'] == True: return 0 else: return thing.session.config['guess_decimals'] # Partition a list l in chinks of n players. The k random curr_guess = player.field_maybe_none('curr_guess') if player.subsession.field_maybe_none('inner_round') == 1 and curr_guess is None: set_curr_guess(player, random_guess(player), 'random') append_to_guesses_list(player) # Pass values to next round (if it is a valid round) if player.round_number < Constants.num_rounds: next_player = player.in_round(player.round_number+1) if player.subsession.curr_game == next_player.subsession.field_maybe_none('curr_game'): if player.curr_guess_type in ['random', 'random_previous']: set_curr_guess(next_player, player.curr_guess, 'random_previous') else: set_curr_guess(next_player, player.curr_guess, 'previous') append_to_guesses_list(next_player) @staticmethod def is_displayed(player: Player): if player.subsession.is_played: if player_passed_control(player): return True else: set_curr_guess(player, random_guess(player), 'random_dropped_out') return False else: return False # The variable my_id is used in Javascript in the page. @staticmethod def js_vars(player: Player): return dict(my_id=player.id_in_group) # live_method is called when a guess is registered in the page # It is assumed that all validation has already taken place there. @staticmethod def live_method(player: Player, data): # We try to decode the message from the webpage and put the guess in the database. # Finally, we return the actual guess registered in the database try: if data['type'] == 'guess': guess = float(data['guess']) set_curr_guess(player, guess, "manual") append_to_guesses_list(player) finally: if player.field_maybe_none('curr_guess') is not None: curr_guess = player.curr_guess else: curr_guess = '' return {player.id_in_group: dict(type='server_response', curr_guess=curr_guess, guesses=player.guesses)} # This WaitPage is ALWAYS displayed class ResultsWaitPage(WaitPage): after_all_players_arrive = compute_targets @staticmethod def is_displayed(player: Player): return player.subsession.is_played class ResultsInRound(Page): @staticmethod def is_displayed(player: Player): return player.session.config['show_feedback_after_round'] and player.subsession.is_played and player_passed_control(player) class ResultsInGame(Page): @staticmethod def is_displayed(player: Player): return player.session.config['show_feedback_after_game'] and player.subsession.is_last_round_in_game and player_passed_control(player) @staticmethod def vars_for_template(player: Player): player_in_rounds = player.in_rounds(player.round_number-player.subsession.num_inner_rounds+1, player.round_number) player_in_rounds = [p for p in player_in_rounds if p.subsession.feedback_selected] return dict( player_in_rounds = player_in_rounds, total_points = sum(p.points for p in player_in_rounds) ) class ResultsInSession(Page): @staticmethod def is_displayed(player: Player): return player.session.config['show_feedback_after_session'] and player.subsession.is_last_round_in_session and player_passed_control(player) @staticmethod def vars_for_template(player: Player): player_in_rounds = player.in_rounds(1, player.subsession.num_rounds_in_session) player_in_rounds = [p for p in player_in_rounds if p.subsession.feedback_selected] return dict( player_in_rounds = player_in_rounds, total_points = sum(p.points for p in player_in_rounds) ) # This WaitPage is ALWAYS displayed class FinalWait(WaitPage): wait_for_all_groups = True @staticmethod def is_displayed(player: Player): return player.subsession.is_last_round_in_session page_sequence = [InitialWaitPage, FailedTest, StartingPage, Guess, ResultsWaitPage, ResultsInRound, ResultsInGame, ResultsInSession, FinalWait]