import math
import all_constants as _K
ISLAND_COLORS = {
'A': ('rgb(21, 96, 130)', 'white'),
'B': ('rgb(233, 113, 50)', 'white'),
'C': ('rgb(25, 107, 36)', 'white'),
'D': ('rgb(15, 158, 213)', 'white'),
'E': ('rgb(160, 43, 147)', 'white'),
'F': ('rgb(209, 209, 209)', '#222'),
}
def generate_candidate_graph(payoffs=[100, 200, 300, 250, 120, 150],
priorities=[100, 100, 100, 100, 100, 100],
treatment='random',
hide_priorities=False,
num_players=None):
"""Generate HTML for candidate payoff table and number lines."""
if num_players is None:
num_players = 150
bar_colors = ['rgb(21, 96, 130)', 'rgb(233, 113, 50)',
'rgb(25, 107, 36)', 'rgb(15, 158, 213)',
'rgb(160, 43, 147)', 'rgb(209, 209, 209)']
islands = ['A', 'B', 'C', 'D', 'E', 'F']
pct_lower_vals = []
for priority_now in priorities:
pct_lower = 100 * (priority_now - 1) / (num_players - 1)
if pct_lower != 100:
pct_lower_vals.append(f'{pct_lower:3.1f}%')
else:
pct_lower_vals.append(f'{pct_lower:3.0f}%')
def _pop_style(i):
parts = []
if treatment == 'aligned' and i in (0, 1):
parts.append('background-color:#f0f0f0')
if treatment == 'aligned' and i == 1:
parts.append('border-right: 3px solid black')
return (f' style="{"; ".join(parts)}"') if parts else ''
html = '
'
html += '
'
if treatment == 'aligned':
html += ('
'
'
'
'
Popular Islands
'
'
')
html += '
'
html += '
Island
'
for i, island in enumerate(islands):
txt_color = '#222' if island == 'F' else 'white'
html += (f'
'
f'{island}
')
html += '
'
html += '
'
html += '
Points
'
for i, pf in enumerate(payoffs):
html += f'
{pf:.0f}
'
html += '
'
html += '
'
html += '
Priority Score
'
for i, pr in enumerate(priorities):
cell_val = '?' if hide_priorities else pr
html += f'
{cell_val}
'
html += '
'
html += '
'
html += '
% Lower Priority
'
for i, pl in enumerate(pct_lower_vals):
cell_val = '?' if hide_priorities else pl
html += f'
{cell_val}
'
html += '
'
html += '
'
html += '
'
svg_width = 600
pad = 50
line_width = svg_width - 2 * pad
spacing = 20
r = 9
def make_svg(label, values, v_min, v_max):
items = []
for i, val in enumerate(values):
x = pad + (val - v_min) / (v_max - v_min) * line_width
items.append({'x': x, 'color': bar_colors[i], 'island': islands[i]})
groups = {}
for item in items:
key = round(item['x'])
groups.setdefault(key, []).append(item)
for key, group in groups.items():
n = len(group)
group.sort(key=lambda p: p['island'])
for j, p in enumerate(group):
p['y'] = -(n - 1) / 2 * spacing + j * spacing
min_sep = 2 * r + 2
for _ in range(300):
moved = False
for idx_a in range(len(items)):
for idx_b in range(idx_a + 1, len(items)):
a, b = items[idx_a], items[idx_b]
dx = abs(a['x'] - b['x'])
if dx >= min_sep:
continue
needed_dy = math.sqrt(max(0.0, min_sep ** 2 - dx ** 2))
dy = b['y'] - a['y']
if abs(dy) < needed_dy - 0.01:
extra = (needed_dy - abs(dy)) / 2
if dy >= 0:
a['y'] -= extra
b['y'] += extra
else:
a['y'] += extra
b['y'] -= extra
moved = True
if not moved:
break
min_y = min(item['y'] for item in items)
offset = (r + 8) - min_y
for item in items:
item['y'] += offset
y_axis_local = int(offset)
max_y = max(item['y'] for item in items)
svg_h = max(70, int(max_y) + r + 20)
s = f'
'
s += f'
{label}
'
s += f'
'
return s
html += make_svg('Points', payoffs, min(payoffs), max(payoffs))
if hide_priorities:
svg_width_h = 600
pad_h = 50
lw_h = svg_width_h - 2 * pad_h
y_ax_h = 17
svg_h_h = y_ax_h + 60
s = '
'
s += '
Priority Scores
'
s += f'
'
html += s
else:
html += make_svg('Priority Scores', priorities, 1, num_players)
return html
def get_treatment(player):
"""Return 'nocap' or 'cap' for this player's current round.
Odd uid → rounds 1-5 nocap, rounds 6-10 cap.
Even uid → rounds 1-5 cap, rounds 6-10 nocap.
"""
uid = player.participant.uid
first_block = player.round_number <= _K.NUM_ROUNDS_PER_BLOCK
is_odd = (uid % 2 == 1)
if first_block:
return 'nocap' if is_odd else 'cap'
else:
return 'cap' if is_odd else 'nocap'
def get_num_cap(player):
return _K.NUM_CAP_NOC if get_treatment(player) == 'nocap' else _K.NUM_CAP_CAP
def _recycle_uid(player):
uid = player.participant.uid
recycled = player.session.recycled_uids
if uid != 1000 and uid not in recycled:
player.session.recycled_uids = recycled + [uid]