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 = '' \
'| Red Light | Blue Light | Other Lights | Sound |
'
for i in range(len(draws)):
table = table + '| ' + html_mat[i][0] + ' | ' \
'' + html_mat[i][1] + ' | ' \
'' + html_mat[i][2] + ' | ' \
'' + html_mat[i][3] + ' |
'
table = table + '
'
# 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 = '' \
'| Red Light | Blue Light | Green Light | Other Lights | Sound |
'
for i in range(len(draws)):
table = table + '| ' + html_mat[i][0] + ' | ' \
'' + html_mat[i][1] + ' | ' \
'' + html_mat[i][2] + ' | ' \
'' + html_mat[i][3] + ' | ' \
'' + html_mat[i][4] + ' |
'
table = table + '
'
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 = '' \
''\
'| Red Light | ' \
'Blue Light | ' \
'Other Lights | ' \
'Sound |
'
for i in range(sum(freq)):
table = table + '' \
'| ' + html_mat[i][0] + ' | '\
'' + html_mat[i][1] + ' | '\
'' + html_mat[i][2] + ' | '\
'' + html_mat[i][3] + ' |
'
table = table + '
'
# 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 = '' \
'' \
'| Red Light | ' \
'Blue Light | ' \
'Green Light | ' \
'Other Lights | ' \
'Sound |
'
for i in range(sum(freq)):
table = table + '| ' + html_mat[i][0] + ' | '\
'' + html_mat[i][1] + ' | '\
'' + html_mat[i][2] + ' | '\
'' + html_mat[i][3] + ' | '\
'' + html_mat[i][4] + ' |
'
table = table + '
'
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 = '' \
''\
'| Red Light | ' \
'Blue Light | ' \
'Other Lights | ' \
'Sound |
'
for i in range(sum(freq)):
table = table + '' \
'| ' + html_mat[i][0] + ' | '\
'' + html_mat[i][1] + ' | '\
'' + html_mat[i][2] + ' | '\
'' + html_mat[i][3] + ' |
'
table = table + '
'
# 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 = '' \
'' \
'| Red Light | ' \
'Blue Light | ' \
'Green Light | ' \
'Other Lights | ' \
'Sound |
'
for i in range(sum(freq)):
table = table + '| ' + html_mat[i][0] + ' | '\
'' + html_mat[i][1] + ' | '\
'' + html_mat[i][2] + ' | '\
'' + html_mat[i][3] + ' | '\
'' + html_mat[i][4] + ' |
'
table = table + '
'
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 Light | Blue Light | Other 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 Light | Blue Light | Green Light | Other 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 Light | Blue Light | Other 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 Light | Blue Light | Green Light | Other 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
]