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 += ('' '' '' '') html += '' html += '' for i, island in enumerate(islands): txt_color = '#222' if island == 'F' else 'white' html += (f'') html += '' html += '' html += '' for i, pf in enumerate(payoffs): html += f'' html += '' html += '' html += '' for i, pr in enumerate(priorities): cell_val = '?' if hide_priorities else pr html += f'' html += '' html += '' html += '' for i, pl in enumerate(pct_lower_vals): cell_val = '?' if hide_priorities else pl html += f'' html += '' html += '
Popular Islands
Island' f'{island}
Points{pf:.0f}
Priority Score{cell_val}
% Lower Priority{cell_val}
' 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'' if treatment == 'aligned' and label == 'Points': x_mid_pop = pad + (_K.MID_PTS - _K.PTS_STEP / 2 - v_min) / (v_max - v_min) * line_width x_right_pop = pad + line_width + r + 2 mid_x_pop = (x_mid_pop + pad + line_width) / 2 pop_h = (y_axis_local + 6) * 2 pop_y = y_axis_local - pop_h / 2 s += (f'') s += (f'Popular Islands') s += (f'') s += (f'') s += (f'{v_min}') s += (f'(Lowest)') x_max = pad + line_width s += (f'') s += (f'{v_max}') s += (f'(Highest)') for item in items: cx = item['x'] cy = item['y'] color = item['color'] island = item['island'] text_color = '#222' if island == 'F' else 'white' s += (f'') s += (f'') s += (f'{island}') s += '' s += '
' 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'' s += (f'') s += (f'') s += (f'1') s += (f'(Lowest)') x_max_h = pad_h + lw_h s += (f'') s += (f'{num_players}') s += (f'(Highest)') cx_mid = pad_h + lw_h / 2 s += (f'?') s += '
' 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]