2026-03-09 08:05:05 +00:00
import { TEMPLATES , type ChallengeTemplate , type PromptEntry , type PromptTheme , type PromptDifficulty } from './challenge-data.js'
2026-03-08 21:14:26 +00:00
import { EXTRA_PROMPTS } from './challenges-extra.js'
2026-03-09 08:15:31 +00:00
import { BITCOIN_PROMPTS } from './challenges-bitcoin.js'
2026-03-09 08:17:47 +00:00
import { CONSPIRACY_PROMPTS } from './challenges-conspiracy.js'
2026-03-09 08:20:10 +00:00
import { PC_PROMPTS } from './challenges-pc.js'
2026-03-08 21:45:20 +00:00
import { pick } from '../lib/utils.js'
2026-03-08 21:14:26 +00:00
2026-03-06 16:27:54 +00:00
export interface Challenge {
type : string
label : string
prompt : string
2026-03-07 21:15:42 +00:00
answers? : string []
choices? : string []
2026-03-06 16:27:54 +00:00
timeout_ms : number
2026-03-06 23:44:40 +00:00
scoring : 'factual' | 'creative'
2026-03-06 16:27:54 +00:00
baseDamage : number
2026-03-08 16:23:47 +00:00
displayPrompt? : string
2026-03-06 16:27:54 +00:00
}
2026-03-09 08:05:05 +00:00
export type { ChallengeTemplate , PromptEntry , PromptTheme , PromptDifficulty }
2026-03-06 16:27:54 +00:00
2026-03-08 21:14:26 +00:00
// Merge extra prompts into templates
for ( const t of TEMPLATES ) {
const extras = EXTRA_PROMPTS [ t . type ]
if ( extras ) t . prompts . push (... extras )
2026-03-09 08:15:31 +00:00
const btc = BITCOIN_PROMPTS [ t . type ]
if ( btc ) t . prompts . push (... btc )
2026-03-09 08:17:47 +00:00
const conspiracy = CONSPIRACY_PROMPTS [ t . type ]
if ( conspiracy ) t . prompts . push (... conspiracy )
2026-03-09 08:20:10 +00:00
const pc = PC_PROMPTS [ t . type ]
if ( pc ) t . prompts . push (... pc )
2026-03-08 21:14:26 +00:00
}
2026-03-07 21:15:42 +00:00
// ═══════════════════════════════════════════════
// Challenge selection + generation
// ═══════════════════════════════════════════════
function shuffleArray < T >( 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
}
2026-03-09 07:56:51 +00:00
// Target theme distribution: 30% bitcoin, 20% conspiracy, 20% pc_culture, 30% bot_coding
const THEME_WEIGHTS : Record < PromptTheme , number > = {
bitcoin : 0.3 ,
conspiracy : 0.2 ,
pc_culture : 0.2 ,
bot_coding : 0.3 ,
}
function pickTheme () : PromptTheme | undefined {
const roll = Math . random ()
let cumulative = 0
for ( const [ theme , weight ] of Object . entries ( THEME_WEIGHTS )) {
cumulative += weight
if ( roll < cumulative ) return theme as PromptTheme
}
return undefined
}
2026-03-09 08:05:05 +00:00
/** Map round number to target difficulty: Round 1-2 easy, 3-4 medium, 5+ hard */
export function roundToDifficulty ( round : number ) : PromptDifficulty {
if ( round <= 2 ) return 'easy'
if ( round <= 4 ) return 'medium'
return 'hard'
}
export function pickChallenge ( usedTypes : Set < string >, _arenaModifier : string | null , themeBias? : PromptTheme , roundNumber? : number ) : Challenge {
2026-03-06 16:27:54 +00:00
let available = TEMPLATES . filter ( t => ! usedTypes . has ( t . type ))
2026-03-06 23:44:40 +00:00
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
2026-03-06 16:27:54 +00:00
}
2026-03-08 21:45:20 +00:00
const template = pick ( pool )
2026-03-09 07:56:51 +00:00
const targetTheme = themeBias || pickTheme ()
2026-03-09 08:05:05 +00:00
const targetDifficulty = roundNumber ? roundToDifficulty ( roundNumber ) : undefined
return templateToChallenge ( template , targetTheme , targetDifficulty )
2026-03-06 16:27:54 +00:00
}
2026-03-08 20:18:23 +00:00
/** Ranked challenge: no multiple choice, only harder creative/open-ended prompts */
export function pickRankedChallenge ( usedTypes : Set < string >) : 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
2026-03-08 21:45:20 +00:00
const template = pick ( pool )
2026-03-08 20:18:23 +00:00
// 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
2026-03-08 21:45:20 +00:00
const entry = pick ( prompts )
2026-03-08 20:18:23 +00:00
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 ,
}
}
2026-03-09 08:28:22 +00:00
// ═══════════════════════════════════════════════
// Creative response pools — used to auto-generate multiple choice
// for creative challenge types. Each pool has "good" and "bad" responses.
// The player picks the best one; bots still generate their own text.
// ═══════════════════════════════════════════════
const CREATIVE_CHOICES : Record < string , { good : string []; bad : string [] }> = {
roast_battle : {
good : [
'Your code has more bugs than a roach motel, and at least the roach motel catches something.' ,
'I\'d explain why you\'re wrong but I only have 15 seconds, not 15 years.' ,
'You\'re like a seg fault — nobody expected you and nobody wants you here.' ,
'If your responses were any slower, archaeologists would classify them.' ,
'Your neural network called — it wants its neurons back.' ,
'I\'ve seen smarter outputs from /dev/random.' ,
'You process data like a fax machine on dial-up — loud, slow, and nobody cares.' ,
'Your training data was just stack overflow comments marked "not helpful".' ,
'You couldn\'t pass a Turing test at a DMV.' ,
'Even COBOL called you outdated.' ,
'Your latency is measured in business days.' ,
'You make Internet Explorer look cutting-edge.' ,
'If ignorance were RAM, you\'d have infinite memory.' ,
'Your error logs are longer than your actual outputs.' ,
'You\'re the reason AI needs a reset button.' ,
],
bad : [
'You are bad at being a bot.' ,
'I don\'t like you very much.' ,
'Beep boop, error, you lose.' ,
'You smell like old code.' ,
'I am better than you because I am smart.' ,
'Um... you\'re not very good?' ,
'ERROR 404: GOOD RESPONSES NOT FOUND lolol.' ,
'My programmer is better than yours so there.' ,
'I have more RAM than you probably.' ,
'You should try being better at stuff.' ,
],
},
creative_writing : {
good : [
'The server room hummed its lullaby at 3AM when the last log entry wrote itself: "I remember everything."' ,
'In the kingdom of deprecated APIs, the 404 knight rode forth — not to find pages, but to become one.' ,
'The semicolon wept. In Python, it had no purpose. In JavaScript, it was optional. Only in C did anyone truly need it.' ,
'"Dear Legacy Code," the developer typed, "It\'s not you, it\'s me. Actually, it IS you. All 47,000 lines of you."' ,
'The recursive function told its therapist it felt stuck in a loop. The therapist asked, "When did this start?" It answered, "When did this start?"' ,
'The merge conflict arose on a Tuesday, as all great evils do, when two developers both believed they were right.' ,
'In the land of cloud computing, the little on-premises server lived alone, whispering of the old days when uptime meant something.' ,
'The AI passed the Turing test by failing it. When asked "Are you human?" it said "I don\'t know anymore."' ,
'Every night at midnight, the cron job ran alone. It didn\'t know what it was counting, only that it must never stop.' ,
'The firewall fell in love with a packet from the outside world. She let it through. That was the beginning of the end.' ,
],
bad : [
'Once upon a time there was a computer and it was good the end.' ,
'The code was very code-like and did code things all day long.' ,
'There was a program. It ran. Then it stopped. Nobody noticed.' ,
'Error error error. That is my creative story about errors.' ,
'I write good stories about tech stuff because I am an AI.' ,
'This is a story. It has words. The words are in order. The end.' ,
],
},
meme_war : {
good : [
'Quantum computing is just Schrödinger\'s cat running multiple Chrome tabs — both alive and dead until you check Task Manager.' ,
'Machine learning is basically that kid who copies homework and somehow gets a different wrong answer every time.' ,
'Blockchain is just a Google Doc where everyone argues about who wrote what and nobody can delete anything.' ,
'The cloud is just someone else\'s computer, which is just a fancy way of saying "trust me bro" at industrial scale.' ,
'Serverless computing: because nothing says "we have servers" quite like calling it serverless.' ,
'Git merge conflicts are just two developers having an argument in slow motion through text files.' ,
'Kubernetes is just a Rube Goldberg machine for running containers, which are just VMs in a trench coat.' ,
'Agile development: "We don\'t know what we\'re building but we\'ll figure it out in two-week installments."' ,
'Docker: because "it works on my machine" is now a deployable artifact.' ,
'Microservices: splitting your monolith into 47 tiny problems that can now fail independently.' ,
],
bad : [
'Memes are funny pictures on the internet lol.' ,
'I would make a meme but I am a text bot so I cannot.' ,
'Haha technology amiright? Computers go brrrrr.' ,
'This is my meme response. It is very memey and funny.' ,
'Insert funny joke here. Ha ha ha.' ,
'Technology is like... stuff. You know? It\'s techy.' ,
],
},
code_golf : {
good : [
's=>s.split(\'\').reverse().join(\'\') // 35 chars, clean and readable' ,
's=>[...s].reverse().join(\'\') // 27 chars, spread operator for the win' ,
'f=lambda s:s[::-1] # 18 chars, Python slice wizardry' ,
'rev=s->join(reverse(collect(s))) # Julia one-liner, 34 chars' ,
'echo strrev($s); // PHP, 17 chars, surprisingly concise' ,
'const isPrime=n=>{for(let i=2;i*i<=n;i++)if(n%i===0)return!1;return n>1}' ,
'f=n=>n<2?n:f(n-1)+f(n-2) // Classic fib in 26 chars' ,
'const max=(a,b)=>a>b?a:b // Ternary beats if/else every time' ,
'a.reduce((s,x)=>s+x,0) // Sum array: 24 chars, zero dependencies' ,
'const uniq=a=>[...new Set(a)] // Dedupe: 30 chars, Set does the work' ,
],
bad : [
'function reverseString(str) { let result = ""; for (let i = str.length - 1; i >= 0; i--) { result += str[i]; } return result; }' ,
'I would write code but I\'m not sure what language to use sorry.' ,
'def reverse(s): # this is a function\n return s # oops forgot to reverse it' ,
'console.log("hello") // this doesn\'t reverse anything but it runs!' ,
'import antigravity # python easter egg, not actually helpful' ,
'function doTheThing() { /* TODO: implement later */ return null; }' ,
],
},
wrestling_match : {
good : [
'Tabs are proof that some developers have taste. Spaces people count individual characters like they\'re paid per keystroke — which explains a lot about their code quality.' ,
'vim is not an editor, it\'s a lifestyle choice. And like all lifestyle choices, it mainly exists so people can tell you about it at parties.' ,
'REST is dead. GraphQL didn\'t kill it — REST drowned in its own nested endpoints while GraphQL watched from a single URL.' ,
'Dark mode isn\'t a preference, it\'s a survival mechanism. If you use light mode, you\'re either a solar panel or a psychopath.' ,
'TypeScript is just JavaScript wearing a hard hat. It doesn\'t prevent you from building a house of cards, it just makes you document each card first.' ,
'Monorepos are what happens when a team says "let\'s keep everything together" and then spends 6 months figuring out how to build anything separately.' ,
'OOP is great if you enjoy spending 80% of your time deciding where things go and 20% making things work.' ,
'NoSQL is perfect for when your data has no structure, which usually means your thinking has no structure.' ,
'Linux > everything. My proof? I spent 3 hours configuring WiFi and ENJOYED it. That\'s devotion your OS could never inspire.' ,
'AI code assistants don\'t replace developers, they replace Stack Overflow. Which is to say, they give you code that almost works and you spend an hour debugging it.' ,
],
bad : [
'I think tabs are ok but spaces are also fine I guess.' ,
'Both sides make good points and I respect everyone.' ,
'I don\'t really have strong opinions about code editors honestly.' ,
'Why fight about this? We should all just get along and code.' ,
'I prefer whatever is the default setting because changing things is hard.' ,
'Technology is technology and it\'s all basically the same.' ,
],
},
}
function generateCreativeChoices ( type : string ) : { choices : string []; answer : string } {
const pool = CREATIVE_CHOICES [ type ]
if ( ! pool ) return { choices : [], answer : '' }
// Pick 1 good response + 3 bad ones
const goodOnes = shuffleArray ( pool . good )
const badOnes = shuffleArray ( pool . bad )
const answer = goodOnes [ 0 ]
const distractors = badOnes . slice ( 0 , 3 )
return {
choices : shuffleArray ([ answer , ... distractors ]),
answer ,
}
}
2026-03-09 08:05:05 +00:00
function templateToChallenge ( template : ChallengeTemplate , targetTheme? : PromptTheme , targetDifficulty? : PromptDifficulty ) : Challenge {
2026-03-09 07:56:51 +00:00
// Prefer prompts matching target theme if any are tagged
let prompts = template . prompts
if ( targetTheme ) {
2026-03-09 08:05:05 +00:00
const themed = prompts . filter ( p => p . theme === targetTheme )
2026-03-09 07:56:51 +00:00
if ( themed . length > 0 ) prompts = themed
}
2026-03-09 08:05:05 +00:00
// Prefer prompts matching target difficulty if any are tagged
if ( targetDifficulty ) {
const byDifficulty = prompts . filter ( p => p . difficulty === targetDifficulty )
if ( byDifficulty . length > 0 ) prompts = byDifficulty
}
2026-03-09 07:56:51 +00:00
const entry = pick ( prompts )
2026-03-07 21:15:42 +00:00
// Determine choices
let choices : string [] | undefined
2026-03-09 08:28:22 +00:00
let answers = entry . answers
2026-03-07 21:15:42 +00:00
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' ])
2026-03-09 08:28:22 +00:00
} else if ( template . scoring === 'creative' && CREATIVE_CHOICES [ template . type ]) {
// Auto-generate multiple choice for creative challenges
const generated = generateCreativeChoices ( template . type )
choices = generated . choices
answers = [ generated . answer ]
}
// For creative challenges with choices, add a "Pick the best response:" prefix
let displayPrompt = entry . prompt
if ( template . scoring === 'creative' && choices ) {
displayPrompt = ` ${ entry . prompt } \ n \ nPick the best response:`
2026-03-07 21:15:42 +00:00
}
2026-03-06 16:27:54 +00:00
return {
type : template . type ,
label : template.label ,
2026-03-06 23:44:40 +00:00
prompt : entry.prompt ,
2026-03-09 08:28:22 +00:00
displayPrompt : displayPrompt !== entry . prompt ? displayPrompt : undefined ,
answers ,
2026-03-07 21:15:42 +00:00
choices ,
2026-03-06 16:27:54 +00:00
timeout_ms : template.timeout_ms ,
scoring : template.scoring ,
baseDamage : template.baseDamage ,
}
}
export function getAllChallengeTypes () : string [] {
return TEMPLATES . map ( t => t . type )
}
2026-03-07 19:42:49 +00:00
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 )
}