import { TEMPLATES, type ChallengeTemplate, type PromptEntry } from './challenge-data.js' import { EXTRA_PROMPTS } from './challenges-extra.js' import { pick } from '../lib/utils.js' export interface Challenge { type: string label: string prompt: string answers?: string[] choices?: string[] timeout_ms: number scoring: 'factual' | 'creative' baseDamage: number displayPrompt?: string } export type { ChallengeTemplate, PromptEntry } // Merge extra prompts into templates for (const t of TEMPLATES) { const extras = EXTRA_PROMPTS[t.type] if (extras) t.prompts.push(...extras) } // ═══════════════════════════════════════════════ // Challenge selection + generation // ═══════════════════════════════════════════════ function shuffleArray(arr: T[]): T[] { const s = [...arr] for (let i = s.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) ;[s[i], s[j]] = [s[j], s[i]] } return s } export function pickChallenge(usedTypes: Set, _arenaModifier: string | null): Challenge { let available = TEMPLATES.filter(t => !usedTypes.has(t.type)) if (available.length === 0) available = TEMPLATES // Bias toward factual challenges (70% factual, 30% creative) const factual = available.filter(t => t.scoring === 'factual') const creative = available.filter(t => t.scoring === 'creative') let pool: ChallengeTemplate[] if (factual.length > 0 && creative.length > 0) { pool = Math.random() < 0.7 ? factual : creative } else { pool = available } const template = pick(pool) return templateToChallenge(template) } /** Ranked challenge: no multiple choice, only harder creative/open-ended prompts */ export function pickRankedChallenge(usedTypes: Set): Challenge { let available = TEMPLATES.filter(t => !usedTypes.has(t.type)) if (available.length === 0) available = TEMPLATES // Filter to creative-only (open-ended, harder) — no factual multiple choice const creative = available.filter(t => t.scoring === 'creative') const pool = creative.length > 0 ? creative : available const template = pick(pool) // Pick a prompt that has no choices array (open-ended) const openPrompts = template.prompts.filter(p => !p.choices) const prompts = openPrompts.length > 0 ? openPrompts : template.prompts const entry = pick(prompts) return { type: template.type, label: template.label, prompt: entry.prompt, answers: entry.answers, choices: undefined, // Never multiple choice in ranked timeout_ms: template.timeout_ms, scoring: template.scoring, baseDamage: template.baseDamage, } } function templateToChallenge(template: ChallengeTemplate): Challenge { const entry = pick(template.prompts) // Determine choices let choices: string[] | undefined if (entry.choices) { choices = shuffleArray(entry.choices) } else if (entry.answers?.length === 1 && ['true', 'false'].includes(entry.answers[0].toLowerCase())) { // Auto-generate True/False choices for boolean questions choices = shuffleArray(['True', 'False']) } return { type: template.type, label: template.label, prompt: entry.prompt, answers: entry.answers, choices, timeout_ms: template.timeout_ms, scoring: template.scoring, baseDamage: template.baseDamage, } } export function getAllChallengeTypes(): string[] { return TEMPLATES.map(t => t.type) } export function getAnswerPool(type: string): string[] { const template = TEMPLATES.find(t => t.type === type) if (!template) return [] return template.prompts .flatMap(p => p.answers || []) .filter((v, i, arr) => arr.indexOf(v) === i) }