import json import random import time from otree.api import * import numpy as np doc = """ Observe trials of a DAG and leave a comment """ class C(BaseConstants): NAME_IN_URL = 'Combined_3lights' PLAYERS_PER_GROUP = None # there must be as many rounds as there are cases in the case_list defined in the instructions page NUM_ROUNDS = 5 # number of machines: must equal len(case_list) N_GUESSES = 16 # total guess trials per machine (GUESSES_PER_CONFIG * number of light configs) GUESSES_PER_CONFIG = 2 # guess trials per light configuration (8 configs × 2 = 16) MACHINE_TIMEOUT = 180 # max seconds on the Machine page MACHINE_TIMEOUT_MINUTES = MACHINE_TIMEOUT // 60 PAYMENT_PER_CORRECT = 1.50 # payment in £ for each correct answer in the randomly selected decisions CURRENCY = '£' PAYMENT_PER_CORRECT_DISPLAY = "1.50" # string version for templates (float 1.50 renders as "1.50") GUESS_TIMEOUT = 0 # max seconds per guess page (0 = no timer) RANDOMIZE_COLORS = 0 # 1 = randomize red/blue display per machine per participant, 0 = fixed (red always first) # ── Next-button delay ──────────────────────────────────── # The delay (in seconds) before the Next button appears on # each page is configured in: # _templates/global/Page.html → NEXT_BUTTON_DELAYS dict # Pages not listed there default to 0 (button visible immediately). # ───────────────────────────────────────────────────────── class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): ID_subject = models.StringField(label="Please write your Prolific ID") notes = models.LongStringField(blank=True) Example_notes = models.LongStringField() case = models.StringField() machine_name = models.StringField() case_order = models.StringField() error = models.IntegerField(initial=0) original_color = models.IntegerField() table = models.LongStringField() explanation = models.LongStringField( label="Please describe in your own words how you made your predictions for this machine.",) bonus_message_effect = models.StringField(label="", choices=[ "It may help another participant make better predictions and earn me a bonus", "It does not affect my bonus", "I will automatically receive the bonus regardless of accuracy",],) advice_text = models.LongStringField(label="", blank=False) predicted_correct_other = models.IntegerField() predicted_correct_self = models.IntegerField() certainty = models.IntegerField(min=0, max=100) difficulty = models.IntegerField(min=1, max=10) difficulty_certainty = models.IntegerField(min=0, max=100) prediction_strategy = models.LongStringField(label="", blank=False) final_comments = models.LongStringField(label="", blank=False) page_load_ts = models.FloatField(blank=True) time_on_page = models.FloatField(blank=True) time_guess_1 = models.FloatField(blank=True) time_guess_2 = models.FloatField(blank=True) time_guess_3 = models.FloatField(blank=True) time_guess_4 = models.FloatField(blank=True) time_guess_5 = models.FloatField(blank=True) time_guess_6 = models.FloatField(blank=True) time_guess_7 = models.FloatField(blank=True) time_guess_8 = models.FloatField(blank=True) time_guess_9 = models.FloatField(blank=True) time_guess_10 = models.FloatField(blank=True) time_guess_11 = models.FloatField(blank=True) time_guess_12 = models.FloatField(blank=True) time_guess_13 = models.FloatField(blank=True) time_guess_14 = models.FloatField(blank=True) time_guess_15 = models.FloatField(blank=True) time_guess_16 = models.FloatField(blank=True) # comprehension questions num_wrong = models.IntegerField(initial=0) cq1 = models.IntegerField( choices=[ (1, "There are 5 tasks. All observations in the same task are generated by the same machine."), (2, "There are 5 tasks. Different observations in the same task can be generated by different machines."), (3, "The machine switches when predictions are correct."), (4, "The machine changes whenever the lights change."), ], widget=widgets.RadioSelect) cq2 = models.IntegerField( choices=[ (1, "Yes, the sound is always identical when the lights match."), (2, "No, the sound may vary even with the same observable lights, due to the presence of unobservable lights."), (3, "The sound depends on previous predictions."), (4, "The rule changes after some trials."), ], widget=widgets.RadioSelect) cq3 = models.IntegerField( choices=[ (1, "Yes, they do."), (2, "Yes, the machine learns over time."), (3, "No, each observation is independent."), (4, "Only in a given task, but not across tasks."), ], widget=widgets.RadioSelect) cq4 = models.IntegerField( choices=[ (1, "Identify which machine is operating."), (2, "Predict whether the machine will make a sound."), (3, "Change the lights to influence the outcome."), (4, "Guess the overall pattern of the lights in the machine."), ], widget=widgets.RadioSelect) cq5 = models.IntegerField( choices=[ (1, "You will be paid only for your last prediction."), (2, "A random observation from each machine will be selected and you will be paid if your prediction is correct."), (3, "A random machine will be selected, a random observation from that machine will be drawn, and you will be paid if your prediction is correct."), (4, "No additional payment, you are only paid a participation fee."), ], widget=widgets.RadioSelect) # from Part2 n_lights = models.IntegerField() guess1 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess2 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess3 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess4 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess5 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess6 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess7 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess8 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess9 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess10 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess11 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess12 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess13 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess14 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess15 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess16 = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') guess_example = models.IntegerField(choices=[[1, 'Ding'], [0, 'No Ding']], label='') # Save each of the sampled rows as strings row1 = models.StringField() row2 = models.StringField() row3 = models.StringField() row4 = models.StringField() row5 = models.StringField() row6 = models.StringField() row7 = models.StringField() row8 = models.StringField() row9 = models.StringField() row10 = models.StringField() row11 = models.StringField() row12 = models.StringField() row13 = models.StringField() row14 = models.StringField() row15 = models.StringField() row16 = models.StringField() # Exported data for analysis guess_configs_json = models.LongStringField(blank=True) order_names_json = models.LongStringField(blank=True) # Color permutations for 3-light cases. # Each tuple (r_src, b_src, g_src) is the data column index that feeds each display color. _PERMS_3L = [ (0, 1, 2), # 0: original (Red=col0, Blue=col1, Green=col2) (1, 0, 2), # 1: swap Red<->Blue (2, 1, 0), # 2: swap Red<->Green (0, 2, 1), # 3: swap Blue<->Green (1, 2, 0), # 4: rotate (Red=col1, Blue=col2, Green=col0) (2, 0, 1), # 5: rotate (Red=col2, Blue=col0, Green=col1) ] # FUNCTIONS PART 1 def html_table_probs(case, probs, n): # case should be an array of dimensions kxn. k is the number of unique trials and n is n_lights + others + sound # frequency should be a vector of size k with each element i equal to the number or repetitions of trial i wanted # the total number of trials will be sum(freq) draws = np.random.choice([i for i in range(len(case))], size=n, replace=True, p=probs) # n_lights is the number of lights excluding "others". This can be 2 (Red and Blue) or 3 (Red, Blue, Green) n_lights = len(case[0]) # Create a matrix with the realizations of each row (they are already in a random order because draws is random) h = [] for i in range(len(draws)): h.append(case[draws[i]]) # 2 lights: Red, Blue (and Others) if n_lights == 4: # Translate the binary matrix into a html matrix red = [] blue = [] sound = [] other = ['?'] * len(draws) for i in range(len(draws)): # red lights if h[i][0] == 1: red.append('
') else: red.append('
') # blue lights if h[i][1] == 1: blue.append('
') else: blue.append('
') # sound if h[i][3] == 1: sound.append('♪ DING ♪') else: sound.append('NO DING') html_mat = np.column_stack((red, blue, other, sound)) # Write out the html table code table = '' \ '' for i in range(len(draws)): table = table + '' \ '' \ '' \ '' table = table + '
Red LightBlue LightOther LightsSound
' + html_mat[i][0] + '' + html_mat[i][1] + '' + html_mat[i][2] + '' + html_mat[i][3] + '
' # 3 lights: Red, Blue, Green (and Others) if n_lights == 5: # Translate the binary matrix into a html matrix red = [] blue = [] green = [] sound = [] other = ['?'] * len(draws) for i in range(len(draws)): # red lights if h[i][0] == 1: red.append('
') else: red.append('
') # blue lights if h[i][1] == 1: blue.append('
') else: blue.append('
') # green lights if h[i][2] == 1: green.append('
') else: green.append('
') # sound if h[i][4] == 1: sound.append('♪ DING ♪') else: sound.append('NO DING') html_mat = np.column_stack((red, blue, green, other, sound)) # Write out the html table code table = '' \ '' for i in range(len(draws)): table = table + '' \ '' \ '' \ '' \ '' table = table + '
Red LightBlue LightGreen LightOther LightsSound
' + html_mat[i][0] + '' + html_mat[i][1] + '' + html_mat[i][2] + '' + html_mat[i][3] + '' + html_mat[i][4] + '
' return table, h def html_table_freqs_original(case, freq, color_perm=0): # case should be an array of dimensions kxn. k is the number of unique trials and n is n_lights + others + sound # frequency should be a vector of size k with each element i equal to the number or repetitions of trial i wanted # the total number of trials will be sum(freq) # n_lights is the number of lights excluding "others". This can be 2 (Red and Blue) or 3 (Red, Blue, Green) n_lights = len(case[0]) # 2 lights: Red, Blue (and Others) if n_lights == 4: # Add the repetitions for each row h = np.tile(case[0], (freq[0], 1)) for i in range(len(freq) - 1): a = np.tile(case[i + 1], (freq[i + 1], 1)) h = np.vstack((h, a)) # Shuffle the rows of html matrix with repeated trials np.random.shuffle(h) # Translate the binary matrix into a html matrix red = [] blue = [] sound = [] other = ['?'] * sum(freq) for i in range(sum(freq)): # red lights if h[i][0] == 1: red.append('
') else: red.append('
') # blue lights if h[i][1] == 1: blue.append('
') else: blue.append('
') # sound if h[i][3] == 1: sound.append('♪ DING ♪') else: sound.append('-') html_mat = np.column_stack((red, blue, other, sound)) # Write out the html table code table = '' \ ''\ '' \ '' \ '' \ '' for i in range(sum(freq)): table = table + '' \ ''\ ''\ ''\ '' table = table + '
Red LightBlue LightOther LightsSound
' + html_mat[i][0] + '' + html_mat[i][1] + '' + html_mat[i][2] + '' + html_mat[i][3] + '
' # 3 lights: Red, Blue, Green (and Others) if n_lights == 5: # Add the repetitions for each row h = np.tile(case[0], (freq[0], 1)) for i in range(len(freq) - 1): a = np.tile(case[i + 1], (freq[i + 1], 1)) h = np.vstack((h, a)) # Shuffle the rows of html matrix with repeated trials np.random.shuffle(h) # Apply color permutation: r_src/b_src/g_src are the data column indices # that feed the Red/Blue/Green display columns respectively. r_src, b_src, g_src = _PERMS_3L[color_perm] # Translate the binary matrix into a html matrix red = [] blue = [] green = [] sound = [] other = ['?'] * sum(freq) for i in range(sum(freq)): red.append('
' if h[i][r_src] == 1 else '
') blue.append('
' if h[i][b_src] == 1 else '
') green.append('
' if h[i][g_src] == 1 else '
') if h[i][4] == 1: sound.append('♪ DING ♪') else: sound.append('-') html_mat = np.column_stack((red, blue, green, other, sound)) # Write out the html table code table = '' \ '' \ '' \ '' \ '' \ '' \ '' for i in range(sum(freq)): table = table + ''\ ''\ ''\ ''\ '' table = table + '
Red LightBlue LightGreen LightOther LightsSound
' + html_mat[i][0] + '' + html_mat[i][1] + '' + html_mat[i][2] + '' + html_mat[i][3] + '' + html_mat[i][4] + '
' return table, h def html_table_freqs_flipped(case, freq, color_perm=0): # same function but whenever a circle was red it will now be blue and vice-versa # case should be an array of dimensions kxn. k is the number of unique trials and n is n_lights + others + sound # frequency should be a vector of size k with each element i equal to the number or repetitions of trial i wanted # the total number of trials will be sum(freq) # n_lights is the number of lights excluding "others". This can be 2 (Red and Blue) or 3 (Red, Blue, Green) n_lights = len(case[0]) # 2 lights: Red, Blue (and Others) if n_lights == 4: # Add the repetitions for each row h = np.tile(case[0], (freq[0], 1)) for i in range(len(freq) - 1): a = np.tile(case[i + 1], (freq[i + 1], 1)) h = np.vstack((h, a)) # Shuffle the rows of html matrix with repeated trials np.random.shuffle(h) # Translate the binary matrix into a html matrix red = [] blue = [] sound = [] other = ['?'] * sum(freq) for i in range(sum(freq)): # red lights still called red but now the color shown is blue if h[i][0] == 1: red.append('
') else: red.append('
') # blue lights still called blue but now the color shown is red if h[i][1] == 1: blue.append('
') else: blue.append('
') # sound if h[i][3] == 1: sound.append('♪ DING ♪') else: sound.append('-') # stack the columns in order such that the first column is blue (shown as red) and the second is red (shown as blue) html_mat = np.column_stack((blue, red, other, sound)) # Write out the html table code table = '' \ ''\ '' \ '' \ '' \ '' for i in range(sum(freq)): table = table + '' \ ''\ ''\ ''\ '' table = table + '
Red LightBlue LightOther LightsSound
' + html_mat[i][0] + '' + html_mat[i][1] + '' + html_mat[i][2] + '' + html_mat[i][3] + '
' # 3 lights: Red, Blue, Green (and Others) (colors here have not been changed) if n_lights == 5: # Add the repetitions for each row h = np.tile(case[0], (freq[0], 1)) for i in range(len(freq) - 1): a = np.tile(case[i + 1], (freq[i + 1], 1)) h = np.vstack((h, a)) # Shuffle the rows of html matrix with repeated trials np.random.shuffle(h) # Apply color permutation: r_src/b_src/g_src are the data column indices # that feed the Red/Blue/Green display columns respectively. r_src, b_src, g_src = _PERMS_3L[color_perm] # Translate the binary matrix into a html matrix red = [] blue = [] green = [] sound = [] other = ['?'] * sum(freq) for i in range(sum(freq)): red.append('
' if h[i][r_src] == 1 else '
') blue.append('
' if h[i][b_src] == 1 else '
') green.append('
' if h[i][g_src] == 1 else '
') if h[i][4] == 1: sound.append('♪ DING ♪') else: sound.append('-') html_mat = np.column_stack((red, blue, green, other, sound)) # Write out the html table code table = '' \ '' \ '' \ '' \ '' \ '' \ '' for i in range(sum(freq)): table = table + ''\ ''\ ''\ ''\ '' table = table + '
Red LightBlue LightGreen LightOther LightsSound
' + html_mat[i][0] + '' + html_mat[i][1] + '' + html_mat[i][2] + '' + html_mat[i][3] + '' + html_mat[i][4] + '
' return table, h # FUNCTIONS PART 2 # translate each of the sampled case rows to the html table it can take 2 or 3 lights def html_table_original(row, n_lights, color_perm=0): # row is a row taken from a case from part 1. The case can have either 4 columns (Red, Blue, others, sound) or # it can have 5 columns (Red, Blue, Green, others, Sound) # 2 lights: Red, Blue (and Others) if n_lights == 4: # Translate the binary matrix into a html matrix red = [] blue = [] other = ['?'] # red lights if row[0] == 1: red.append('
') else: red.append('
') # blue lights if row[1] == 1: blue.append('
') else: blue.append('
') html_mat = np.column_stack((red, blue, other)) # Write out the html table code table = '' \ '
Red LightBlue LightOther Lights
' \ + html_mat[0][0] + '' + html_mat[0][1] + '' + html_mat[0][2] + '
' # 3 lights: Red, Blue, Green (and Others) if n_lights == 5: # Apply color permutation r_src, b_src, g_src = _PERMS_3L[color_perm] other = ['?'] red = ['
' if row[r_src] == 1 else '
'] blue = ['
' if row[b_src] == 1 else '
'] green = ['
' if row[g_src] == 1 else '
'] html_mat = np.column_stack((red, blue, green, other)) # Write out the html table code table = '' \ ''\ '
Red LightBlue LightGreen LightOther Lights
' + html_mat[0][0] + '' + html_mat[0][1] + \ '' + html_mat[0][2] + \ '' + html_mat[0][3] + '
' return table def html_table_flipped(row, n_lights, color_perm=0): # this table has the opposite colors but takes the data in in the same order. # row is a row taken from a case from part 1. The case can have either 4 columns (Red, Blue, others, sound) or # it can have 5 columns (Red, Blue, Green, others, Sound) # 2 lights: Red, Blue (and Others) if n_lights == 4: # Translate the binary matrix into a html matrix red = [] blue = [] other = ['?'] # red lights (they are still called red but the color shown is blue) if row[0] == 1: red.append('
') else: red.append('
') # blue lights (they are still called blue but the color shown is red) if row[1] == 1: blue.append('
') else: blue.append('
') html_mat = np.column_stack((blue, red, other)) # Write out the html table code table = '' \ '
Red LightBlue LightOther Lights
' \ + html_mat[0][0] + '' + html_mat[0][1] + '' + html_mat[0][2] + '
' # 3 lights: Red, Blue, Green (and Others) if n_lights == 5: # Apply color permutation r_src, b_src, g_src = _PERMS_3L[color_perm] other = ['?'] red = ['
' if row[r_src] == 1 else '
'] blue = ['
' if row[b_src] == 1 else '
'] green = ['
' if row[g_src] == 1 else '
'] html_mat = np.column_stack((red, blue, green, other)) # Write out the html table code table = '' \ ''\ '
Red LightBlue LightGreen LightOther Lights
' + html_mat[0][0] + '' + html_mat[0][1] + \ '' + html_mat[0][2] + \ '' + html_mat[0][3] + '
' return table def sample_balanced_guess_rows(case_def, freq, guesses_per_config=3): """ Sample guess trial rows from the case definition and frequencies. Returns a list of rows (each a list of ints), ordered so that no two consecutive rows share the same light configuration. For 2-light cases (4 columns): light config = first 2 elements (Red, Blue) For 3-light cases (5 columns): light config = first 3 elements (Red, Blue, Green) """ n_cols = len(case_def[0]) config_cols = n_cols - 2 # exclude Others and Sound columns # Group case rows by light config, with their frequencies as weights config_groups = {} for i, row in enumerate(case_def): config = tuple(row[:config_cols]) if config not in config_groups: config_groups[config] = {'rows': [], 'weights': []} config_groups[config]['rows'].append(list(row)) config_groups[config]['weights'].append(freq[i]) # For each config, sample guesses_per_config rows weighted by frequency config_pools = {} for config, group in config_groups.items(): weights = group['weights'] total_weight = sum(weights) if total_weight == 0: # If config never appeared, use equal weights (50/50 Ding vs No Ding) weights = [1] * len(group['rows']) chosen = random.choices(group['rows'], weights=weights, k=guesses_per_config) config_pools[config] = chosen # Greedy arrangement: no two consecutive rows with the same light config. # Always pick from the config with the most remaining rows to avoid dead ends. result = [] prev_config = None total = sum(len(v) for v in config_pools.values()) for _ in range(total): # Configs different from the previous one that still have rows available = {k: v for k, v in config_pools.items() if len(v) > 0 and k != prev_config} if not available: # Fallback: only one config left available = {k: v for k, v in config_pools.items() if len(v) > 0} # Among available, pick from the most populated (random tie-break) max_count = max(len(v) for v in available.values()) top_configs = [k for k, v in available.items() if len(v) == max_count] chosen_config = random.choice(top_configs) result.append(config_pools[chosen_config].pop()) prev_config = chosen_config return result # PAGES class Introduction(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 def vars_for_template(player): return dict( max_total_payment= C.PAYMENT_PER_CORRECT + player.session.config['participation_fee'] ) class Instructions1(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Load_Example(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): participant = player.participant # ------------------------- # 1) Define your EXAMPLE case + freq_example here # Choose ONE: case_example = case1 (2 lights) OR case2 (3 lights) # ------------------------- # Example: 3-lights case (Red, Blue, Green, Other, Sound) case_example = [ [0, 0, 0, 0, 0], # all lights off, z=0 [0, 0, 0, 0, 1], # all lights off, z=1 [1, 0, 0, 0, 1], # Red on, z=1 [0, 1, 0, 0, 1], # Blue on, z=1 [0, 0, 1, 0, 1], # Green on, z=1 [1, 1, 0, 0, 1], # Red+Blue on, z=1 ] # Your special example frequencies (same length as case_example rows) freq_example = [1, 2, 1, 1, 1, 1] # <-- EDIT THIS # ------------------------- # 2) Create the example table (original vs flipped colors) # ------------------------- original_color = 1 # example always uses original colors regardless of RANDOMIZE_COLORS player.original_color = original_color participant.vars['example_original_color'] = original_color # optional if original_color == 1: table_html, matrix = html_table_freqs_original(case_example, freq_example) else: table_html, matrix = html_table_freqs_flipped(case_example, freq_example) # ------------------------- # 3) Save to player so Example page can display it # ------------------------- player.table = table_html player.case = str(matrix) class Example(Page): form_model = 'player' form_fields = ['Example_notes'] @staticmethod def error_message(player, values): if len(values['Example_notes'].strip()) == 0: return 'Please write something before continuing.' @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): return dict( table=player.table, round=player.round_number, ) @staticmethod def before_next_page(player: Player, timeout_happened): # store notes safely for example only player.participant.vars['Example_notes'] = player.Example_notes class Guess_Example(Page): form_model = 'player' form_fields = ['guess_example'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def vars_for_template(player: Player): # YOU choose the example row EXAMPLE_ROW = [0, 0, 1, 0, 1] n_lights = len(EXAMPLE_ROW) if player.original_color == 1: table = html_table_original(EXAMPLE_ROW, n_lights) else: table = html_table_flipped(EXAMPLE_ROW, n_lights) return dict( table=table, Example_notes=player.participant.vars.get('Example_notes', ''), ) class Instructions1_2(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Instructions1_2a(Page): form_model = 'player' form_fields = ['Example_notes', 'guess_example'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def error_message(player, values): if not (values.get('Example_notes') or '').strip(): return 'Please write something in the notes before continuing.' @staticmethod def vars_for_template(player: Player): participant = player.participant # ------------------------- # 1) Define your EXAMPLE case + freq_example here # ------------------------- case_example = [ [0, 1, 0, 0, 1], # Blue on, z=1 [0, 1, 1, 0, 1], # Blue+Green on, z=1 [1, 1, 0, 0, 1], # Red+Blue on, z=1 [0, 1, 0, 0, 0], # Blue on, z=0 [1, 0, 1, 0, 0], # Red+Green on, z=0 [1, 0, 0, 0, 0], # Red on, z=0 ] freq_example = [2, 1, 1, 1, 1, 1] # ------------------------- # 2) Create the example table (original vs flipped colors) # ------------------------- original_color = 1 # example always uses original colors regardless of RANDOMIZE_COLORS player.original_color = original_color participant.vars['example_original_color'] = original_color if original_color == 1: table_html, matrix = html_table_freqs_original(case_example, freq_example) else: table_html, matrix = html_table_freqs_flipped(case_example, freq_example) # Save to player player.table = table_html player.case = str(matrix) # Continuation example row EXAMPLE_ROW = [1, 1, 0, 0, 1] n_lights = len(EXAMPLE_ROW) if original_color == 1: table_cont = html_table_original(EXAMPLE_ROW, n_lights) else: table_cont = html_table_flipped(EXAMPLE_ROW, n_lights) correct_example = EXAMPLE_ROW[-1] # Build expanded observation list for JS payment simulation n_obs = len(case_example[0]) - 2 # observable lights (exclude unobserved and z) z_idx = len(case_example[0]) - 1 obs_list = [] for row, freq in zip(case_example, freq_example): for _ in range(freq): obs_list.append([row[:n_obs], row[z_idx]]) pred_obs_lights = EXAMPLE_ROW[:n_obs] return dict( table=table_html, table_cont=table_cont, correct_example=correct_example, obs_list_json=json.dumps(obs_list), pred_obs_lights_json=json.dumps(pred_obs_lights), original_color=original_color, round=player.round_number, ) @staticmethod def before_next_page(player: Player, timeout_happened): # store notes safely for example only player.participant.vars['Example_notes'] = player.Example_notes class Load_Example_Continuation(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Instructions_Summary(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Instructions(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 @staticmethod def before_next_page(player: Player, timeout_happened): if player.round_number == 1: participant = player.participant participant.notes = [] participant.guesses = [] participant.sound = [] participant.realized_cases = [] participant.original_color = [] participant.guess_values = [] participant.guess_configs = [] # ── Researcher-defined correct predictions ────────────────── # Keys = machine names (must match the names in case_list). # Sub-keys = light-config tuples as strings, e.g. "(0, 0)". # Values = the correct sound prediction (1 = Ding, 0 = No Ding). # Edit these to match your experimental design. participant.correct_predictions = { '1L': { '(0, 0, 0)': 0, '(0, 0, 1)': 0, '(0, 1, 0)': 0, '(0, 1, 1)': 0, '(1, 0, 0)': 0, '(1, 0, 1)': 0, '(1, 1, 0)': 0, '(1, 1, 1)': 0, }, # '2L': { # '(0, 0, 0)': 0, # '(0, 0, 1)': 0, # '(0, 1, 0)': 0, # '(0, 1, 1)': 0, # '(1, 0, 0)': 0, # '(1, 0, 1)': 0, # '(1, 1, 0)': 0, # '(1, 1, 1)': 0, # }, } # The columns of a case with 3 lights are Red light, Blue light, Green light, Other lights, Sound case2 = np.array([ [0, 0, 0, 0, 0], [0, 0, 0, 0, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 1], [0, 1, 0, 0, 0], [0, 1, 0, 0, 1], [0, 1, 1, 0, 0], [0, 1, 1, 0, 1], [1, 0, 0, 0, 0], [1, 0, 0, 0, 1], [1, 0, 1, 0, 0], [1, 0, 1, 0, 1], [1, 1, 0, 0, 0], [1, 1, 0, 0, 1], [1, 1, 1, 0, 0], [1, 1, 1, 0, 1], ]) freq1 = [4, 0, 2, 1, 9, 0, 3, 1, 1, 0, 0, 0, 1, 2, 1, 5] # AND DIFFICULT, second best 1 variable, 87% true accuracy (AND_difficult_x1_alt2) freq1_alt = [1, 0, 0, 0, 5, 1, 2, 1, 4, 0, 2, 1, 1, 4, 0, 8] #AND DIFFICULT, second best Joint freq2 = [4, 0, 4, 1, 3, 0, 3, 1, 3, 0, 3, 1, 1, 3, 0, 3] # AND EASY NOT INCLUDED, second best 1 variable, 87% true accuracy (AND_easy_notinv_alt2) freq2_alt = [2, 0, 2, 0, 4, 0, 3, 1, 3, 0, 3, 1, 1, 3, 0, 4] #AND EASY higher acc freq_2_VE = [5, 0, 4, 1, 3, 0, 3, 1, 3, 0, 3, 1, 0, 2, 0, 2] # AND very easy freq3 = [5, 0, 2, 1, 1, 2, 0, 5, 1, 1, 0, 1, 1, 4, 0, 7] # OR DIFFICULT, second best 1 variable, 87% true accuracy (OR_difficult_x2_alt2) freq5 = [4, 1, 5, 0, 0, 4, 1, 3, 0, 3, 1, 3, 0, 3, 1, 3] # OR EASY NOT INCLUDED, second best 1 variable, 87% true accuracy (OR_easy_notinv_alt2) freq6 = [7, 0, 3, 1, 1, 1, 0, 1, 1, 3, 0, 9, 1, 0, 1, 1] # EITHER DIFFICULT freq6_B = [6, 0, 4, 1, 1, 1, 0, 1, 0, 4, 1, 8, 0, 0, 2, 1] # EITHER DIFFICULT NEW freq7 = [4, 0, 3, 1, 1, 4, 0, 4, 1, 4, 0, 4, 3, 0, 3, 1] # EITHER EASY freq8 = [0, 0, 0, 1, 6, 0, 1, 1, 7, 0, 2, 1, 1, 2, 0, 7] # JOINT DIFFICULT freq9 = [0, 3, 1, 3, 3, 1, 3, 0, 4, 1, 4, 0, 0, 3, 1, 3] # JOINT EASY, second best 2 variable (NOR), 88% true accuracy (JOINT_easy_AND_alt1) freq9_A = [1, 3, 0, 3, 3, 0, 3, 1, 3, 0, 3, 1, 0, 3, 0, 3] # JOINT EASY freq12 = [4, 0, 1, 1, 4, 0, 2, 0, 0, 4, 0, 10, 2, 1, 1, 1] # INHIBITION DIFFICULT freq15 = [4, 0, 3, 0, 5, 0, 4, 1, 1, 4, 0, 4, 3, 0, 2, 0] # INHIBITION EASY, second best 2 variables, 88% true accuracy (INHIBIT_easy_alt2) freq10 = [4, 0, 2, 1, 7, 0, 4, 1, 0, 0, 0, 1, 1, 2, 0, 5] # ALONE DIFFICULT freq10_2 = [6, 0, 3, 1, 5, 0, 2, 1, 1, 1, 0, 1, 1, 2, 0, 5] freq11_AA = [2, 0, 2, 0, 4, 0, 4, 1, 0, 2, 0, 2, 1, 4, 1, 5] # ALONE EASY ## AND DATASETS #### #freq1_AND = [4, 0, 2, 1, 2, 0, 1, 0, 2, 1, 1, 1, 1, 4, 0, 8] #AND - difficult -- x_1 alone predicts well # [5, 0, 2, 1, 7, 0, 3, 1, 1, 0, 1, 1, 1, 3, 0, 5] # [4, 0, 2, 1, 9, 0, 3, 1, 1, 0, 0, 0, 1, 2, 1, 5] # [5, 0, 2, 1, 1, 0, 0, 0, 4, 0, 2, 1, 1, 3, 1, 8] here second x_2 alone #freq2_AND = [2, 0, 2, 0, 4, 0, 3, 1, 3, 0, 3, 1, 1, 3, 0, 4] # AND -- easy -- not invariant, the true one is slightly better # [3, 0, 3, 1, 4, 0, 4, 1, 3, 0, 3, 1, 1, 3, 0, 4] # [4, 0, 4, 1, 3, 0, 3, 1, 3, 0, 3, 1, 1, 3, 0, 3] # [4, 0, 4, 1, 3, 0, 3, 1, 4, 0, 3, 1, 1, 3, 0, 4] #freq3_AND = [3, 0, 3, 1, 1, 0, 1, 0, 2, 1, 2, 1, 1, 5, 0, 7] # AND -- easy -- INVARIANT, the true one is the same #freq4_AND = [4, 1, 2, 1, 3, 0, 2, 1, 4, 1, 2, 1, 1, 3, 0, 4] # [3, 0, 3, 1, 5, 0, 4, 1, 3, 1, 3, 1, 1, 2, 0, 2] # AND -- medium -- should be easier than the difficult one, and the true performance is worse than in difficult #freq5_AND = [4, 0, 2, 1, 7, 0, 3, 1, 6, 0, 2, 1, 1, 1, 0, 1] # Difficult, second best is the prior, the easier version with the prior is [3, 1, 4, 0, 3, 1, 3, 0, 3, 1, 3, 0, 0, 3, 1, 2] #freq6 = [1, 0, 0, 0, 5, 1, 2, 1, 4, 0, 2, 1, 1, 4, 0, 8] # Difficult, second best is 00 -> 1 or 11 -->1, the easier version is #[3, 0, 2, 1, 4, 0, 3, 1, 3, 0, 3, 1, 1, 3, 0, 3] #[3, 0, 3, 1, 3, 0, 3, 1, 4, 0, 3, 1, 1, 3, 0, 4] ## OR DATASETS #### #freq1_OR = [2, 1, 7, 1, 0, 2, 0, 3, 0, 2, 0, 0, 0, 3, 1, 7] (alternative [6, 1, 3, 1, 0, 2, 0, 3, 0, 1, 0, 1, 1, 3, 0, 7]) # OR -- difficult -- x2 alone predicts well # [3, 1, 2, 1, 1, 3, 0, 6, 0, 0, 0, 1, 1, 3, 0, 7] # [5, 0, 2, 1, 1, 2, 0, 5, 1, 1, 0, 1, 1, 4, 0, 7] # [6, 0, 3, 1, 1, 3, 1, 6, 0, 0, 0, 1, 1, 1, 0, 4] #freq2_OR = [3, 1, 4, 0, 0, 3, 1, 3, 0, 3, 1, 2, 0, 3, 1, 2] # OR -- easy -- x1 alone predicts well -- already lower accuracy than difficult, here even more so #[6, 2, 1, 4, 1, 9, 1, 6] (80%) -- Easier version #[4, 1, 5, 0, 0, 4, 1, 3, 0, 3, 1, 3, 0, 3, 1, 3] #freq3_OR = [4, 1, 5, 1, 0, 2, 0, 1, 0, 2, 0, 2, 0, 5, 1, 5] # OR -- easy -- INVARIANT #freq4_OR = [1, 0, 1, 1, 1, 3, 0, 7, 1, 2, 0, 5, 1, 2, 0, 5] # Difficult, second best is the prior, the easier version with the prior is [3, 0, 3, 1, 1, 3, 0, 4, 1, 3, 0, 3, 1, 3, 0, 4] #freq5_OR = [7, 1, 4, 1, 1, 3, 0, 6, 1, 2, 0, 5, 1, 1, 0, 1] # Difficult, second best is either # [5, 0, 2, 1, 1, 3, 0, 6, 1, 2, 0, 6, 1, 1, 0, 1] -- The easier versions are #[3, 0, 2, 1, 1, 3, 0, 4, 1, 2, 0, 3, 1, 3, 0, 4] # [3, 1, 3, 0, 0, 4, 1, 4, 0, 3, 1, 3, 0, 3, 1, 3] ## EITHER DATASETS #### #freq1_EITHER = [0, 0, 10, 1, 1, 2, 0, 0, 0, 6, 1, 6, 0, 0, 2, 1] (alternative [7, 0, 3, 1, 1, 1, 0, 1, 1, 3, 0, 9, 1, 0, 1, 1]) # EITHER -- difficult -- inhibit second best [7, 1, 3, 1, 1, 1, 0, 1, 1, 2, 0, 5, 7, 0, 3, 1], [9, 0, 3, 1, 1, 2, 0, 5, 0, 0, 0, 1, 5, 1, 2, 1] ---- OR second best #[6, 0, 3, 1, 1, 2, 0, 5, 1, 3, 1, 5, 1, 0, 0, 0] , [6, 0, 2, 1, 2, 2, 0, 7, 1, 1, 0, 6, 1, 0, 0, 0] #freq2_EITHER = [4, 0, 4, 1, 1, 3, 0, 4, 0, 2, 0, 3, 4, 0, 3, 1] # EITHER -- easy -- inhibit second best, more accurate than difficult --- [3, 1, 3, 0, 0, 3, 1, 3, 0, 3, 1, 3, 3, 1, 3, 0], [4, 0, 3, 1, 1, 3, 0, 3, 1, 2, 0, 3, 3, 0, 3, 1] --- OR second best [3, 0, 3, 1, 1, 3, 0, 3, 1, 3, 0, 4, 4, 0, 3, 1], [4, 0, 3, 1, 1, 4, 0, 4, 1, 4, 0, 4, 3, 0, 3, 1] #freq3_EITHER = [4, 0, 2, 1, 1, 2, 1, 4, 1, 3, 0, 4, 3, 0, 2, 1] # EITHER -- medium but should be doable -- less accurate than difficult ## JOINT DATASETS #### #freq1_JOINT = [1, 1, 0, 1, 7, 0, 2, 1, 6, 0, 2, 1, 1, 2, 0, 4] # [1, 1, 0, 1, 6, 0, 2, 1, 7, 0, 3, 1, 1, 3, 0, 6] # [0, 0, 0, 1, 6, 0, 1, 1, 7, 0, 2, 1, 1, 2, 0, 7] --- [1, 1, 0, 2, 2, 1, 1, 1, 4, 0, 2, 0, 1, 4, 0, 8] # JOINT -- original difficult -- alternative is all but 1,0 -> 1 (complicated, weird) #[1, 2, 0, 5, 6, 0, 3, 1, 1, 0, 1, 1, 1, 3, 0, 6] ,[1, 1, 0, 6, 9, 0, 2, 1, 1, 0, 0, 0, 1, 2, 0, 8] #freq2_JOINT = [1, 3, 0, 3, 3, 0, 3, 1, 3, 0, 3, 1, 0, 3, 0, 3] # JOINT -- easy -- alternative is AND [0, 3, 1, 3, 3, 1, 3, 0, 4, 1, 4, 0, 0, 3, 1, 3] , [0, 4, 1, 4, 3, 1, 3, 0, 3, 1, 4, 0, 0, 4, 1, 4] ---- [1, 1, 0, 2, 3, 0, 1, 0, 4, 0, 2, 1, 1, 3, 1, 7] # Medium #[0, 4, 1, 3, 4, 1, 4, 0, 3, 1, 4, 0, 0, 3, 1, 3],[1, 3, 0, 4, 5, 0, 4, 1, 4, 0, 3, 1, 1, 3, 0, 4] ## ALONE DATASETS #### #freq1_ALONE = [7, 0, 3, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 3, 0, 7] # ALONE difficult alternative or [8, 0, 4, 1, 1, 0, 1, 1, 1, 2, 1, 4, 1, 3, 0, 6] -- alternative is AND [4, 0, 2, 1, 7, 0, 4, 1, 0, 0, 0, 1, 1, 2, 0, 5] -- [6, 0, 3, 1, 5, 0, 2, 1, 1, 1, 0, 1, 1, 2, 0, 5] #freq2_ALONE = [2, 0, 2, 0, 4, 0, 4, 1, 1, 4, 0, 5, 1, 3, 0, 3] # ALONE easy , accuracy is higher than difficult [4, 0, 4, 1, 4, 0, 3, 1, 1, 4, 0, 5, 1, 3, 0, 3] , [4, 0, 3, 1, 4, 0, 3, 1, 1, 4, 0, 4, 1, 3, 0, 4], [4, 0, 3, 1, 4, 0, 3, 1, 1, 4, 0, 5, 1, 3, 0, 4] #freq3_ALONE = [2, 0, 2, 1, 3, 0, 3, 1, 1, 3, 1, 5, 1, 3, 1, 3] -- medium accuracy 80%, less than difficult ## INHIBIT DATASETS #### #freq1_INHIBIT = [1, 0, 1, 1, 6, 0, 2, 0, 0, 4, 0, 11, 3, 0, 1, 0] # difficult INHIBIT alternative x_2 #[1, 0, 1, 1, 4, 0, 1, 0, 1, 3, 1, 7, 7, 0, 2, 1] #[4, 0, 1, 1, 4, 0, 2, 0, 0, 4, 0, 10, 2, 1, 1, 1] #freq2_INHIBIT = [2, 1, 2, 1, 1, 0, 1, 0, 1, 4, 0, 5, 4, 0, 2, 1] # medium INHIBIT #freq3_INHIBIT = [3, 0, 2, 1, 4, 1, 3, 1, 1, 3, 0, 4, 3, 0, 2, 0] # easy INHIBIT # [3, 1, 4, 0, 3, 1, 3, 1, 0, 3, 1, 2, 2, 1, 3, 0] --- [4, 0, 3, 0, 5, 0, 4, 1, 1, 4, 0, 4, 3, 0, 2, 0] , [4, 0, 4, 0, 4, 0, 4, 0, 1, 2, 0, 3, 3, 0, 2, 0], [4, 0, 4, 0, 2, 0, 2, 0, 1, 3, 0, 3, 4, 0, 4, 1] #freq4_INHIBIT = [3, 0, 1, 0, 2, 1, 1, 1, 0, 3, 0, 6, 7, 0, 3, 0] , [4, 0, 1, 0, 1, 0, 1, 1, 0, 3, 0, 9, 8, 1, 3, 1] # Difficult alternative EITHER ## NOR DATASETS #### #freq1_NOR = [0, 4, 0, 8, 2, 1, 1, 1, 4, 0, 2, 1, 5, 0, 3, 0] # NOR -- difficult [1, 2, 0, 5, 6, 0, 2, 1, 1, 0, 1, 1, 7, 0, 3, 1], [1, 2, 0, 8, 1, 0, 0, 0, 6, 0, 1, 1, 6, 0, 2, 0] alternative x_1 (x_2) #freq2_NOR = [1, 1, 0, 1, 7, 0, 2, 0, 4, 0, 2, 0, 7, 1, 2, 1] # NOR -- diffucult prior -- The easy version is [0, 2, 0, 3, 4, 0, 4, 1, 5, 0, 5, 0, 4, 0, 3, 0] #freq3_NOR = [1, 3, 0, 4, 4, 0, 3, 0, 4, 0, 4, 1, 2, 0, 2, 0] # NOR -- easy [1, 4, 0, 4, 4, 0, 3, 1, 4, 0, 3, 1, 3, 0, 3, 1], [0, 4, 0, 5, 3, 0, 3, 0, 3, 0, 2, 0, 3, 0, 3, 1] # bundle each case together with its frequencies and then list all bundles to be used # The number of rounds for each subsession must be equal to the number of bundles in this list. # The list will be shuffled at a participant level and that order of cases will be shown in all parts #case_list = [[case2, freq2, 'AND'], # [case2, freq3, 'OR'], # [case2, freq6, 'EITHER'], # [case2, freq9, 'JOINT'], # [case2, freq15, 'INHIBIT'], #] case_list = [[case2, freq1, 'AND'], [case2, freq5, 'OR'], [case2, freq7, 'EITHER'], [case2, freq9_A, 'JOINT'], [case2, freq12, 'INHIBIT'], ] # shuffle the bundles of [case, freq] to determine the order in which they are shown to the participant. # the ordered list is saved at the participant level so participant variables should be downloaded to get it np.random.shuffle(case_list) participant.order_names = [case_list[i][-1] for i in range(len(case_list))] participant.cases_ordered = [case_list[i][0:2] for i in range(len(case_list))] player.case_order = str(participant.cases_ordered) class Code(Page): form_model = 'player' form_fields = ['ID_subject'] @staticmethod def is_displayed(player: Player): return player.round_number == 1 class Load(Page): timeout_seconds = 1 @staticmethod def before_next_page(player: Player, timeout_happened): r = player.round_number participant = player.participant # get the case corresponding to the round case = participant.cases_ordered[r - 1] player.machine_name = participant.order_names[r - 1] player.order_names_json = json.dumps(participant.order_names) n_lights_case = len(case[0][0]) if n_lights_case == 4: # 2 lights original_color = random.randint(0, 1) if C.RANDOMIZE_COLORS else 1 else: # 3 lights: pick one of 6 color permutations original_color = random.randint(0, 5) if C.RANDOMIZE_COLORS else 0 player.original_color = original_color participant.original_color.append(original_color) # get the html code for the table (this part also generates the repetitions of each row) if n_lights_case == 4: # 2 lights: original_color is 0/1 if original_color == 1: evaluated = html_table_freqs_original(case[0], case[1]) else: evaluated = html_table_freqs_flipped(case[0], case[1]) else: # 3 lights: original_color is permutation index 0-5 evaluated = html_table_freqs_original(case[0], case[1], color_perm=original_color) matrix = evaluated[1] # save the case matrix as a string for the player. The cases are also saved as matrices at the participant level player.case = str(matrix) player.table = evaluated[0] participant.current_case = matrix class Machine(Page): form_model = 'player' form_fields = ['notes'] timeout_seconds = C.MACHINE_TIMEOUT @staticmethod def vars_for_template(player: Player): # called when the page is rendered if player.field_maybe_none('page_load_ts') is None: player.page_load_ts = time.time() r = player.round_number return dict(table=player.table, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): # If the timeout fired and notes weren't submitted, store an empty string if timeout_happened and not player.notes: player.notes = '' participant = player.participant all_notes = participant.notes all_notes.append(player.notes) participant.notes = all_notes participant.realized_cases.append(participant.current_case) ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_on_page = time.time() - ts @staticmethod def error_message(player, values): if len(values['notes'].strip()) == 0: return 'Please write something before continuing.' class MyWaitPage(WaitPage): # this wait page is not necessary. It is here to get everyone up to the same point and possibly shorten the necessary # wait between stages 2 and 3. @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS title_text = "End of Part 1" body_text = "You have reached the end of Part 1. " \ "Before we move on to Part 2 we will wait for the other participants to finish Part 1" wait_for_all_groups = True class Transition(Page): @staticmethod def vars_for_template(player: Player): participant = player.participant # original_color was appended in Load.before_next_page player.original_color = participant.original_color[player.round_number - 1] return dict(r=player.round_number) class Comprehension(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 form_model = 'player' form_fields = ['cq1', 'cq2', 'cq3', 'cq4', 'cq5'] def error_message(self, values): solutions = dict(cq1=1, cq2=2, cq3=3, cq4=2, cq5=3) incorrect = [ field for field, correct in solutions.items() if values[field] != correct ] # Store total wrong for THIS attempt in participant.vars # accumulate prev = self.participant.vars.get('num_wrong_attempt', 0) self.participant.vars['num_wrong_attempt'] = prev + len(incorrect) # Store incorrect fields for highlighting self.participant.vars['incorrect_fields'] = incorrect # Store submitted values so they can be restored on re-render self.participant.vars['cq_previous'] = { field: values[field] for field in solutions } if incorrect: return "Some answers are incorrect. Please review the highlighted questions." def vars_for_template(self): import json return dict( incorrect_fields=self.participant.vars.get('incorrect_fields', []), cq_previous=json.dumps(self.participant.vars.get('cq_previous', {})), ) def before_next_page(self, timeout_happened): """ When the participant finally passes (no error_message), copy the last num_wrong_attempt into the persistent player field. """ # If they never got anything wrong, this will be 0 or missing num_wrong_attempt = self.participant.vars.get('num_wrong_attempt', 0) self.num_wrong = num_wrong_attempt class Guess1(Page): # All 10 pages follow the same structure as this one. Only the code in Page1 has comments (sorry) form_model = 'player' form_fields = ['guess1'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): player.page_load_ts = time.time() r = player.round_number participant = player.participant # get notes from part 1 all_notes = participant.notes notes = all_notes[r-1] # Get original case definition and frequencies for this round case_def, freq = participant.cases_ordered[r - 1] n_lights = len(case_def[0]) player.n_lights = n_lights # Sample 16 rows: 2 per light config, no two consecutive with the same config ordered_rows = sample_balanced_guess_rows(case_def, freq, C.GUESSES_PER_CONFIG) # Save each sampled row as a string (numpy format for parsing compatibility) player.row1 = str(np.array(ordered_rows[0])) player.row2 = str(np.array(ordered_rows[1])) player.row3 = str(np.array(ordered_rows[2])) player.row4 = str(np.array(ordered_rows[3])) player.row5 = str(np.array(ordered_rows[4])) player.row6 = str(np.array(ordered_rows[5])) player.row7 = str(np.array(ordered_rows[6])) player.row8 = str(np.array(ordered_rows[7])) player.row9 = str(np.array(ordered_rows[8])) player.row10 = str(np.array(ordered_rows[9])) player.row11 = str(np.array(ordered_rows[10])) player.row12 = str(np.array(ordered_rows[11])) player.row13 = str(np.array(ordered_rows[12])) player.row14 = str(np.array(ordered_rows[13])) player.row15 = str(np.array(ordered_rows[14])) player.row16 = str(np.array(ordered_rows[15])) # Render the first row row = list(ordered_rows[0]) if n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, n_lights) else: evaluated = html_table_flipped(row, n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r, lights=n_lights) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_1 = time.time() - ts if timeout_happened and player.field_maybe_none('guess1') is None: player.guess1 = random.randint(0, 1) row = player.row1[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 # exclude Others and Sound config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess1) participant.guess_configs.append(str(config)) if player.guess1 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess2(Page): form_model = 'player' form_fields = ['guess2'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row2[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_2 = time.time() - ts if timeout_happened and player.field_maybe_none('guess2') is None: player.guess2 = random.randint(0, 1) row = player.row2[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess2) participant.guess_configs.append(str(config)) if player.guess2 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess3(Page): form_model = 'player' form_fields = ['guess3'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row3[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_3 = time.time() - ts if timeout_happened and player.field_maybe_none('guess3') is None: player.guess3 = random.randint(0, 1) row = player.row3[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess3) participant.guess_configs.append(str(config)) if player.guess3 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess4(Page): form_model = 'player' form_fields = ['guess4'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row4[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_4 = time.time() - ts if timeout_happened and player.field_maybe_none('guess4') is None: player.guess4 = random.randint(0, 1) row = player.row4[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess4) participant.guess_configs.append(str(config)) if player.guess4 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess5(Page): form_model = 'player' form_fields = ['guess5'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row5[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_5 = time.time() - ts if timeout_happened and player.field_maybe_none('guess5') is None: player.guess5 = random.randint(0, 1) row = player.row5[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess5) participant.guess_configs.append(str(config)) if player.guess5 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess6(Page): form_model = 'player' form_fields = ['guess6'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row6[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_6 = time.time() - ts if timeout_happened and player.field_maybe_none('guess6') is None: player.guess6 = random.randint(0, 1) row = player.row6[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess6) participant.guess_configs.append(str(config)) if player.guess6 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess7(Page): form_model = 'player' form_fields = ['guess7'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row7[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_7 = time.time() - ts if timeout_happened and player.field_maybe_none('guess7') is None: player.guess7 = random.randint(0, 1) row = player.row7[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess7) participant.guess_configs.append(str(config)) if player.guess7 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess8(Page): form_model = 'player' form_fields = ['guess8'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row8[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_8 = time.time() - ts if timeout_happened and player.field_maybe_none('guess8') is None: player.guess8 = random.randint(0, 1) row = player.row8[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess8) participant.guess_configs.append(str(config)) if player.guess8 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess9(Page): form_model = 'player' form_fields = ['guess9'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row9[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_9 = time.time() - ts if timeout_happened and player.field_maybe_none('guess9') is None: player.guess9 = random.randint(0, 1) row = player.row9[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess9) participant.guess_configs.append(str(config)) if player.guess9 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess10(Page): form_model = 'player' form_fields = ['guess10'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row10[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_10 = time.time() - ts if timeout_happened and player.field_maybe_none('guess10') is None: player.guess10 = random.randint(0, 1) row = player.row10[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess10) participant.guess_configs.append(str(config)) if player.guess10 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess11(Page): form_model = 'player' form_fields = ['guess11'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row11[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_11 = time.time() - ts if timeout_happened and player.field_maybe_none('guess11') is None: player.guess11 = random.randint(0, 1) row = player.row11[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess11) participant.guess_configs.append(str(config)) if player.guess11 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess12(Page): form_model = 'player' form_fields = ['guess12'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row12[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_12 = time.time() - ts if timeout_happened and player.field_maybe_none('guess12') is None: player.guess12 = random.randint(0, 1) row = player.row12[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess12) participant.guess_configs.append(str(config)) if player.guess12 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess13(Page): form_model = 'player' form_fields = ['guess13'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row13[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_13 = time.time() - ts if timeout_happened and player.field_maybe_none('guess13') is None: player.guess13 = random.randint(0, 1) row = player.row13[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess13) participant.guess_configs.append(str(config)) if player.guess13 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess14(Page): form_model = 'player' form_fields = ['guess14'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row14[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_14 = time.time() - ts if timeout_happened and player.field_maybe_none('guess14') is None: player.guess14 = random.randint(0, 1) row = player.row14[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess14) participant.guess_configs.append(str(config)) if player.guess14 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess15(Page): form_model = 'player' form_fields = ['guess15'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row15[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_15 = time.time() - ts if timeout_happened and player.field_maybe_none('guess15') is None: player.guess15 = random.randint(0, 1) row = player.row15[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess15) participant.guess_configs.append(str(config)) if player.guess15 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) class Guess16(Page): form_model = 'player' form_fields = ['guess16'] @staticmethod def get_timeout_seconds(player: Player): return C.GUESS_TIMEOUT or None @staticmethod def vars_for_template(player: Player): r = player.round_number participant = player.participant all_notes = participant.notes notes = all_notes[r-1] player.page_load_ts = time.time() row = player.row16[1:-1] row = [int(s) for s in row.split(' ')] if player.n_lights == 4: # 2 lights if player.original_color == 1: evaluated = html_table_original(row, player.n_lights) else: evaluated = html_table_flipped(row, player.n_lights) else: # 3 lights: use permutation evaluated = html_table_original(row, player.n_lights, color_perm=player.original_color) return dict(notes=notes, table=evaluated, round=r) @staticmethod def before_next_page(player: Player, timeout_happened): ts = player.field_maybe_none('page_load_ts') if ts is not None: player.time_guess_16 = time.time() - ts if timeout_happened and player.field_maybe_none('guess16') is None: player.guess16 = random.randint(0, 1) r = player.round_number row = player.row16[1:-1] participant = player.participant row = [int(s) for s in row.split(' ')] n_cols = len(row) config_cols = n_cols - 2 config = tuple(row[:config_cols]) participant.sound.append(row[-1]) participant.guess_values.append(player.guess16) participant.guess_configs.append(str(config)) if player.guess16 == row[-1]: participant.guesses.append(1) else: participant.guesses.append(0) gpg = C.N_GUESSES player.guess_configs_json = json.dumps(participant.guess_configs[(r - 1) * gpg: r * gpg]) class Post_Guesses_Difficulty(Page): form_model = 'player' form_fields = ['difficulty', 'difficulty_certainty'] def vars_for_template(self): return dict( certainty_values=range(0, 101, 5) # 0,5,10,...,100 ) class Post_Guesses_Confidence(Page): form_model = 'player' form_fields = ['predicted_correct_self', 'certainty'] def vars_for_template(self): return dict( certainty_values=range(0, 101, 5) # 0,5,10,...,100 ) class Explicit(Page): form_model = 'player' form_fields = ['explanation'] @staticmethod def vars_for_template(player: Player): participant = player.participant r = player.round_number # if you want to reference their notes for this machine: notes = participant.notes[r - 1] # because you kept notes # if you want to say which machine (using your order_names from Instructions): machine_name = participant.order_names[r - 1] return dict( round=r, notes=notes, machine_name=machine_name, ) class Explicit_Advice(Page): form_model = 'player' form_fields = ['bonus_message_effect'] def error_message(self, values): correct = "It may help another participant make better predictions and earn me a bonus" if values['bonus_message_effect'] != correct: return "Your answer is incorrect. Please try again." class Explicit_Advice_Typing(Page): form_model = 'player' form_fields = ['advice_text'] # prevent empty answers def error_message(self, values): if len(values['advice_text'].strip()) == 0: return "Please write something before continuing." @staticmethod def vars_for_template(player: Player): participant = player.participant r = player.round_number # if you want to reference their notes for this machine: notes = participant.notes[r - 1] # because you kept notes # if you want to say which machine (using your order_names from Instructions): machine_name = participant.order_names[r - 1] return dict( round=r, notes=notes, machine_name=machine_name, ) class Explicit_Advice_Confidence(Page): form_model = 'player' form_fields = ['predicted_correct_other'] class PredictionStrategy(Page): MIN_CHARS = 20 def is_displayed(self): return self.round_number == C.NUM_ROUNDS form_model = 'player' form_fields = ['prediction_strategy', 'difficulty', 'difficulty_certainty'] def vars_for_template(self): return dict( certainty_values=range(0, 101, 5), difficulty_values=range(1, 11) ) @staticmethod def prediction_strategy_error_message(_player, value): min_chars = PredictionStrategy.MIN_CHARS if not value or len(value) < min_chars: return f'Please write at least {min_chars} characters.' class FinalComments(Page): MIN_CHARS = 20 form_model = 'player' form_fields = ['final_comments'] @staticmethod def is_displayed(player: Player): return player.round_number == C.NUM_ROUNDS @staticmethod def final_comments_error_message(_player, value): min_chars = FinalComments.MIN_CHARS if not value or len(value) < min_chars: return f'Please write at least {min_chars} characters.' class Redirect(Page): pass class NoAI(Page): @staticmethod def is_displayed(player: Player): return player.round_number == 1 class ResultsWaitPage(WaitPage): pass page_sequence = [ Code, Introduction, NoAI, #Instructions1, #Instructions1_2, Instructions1_2a, #Instructions_Summary, Comprehension, Instructions, Load, Machine, Transition, Guess1, Guess2, Guess3, Guess4, Guess5, Guess6, Guess7, Guess8, Guess9, Guess10, Guess11, Guess12, Guess13, Guess14, Guess15, Guess16, Post_Guesses_Confidence, #Explicit_Advice, Explicit_Advice_Typing, Explicit_Advice_Confidence, PredictionStrategy, FinalComments, # Redirect # For subjects to be authomatically redirected to Prolific at the end of the experiment. Set the URL in settings.py ]