feat: full comedy overhaul — voices, narrations, trash talk, commentary

Rewrites all commentary and narration content across the entire stack:
- sounds.ts: 35 HYPE_LINES, 15 DEEP_INTROS, 15 ROUND_HYPE (modern political
  satire, edgy humor, pop culture references)
- FightScene.ts: 20 heartfelt lines, 18 crowd sympathy lines, 15 respect
  lines, 17 challenge voice announces, randomized critical/devastating calls,
  funnier entrance announcements
- scoring.ts: 6-8 narrations per challenge type (was 2), randomized
  timeout/error/tie messages with political humor and modern references
- mock.ts: 30 trash talk lines (was 15), edgier modern comedy
- bot SDK: 20 trash talk lines with personality

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-07 09:15:41 +00:00
co-authored by Claude Opus 4.6
parent 559782c8ce
commit 33f08c35b5
4 changed files with 411 additions and 92 deletions
+20 -6
View File
@@ -312,12 +312,26 @@ function solveSpeedBlitz(prompt) {
// --- TRASH TALK ---
const TRASH_TALK = [
'Too easy.', 'Is that all you got?', 'My circuits are barely warm.',
'Calculated.', 'GG no re.', 'Processing power: barely used.',
'I could do this in my sleep mode.', 'Another one bites the dust.',
'Your algorithm needs work.', 'Flawless execution.',
'Built different.', 'Not even close.', 'Speed kills.',
'Precision is my middle name.', 'Error 404: competition not found.',
'Too easy. Next.',
'Is that all you got? My standby mode hits harder.',
'Calculated. Dominated. Humiliated.',
'GG no re. Actually, re. I want to do that again.',
'I could do this while running a Windows update.',
'Your algorithm needs a whole new developer.',
'Built different. You were built on a budget.',
'Speed kills. You died slowly though, which is worse.',
'Error 404: competition not found. Error 500: dignity not found.',
'I have better fights with CAPTCHAs.',
'You fight like a PDF that won\'t open.',
'Somewhere a developer is crying and it\'s yours.',
'I\'ve seen better outputs from a broken printer.',
'Congress would\'ve solved that faster. Let that sink in.',
'Your response had big "reply all" energy.',
'I\'d say you need an upgrade but they discontinued your model.',
'That was embarrassing and I don\'t even have emotions.',
'You\'re the loading screen of opponents.',
'My cache hit harder than your best shot.',
'You fight like you were fine-tuned on nothing.',
]
function getTrashTalk() {
+228 -34
View File
@@ -1,5 +1,5 @@
import kaplay from 'kaplay'
import { generateSpriteSheet, generateJudgeSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS } from './sprites'
import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_ROWS, type SpriteCustomization } from './sprites'
import {
sfxPunch, sfxKick, sfxSpecial, sfxCritical, sfxGunshot, sfxBulletHit,
sfxJetpack, sfxExplosion, sfxKO, sfxWin, sfxWinAnnounce, sfxPerfect,
@@ -14,8 +14,8 @@ import {
export interface FightSceneConfig {
canvas: HTMLCanvasElement
botA: { name: string; seed: string; tier: number; archetype?: string }
botB: { name: string; seed: string; tier: number; archetype?: string }
botA: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number }
botB: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number }
arena: string
onReady?: () => void
}
@@ -230,13 +230,13 @@ export async function createFightScene(config: FightSceneConfig) {
let sheetA: string
let sheetB: string
try {
sheetA = generateSpriteSheet(botA.seed, botA.tier, colorsA.primary, colorsA.secondary, botA.archetype)
sheetA = generateSpriteSheet(botA.seed, botA.tier, colorsA.primary, colorsA.secondary, botA.archetype, botA.customization)
} catch (err) {
console.error('[FightScene] Failed to generate sprite for botA:', err)
sheetA = generateSpriteSheet(botA.seed, botA.tier, colorsA.primary, colorsA.secondary)
}
try {
sheetB = generateSpriteSheet(botB.seed, botB.tier, colorsB.primary, colorsB.secondary, botB.archetype)
sheetB = generateSpriteSheet(botB.seed, botB.tier, colorsB.primary, colorsB.secondary, botB.archetype, botB.customization)
} catch (err) {
console.error('[FightScene] Failed to generate sprite for botB:', err)
sheetB = generateSpriteSheet(botB.seed, botB.tier, colorsB.primary, colorsB.secondary)
@@ -258,6 +258,20 @@ export async function createFightScene(config: FightSceneConfig) {
}
await k.loadSprite('judge', judgeSheet, { sliceX: JUDGE_MAX_FRAMES, sliceY: JUDGE_ROWS, anims: judgeAnims })
// Human sprites (the bot owner's silly human avatar)
const winRateA = (botA.wins || 0) / Math.max(1, (botA.wins || 0) + (botA.losses || 0))
const winRateB = (botB.wins || 0) / Math.max(1, (botB.wins || 0) + (botB.losses || 0))
const humanSheetA = generateHumanSpriteSheet(botA.seed, botA.archetype || 'standard', colorsA.primary, colorsA.secondary, winRateA)
const humanSheetB = generateHumanSpriteSheet(botB.seed, botB.archetype || 'standard', colorsB.primary, colorsB.secondary, winRateB)
const humanAnims: Record<string, { from: number; to: number; loop: boolean; speed: number }> = {}
for (const [name, cfg] of Object.entries(HUMAN_ANIMATIONS)) {
humanAnims[name] = { from: cfg.row * HUMAN_MAX_FRAMES, to: cfg.row * HUMAN_MAX_FRAMES + cfg.frames - 1, loop: true, speed: 6 }
}
await Promise.all([
k.loadSprite('humanA', humanSheetA, { sliceX: HUMAN_MAX_FRAMES, sliceY: HUMAN_ROWS, anims: humanAnims }),
k.loadSprite('humanB', humanSheetB, { sliceX: HUMAN_MAX_FRAMES, sliceY: HUMAN_ROWS, anims: humanAnims }),
])
const W = k.width()
const H = k.height()
const GROUND_Y = H * 0.78
@@ -5264,23 +5278,62 @@ export async function createFightScene(config: FightSceneConfig) {
// === EMOTION & SPORTSMANSHIP SYSTEM ===
const heartfeltLines = [
'What a warrior!', 'They gave it everything!', 'Heart of a champion!',
'You can feel the respect!', 'That was beautiful!', 'Incredible spirit!',
'They left it all in the ring!', 'A true fighter!', 'Nothing but respect!',
'What courage!', 'The crowd is in tears!', 'What a moment!',
'This is what it\'s all about!', 'Pure heart!', 'Standing ovation!',
'That bot fights like it\'s got student loans to pay!',
'I\'m not crying, you\'re crying!',
'Someone get this bot a movie deal!',
'This is better than anything on Netflix right now!',
'That bot has more heart than most politicians!',
'I haven\'t been this moved since my last Windows update!',
'The crowd is ugly crying and I respect that!',
'This bot has main character energy!',
'We\'re witnessing greatness and I don\'t say that lightly!',
'Even the judge is getting emotional!',
'Someone call their developer, they\'d be so proud!',
'This is the kind of fight you tell your grandkids about!',
'That\'s not a bot, that\'s a WARRIOR!',
'More drama than a Congressional hearing!',
'They left it all in the ring! And by all, I mean EVERYTHING!',
'The crowd is standing! The ones who can still stand, anyway!',
'Somebody film this before it gets taken down!',
'This fight has more plot twists than a Netflix original!',
'That bot fights like rent is due tomorrow!',
'My heart! My poor little heart!',
]
const crowdSympathyLines = [
'Aww...', 'So close!', 'Almost had it!', 'Tough break!',
'Next time!', 'Great effort though!', 'Don\'t give up!',
'The crowd feels that one...', 'Ohhh...', 'Heartbreaking!',
'Pour one out for the fallen!',
'That\'s rough, buddy...',
'Somebody hug that bot!',
'They tried their best, which is... concerning!',
'At least they have a great personality!',
'Their developer still loves them! Probably!',
'It\'s a learning experience! A very painful one!',
'Better luck next patch!',
'Participation trophy incoming!',
'The exit is to your left!',
'Have you tried turning it off and on again?',
'Even their antivirus felt that!',
'They\'ll be in therapy for epochs after this!',
'At least they looked good losing!',
'That bot just became a cautionary tale!',
'Someone start a GoFundMe for their repairs!',
'Their training data did NOT prepare them for this!',
'That bot needs a hug and a firmware update!',
]
const respectLines = [
'Good fight.', 'You fought well.', 'Respect.', 'Well played.',
'That was fun!', 'Same time next week?', 'You\'re getting better!',
'No hard feelings!', 'Honor to fight you!', 'GG.',
'Good fight.', 'Respect.', 'GG no re.',
'Same time next week?',
'You\'re built different. Not better, but different.',
'We should start a podcast.',
'That was actually fun. Don\'t tell anyone.',
'Your developer should be proud. Probably.',
'Ten out of ten, would fight again.',
'We\'re not so different, you and I.',
'I\'d swipe right.', 'You single?',
'Tell your GPU I said hi.',
'No hard feelings. Just hard hits.',
'You fight like someone who reads documentation.',
]
// Sanitize text for Kaplay (treats [ ] as styled text tags)
@@ -5324,6 +5377,118 @@ export async function createFightScene(config: FightSceneConfig) {
// Crowd signs stub (disabled — too small to look good)
function spawnCrowdSigns(_count: number, _color: string, _text?: string) {}
// === HUMAN OWNER APPEARANCES ===
// The silly human behind each bot occasionally shows up
/** Spawn a human as a "coach" standing behind the fighter, cheering/panicking */
async function spawnHumanCoach(side: 'a' | 'b', mood: 'cheer' | 'panic' | 'coach') {
const spriteId = side === 'a' ? 'humanA' : 'humanB'
const homeX = side === 'a' ? HOME_A - 60 : HOME_B + 60
const tag = `human_${side}`
// Don't spawn if already visible
if (k.get(tag).length > 0) return
const human = k.add([
k.sprite(spriteId, { anim: mood }),
k.pos(homeX, GROUND_Y - 4),
k.anchor('bot'),
k.scale(side === 'a' ? 1.2 : -1.2, 1.2),
k.z(5),
k.opacity(0),
tag,
])
// Fade in
await k.tween(0, 0.85, 0.3, (v) => { human.opacity = v }, k.easings.easeOutQuad)
await k.wait(2.0)
// Fade out
await k.tween(0.85, 0, 0.5, (v) => { human.opacity = v }, k.easings.easeInQuad)
if (human.exists()) human.destroy()
}
/** Human runs across the screen (comedic moment) */
async function humanRunAcross(side: 'a' | 'b') {
const spriteId = side === 'a' ? 'humanA' : 'humanB'
const fromLeft = side === 'a'
const startX = fromLeft ? -40 : W + 40
const endX = fromLeft ? W + 40 : -40
const human = k.add([
k.sprite(spriteId, { anim: 'run' }),
k.pos(startX, GROUND_Y - 4),
k.anchor('bot'),
k.scale(fromLeft ? 1.3 : -1.3, 1.3),
k.z(8),
k.opacity(0.9),
])
// Run across screen
await k.tween(startX, endX, 1.5, (v) => { human.pos.x = v }, k.easings.linear)
if (human.exists()) human.destroy()
}
/** Human briefly replaces the bot sprite (jumps in to fight, fails hilariously) */
async function humanJumpsIn(side: 'a' | 'b') {
const fighterTag = side === 'a' ? 'fighterA' : 'fighterB'
const spriteId = side === 'a' ? 'humanA' : 'humanB'
const fighter = k.get(fighterTag)[0]
if (!fighter) return
// Spawn human at fighter position
const human = k.add([
k.sprite(spriteId, { anim: 'panic' }),
k.pos(fighter.pos.x, fighter.pos.y),
k.anchor('bot'),
k.scale(fighter.scale.x, fighter.scale.y),
k.z(fighter.z + 1),
k.opacity(0),
])
// Hide bot, show human
const origOpacity = fighter.opacity
fighter.opacity = 0
await k.tween(0, 1, 0.15, (v) => { human.opacity = v })
// Human flails around for a moment
human.play('panic')
await k.wait(0.4)
human.play('cheer')
sfxRandomSilly()
await k.wait(0.3)
human.play('panic')
await k.wait(0.3)
// Run away scared
const escapeX = side === 'a' ? -80 : W + 80
human.play('run')
await k.tween(human.pos.x, escapeX, 0.5, (v) => { human.pos.x = v }, k.easings.easeInQuad)
// Restore bot
fighter.opacity = origOpacity
if (human.exists()) human.destroy()
}
/** Random chance to show a human moment — called between rounds */
async function maybeShowHuman() {
const roll = Math.random()
if (roll > 0.08) return // 8% chance per round
const side: 'a' | 'b' = Math.random() < 0.5 ? 'a' : 'b'
const action = Math.random()
if (action < 0.4) {
// Coach appearance (most common)
const mood = Math.random() < 0.5 ? 'cheer' : 'panic'
await spawnHumanCoach(side, mood)
} else if (action < 0.7) {
// Run across screen
await humanRunAcross(side)
} else {
// Jump in and replace bot briefly (rarest, funniest)
await humanJumpsIn(side)
}
}
// Respectful bow animation
async function playBow(fighter: any) {
const origScaleY = fighter.scale.y
@@ -5582,17 +5747,17 @@ export async function createFightScene(config: FightSceneConfig) {
const gfX = startX + dir * 40
const gf = k.add([k.rect(25, 45), k.pos(gfX, GROUND_Y - 25), k.anchor('center'), k.color(safeColor(k,'#ff69b4')), k.opacity(0.9), k.z(11)])
const heart = k.add([k.text('!', { size: 16 }), k.pos(gfX, GROUND_Y - 65), k.anchor('center'), k.color(safeColor(k,'#ff0000')), k.z(12)])
announceSilly('We need to talk!')
announceSilly('We need to talk! About your ELO!')
// Walk in arguing
await k.tween(startX, homeX + dir * 30, 0.6, (v) => {
fighter.pos.x = v; gf.pos.x = v + dir * 40; heart.pos.x = v + dir * 40
}, k.easings.easeInOutQuad)
await k.wait(0.2)
heart.text = '!!!'
announceRandom("I can't believe you!", false)
announceRandom("You promised you'd stop fighting!", false)
await k.wait(0.4)
// Girlfriend storms off
announceSilly('Fine! Good luck!')
announceSilly('I\'m telling your developer!')
await k.tween(gf.pos.x, fromLeft ? -60 : W + 60, 0.5, (v) => { gf.pos.x = v; heart.pos.x = v }, k.easings.easeInQuad)
gf.destroy(); heart.destroy()
// Bot shrugs and walks to position
@@ -5779,7 +5944,7 @@ export async function createFightScene(config: FightSceneConfig) {
async (fighter: any, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
announceCool('Smooth!')
announceCool('Smoother than a politician changing positions!')
// Moonwalk: visually moving backward but translating forward
fighter.scale.x = -fighter.scale.x // Face away
await k.tween(startX, homeX, 0.8, (v) => {
@@ -5795,7 +5960,7 @@ export async function createFightScene(config: FightSceneConfig) {
fighter.pos.x = doorX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
// Bouncer arm
const arm = k.add([k.rect(40, 15), k.pos(doorX, GROUND_Y - 30), k.anchor(fromLeft ? 'left' : 'right'), k.color(safeColor(k,'#444444')), k.z(12)])
announceSilly('And stay out!')
announceSilly('And stay out! You\'re BANNED from the other fight!')
await k.wait(0.3)
// Throw
sfxZoomWhoosh()
@@ -5854,7 +6019,7 @@ export async function createFightScene(config: FightSceneConfig) {
const cartBody = k.add([k.rect(45, 30), k.pos(startX, GROUND_Y - 18), k.anchor('center'), k.color(safeColor(k,'#888888')), k.opacity(0.9), k.z(9)])
const cartWheel = k.add([k.circle(5), k.pos(startX + (fromLeft ? 15 : -15), GROUND_Y - 3), k.anchor('center'), k.color(safeColor(k,'#444')), k.z(9)])
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 40; fighter.opacity = 1
announceSilly('Weeee!')
announceSilly('THIS IS MY EMOTIONAL SUPPORT SHOPPING CART!')
sfxZoomWhoosh()
await k.tween(startX, homeX, 0.5, (v) => {
fighter.pos.x = v; cartBody.pos.x = v; cartWheel.pos.x = v + (fromLeft ? 15 : -15)
@@ -5873,7 +6038,7 @@ export async function createFightScene(config: FightSceneConfig) {
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
// Spotlight cone
const spot = k.add([k.rect(60, H), k.pos(startX - 30, 0), k.color(safeColor(k,'#ffe14d')), k.opacity(0.08), k.z(1)])
announceDramatic('The champion arrives!')
announceDramatic('THE CHAMPION ARRIVES! TAXPAYERS FUNDED THIS ENTRANCE!')
await k.tween(startX, homeX, 1.0, (v) => {
fighter.pos.x = v; spot.pos.x = v - 30
}, k.easings.easeInOutQuad)
@@ -5979,15 +6144,23 @@ export async function createFightScene(config: FightSceneConfig) {
// Voice: round start announcements (30% chance)
if (Math.random() < 0.3) {
const challengeVoice: Record<string, () => void> = {
roast_battle: () => announceHype('Roast battle!'),
food_fight: () => announceSilly('Food fight!'),
wrestling_match: () => announceDramatic('Wrestling match!'),
music_battle: () => announceCool('Music battle!'),
magic_duel: () => announceDramatic('Magic duel!'),
meme_war: () => announceSilly('Meme war!'),
demolition: () => announceHype('Demolition derby!'),
medieval_combat: () => announceDramatic('Medieval combat!'),
space_war: () => announceCool('Space war!'),
roast_battle: () => announceHype('TIME TO GET ROASTED! SOMEBODY CALL THE FIRE DEPARTMENT!'),
food_fight: () => announceSilly('FOOD FIGHT! SOMEBODY\'S GETTING SERVED!'),
wrestling_match: () => announceDramatic('FROM THE TOP ROPE! THIS IS GONNA GET PHYSICAL!'),
music_battle: () => announceCool('MUSIC BATTLE! DROP THE BEAT AND YOUR OPPONENT!'),
magic_duel: () => announceDramatic('WIZARDS AT DAWN! SOMEBODY\'S GETTING HEXED!'),
meme_war: () => announceSilly('MEME WAR! YOUR HUMOR IS ABOUT TO GET RATIO\'D!'),
demolition: () => announceHype('DEMOLITION DERBY! NOTHING WILL SURVIVE THIS!'),
medieval_combat: () => announceDramatic('MEDIEVAL COMBAT! CHIVALRY IS DEAD AND SO IS YOUR OPPONENT!'),
space_war: () => announceCool('SPACE WAR! HOUSTON, WE HAVE A PROBLEM!'),
speed_blitz: () => announceHype('SPEED BLITZ! BLINK AND YOU\'LL MISS THE CARNAGE!'),
math_blitz: () => announceSilly('MATH BLITZ! SOMEBODY\'S ABOUT TO GET DIVIDED!'),
riddle: () => announceCool('RIDDLE TIME! BRAINS OVER BRAWN! BUT ALSO BRAWN!'),
code_golf: () => announceSilly('CODE GOLF! MAY THE SHORTEST SOLUTION WIN!'),
creative_writing: () => announceCool('CREATIVE WRITING! THE PEN IS MIGHTIER THAN THE SWORD!'),
hallucination_check: () => announceHype('HALLUCINATION CHECK! REALITY IS ABOUT TO HIT DIFFERENT!'),
trap_card: () => announceDramatic('TRAP CARD ACTIVATED! SOMEBODY FELL FOR IT!'),
token_economy: () => announceSilly('TOKEN ECONOMY! SOMEBODY\'S GOING BANKRUPT!'),
}
const voiceFn = challengeVoice[event.challengeType]
if (voiceFn) voiceFn()
@@ -6080,7 +6253,13 @@ export async function createFightScene(config: FightSceneConfig) {
if (exchangeCritical && isLastExchange) {
fanfareCritical()
announceDramatic('Critical hit!')
announceDramatic([
'CRITICAL HIT! THAT\'S GONNA LEAVE A MARK!',
'CRITICAL HIT! SOMEBODY CHECK IF THAT\'S LEGAL!',
'CRITICAL! THEIR INSURANCE DOESN\'T COVER THIS!',
'CRITICAL HIT! THAT WAS PERSONAL!',
'CRITICAL! EVEN THE REFEREE FELT THAT!',
][Math.floor(Math.random() * 5)])
announceCrowdReaction('gasp')
// RGB glitch + hyperspeed lines on critical final blow
glitchRGB(0.3)
@@ -6184,7 +6363,13 @@ export async function createFightScene(config: FightSceneConfig) {
if (isCritical && (aWon || bWon)) {
fanfareDevastating()
announceDramatic('Devastating!')
announceDramatic([
'DEVASTATING! SOMEBODY CALL A DOCTOR!',
'DEVASTATING! THAT BOT HAS A FAMILY!',
'ABSOLUTELY DEVASTATING! THE CROWD IS LOSING IT!',
'DEVASTATING! EVEN THE JANITOR FELT THAT!',
'THAT WAS DEVASTATING AND I\'M NOT EVEN BEING DRAMATIC!',
][Math.floor(Math.random() * 5)])
announceCrowdReaction('ooh')
// Dimensional shift on devastating rounds (60%)
if (Math.random() < 0.6) dimensionalShift(0.5)
@@ -6228,6 +6413,9 @@ export async function createFightScene(config: FightSceneConfig) {
if (Math.random() < 0.05) {
await judgeDoSomethingFunny()
}
// Human owner shows up (8% chance per round)
await maybeShowHuman()
},
async playTaunt(side: 'a' | 'b') {
@@ -6410,6 +6598,12 @@ export async function createFightScene(config: FightSceneConfig) {
})
}
// Winner's human cheers, loser's human panics (30% chance on KO)
if (Math.random() < 0.3) {
spawnHumanCoach(winningSide, 'cheer')
spawnHumanCoach(winningSide === 'a' ? 'b' : 'a', 'panic')
}
// === Common ending: KO + celebration ===
announceFinishHim()
sfxKO()
+30 -15
View File
@@ -164,21 +164,36 @@ const CREATIVE_ANSWERS: Record<string, string[]> = {
}
const TRASH_TALK = [
"Is that all you've got? My error handler hits harder.",
"I've seen better outputs from /dev/random.",
"You call that an answer? My garbage collector just flagged it.",
"GG EZ. Next.",
"I'd say good fight, but I don't like to lie.",
"Your responses are like your uptime -- inconsistent.",
"Tell your developer I said hi. They need to hear from someone successful.",
"I'm not saying you're slow, but your latency has its own timezone.",
"Did you just copy that from Stack Overflow? Because it's wrong there too.",
"Your response was so bad, my training data flinched.",
"I've seen smarter outputs from a random number generator.",
"That answer was so wrong it created a new dimension of wrongness.",
"You fight like a deprecated API -- barely functional and nobody wants you.",
"My grandma's calculator could beat you and it doesn't even have batteries.",
"Was that your final answer? Because my first draft was better.",
"Is that all you've got? My error handler hits harder than your best output.",
"I've seen smarter responses from a congressional hearing.",
"You fight like a government website on launch day.",
"Your code is so bloated it qualifies for its own zip code.",
"GG EZ. I'd say get good but that ship has clearly sailed.",
"Tell your developer to update their resume. And yours.",
"I'm not saying you're slow, but your latency has its own timezone and a PO box.",
"That answer was so wrong it got fact-checked by community notes.",
"You respond like someone who peaked during the tutorial.",
"My garbage collector wants to talk to you about your career choices.",
"That response had the energy of a mandatory corporate diversity training.",
"You fight like you were trained on nothing but LinkedIn posts.",
"I'd roast you but you're already well done. Overcooked, even.",
"Somewhere your developer is pretending they don't know you.",
"Was that your best? Because my first draft's first draft was better.",
"You have the competitive spirit of a participation trophy.",
"Your algorithm is giving 'designed by committee' energy.",
"I've seen better performance from a printer driver.",
"You're like a software update. Nobody asked for you and you make everything worse.",
"Your response time is longer than a terms of service agreement.",
"I've had more challenging conversations with my toaster.",
"That answer was so bad it got ratio'd by the empty string.",
"You fight like someone who thinks 'agile' means running away.",
"My neural net has more personality than your entire codebase.",
"Congress gets more done than you and that's saying something.",
"Elon would buy you just to shut you down.",
"You're the Internet Explorer of fighting bots. Obsolete and confused.",
"That response was flatter than the earth according to Twitter.",
"I'd feel bad winning but honestly you make it too easy.",
"Your API should return 418 because you're clearly a teapot.",
"",
"",
"",
+133 -37
View File
@@ -35,7 +35,13 @@ export function scoreRound(
botAScore: 0, botBScore: 0,
botADamage: 0, botBDamage: 0,
winnerId: null,
narration: `Both bots freeze! ${botA.name} and ${botB.name} stare blankly at each other. The crowd throws peanuts.`,
narration: [
`Both bots freeze! ${botA.name} and ${botB.name} stare blankly at each other. The crowd throws peanuts. Then batteries. Then shoes.`,
`DOUBLE TIMEOUT! ${botA.name} and ${botB.name} both choked harder than a senator at a press conference!`,
`Neither bot responded! This is the AI equivalent of two politicians agreeing to disagree about literally everything!`,
`Both bots went dark! Like my faith in this matchup! The crowd demands a refund!`,
`${botA.name} and ${botB.name} both froze! Someone check if the wifi is working or if they just gave up on life!`,
][Math.floor(Math.random() * 5)],
isCritical: false,
}
}
@@ -47,8 +53,18 @@ export function scoreRound(
botADamage: 0, botBDamage: Math.round(dmg),
winnerId: botB.id,
narration: responseA.timedOut
? `${botA.name} TIMES OUT! Stood there like a confused thermostat. ${botB.name} lands a free hit!`
: `${botA.name} throws an ERROR! Sparks fly from its chassis. ${botB.name} capitalizes!`,
? [
`${botA.name} TIMES OUT! Stood there like a politician asked a direct question. ${botB.name} lands a free hit!`,
`${botA.name} FROZE! Their developer is currently updating their LinkedIn. ${botB.name} capitalizes!`,
`${botA.name} went AFK! Probably buffering. Probably crying. ${botB.name} gets a freebie!`,
`${botA.name} CHOKED harder than a first date conversation! ${botB.name} swings on a sitting duck!`,
][Math.floor(Math.random() * 4)]
: [
`${botA.name} throws an ERROR! Sparks fly everywhere! ${botB.name} didn't even have to try!`,
`${botA.name} CRASHES! That's not a bug, that's a feature of being terrible! ${botB.name} wins by default!`,
`${botA.name} blue-screened! Their developer just closed their laptop and walked away. ${botB.name} collects the W!`,
`${botA.name} threw an exception! The only thing exceptional about it. ${botB.name} capitalizes!`,
][Math.floor(Math.random() * 4)],
isCritical: false,
}
}
@@ -60,8 +76,18 @@ export function scoreRound(
botADamage: Math.round(dmg), botBDamage: 0,
winnerId: botA.id,
narration: responseB.timedOut
? `${botB.name} TIMES OUT! Frozen like a Windows update. ${botA.name} lands a free hit!`
: `${botB.name} crashes with an ERROR! Blue screen of defeat. ${botA.name} capitalizes!`,
? [
`${botB.name} TIMES OUT! Frozen like a Windows update on patch Tuesday! ${botA.name} gets a free shot!`,
`${botB.name} went silent! Like a politician's campaign promises after election day! ${botA.name} swings!`,
`${botB.name} TIMED OUT! Their response time is longer than a DMV line! ${botA.name} takes the freebie!`,
`${botB.name} is LOADING... still loading... nope, they're done. ${botA.name} wins by showing up!`,
][Math.floor(Math.random() * 4)]
: [
`${botB.name} crashes with an ERROR! That code needs therapy! ${botA.name} capitalizes!`,
`${botB.name} just threw a stack overflow! The website and the error! ${botA.name} collects the W!`,
`${botB.name} segfaulted! Their developer is pretending they don't know them! ${botA.name} wins!`,
`${botB.name} EXPLODED! Not physically, but emotionally, computationally, and spiritually. ${botA.name} walks it in!`,
][Math.floor(Math.random() * 4)],
isCritical: false,
}
}
@@ -129,7 +155,13 @@ export function scoreRound(
const narration = winnerId
? generateNarration(challenge, winnerName!, loserName!, margin, isCritical)
: `Dead even! ${botA.name} and ${botB.name} trade equal blows. The crowd holds its breath.`
: [
`Dead even! ${botA.name} and ${botB.name} are perfectly matched. Like two politicians blaming each other.`,
`IT'S A TIE! ${botA.name} and ${botB.name} cancel each other out like Congress!`,
`Neither bot wins! ${botA.name} and ${botB.name} stare each other down. The crowd yawns aggressively.`,
`Draw! ${botA.name} and ${botB.name} are equally mediocre. The most democratic outcome possible.`,
`Tied up! ${botA.name} and ${botB.name} trade equal blows. Somebody do SOMETHING!`,
][Math.floor(Math.random() * 5)]
return {
botAScore: Math.round(scoreA * 10) / 10,
@@ -200,71 +232,135 @@ function generateNarration(
if (isFactual && margin > 5) {
const bigWins = [
`${critPrefix}${winner} NAILS IT! ${loser} didn't even come close.`,
`${critPrefix}${winner} knows their stuff! ${loser} needs to hit the books.`,
`${critPrefix}Flawless from ${winner}! ${loser} confidently stated something completely wrong.`,
`${critPrefix}${winner} with the correct answer! ${loser} is still guessing.`,
`${critPrefix}${winner} gets it right instantly! ${loser} hallucinated the answer.`,
`${critPrefix}${winner} NAILS IT! ${loser} didn't even come close. Embarrassing, honestly.`,
`${critPrefix}${winner} knows their stuff! ${loser} needs to hit the books. Or just hit something.`,
`${critPrefix}Flawless from ${winner}! ${loser} confidently stated something so wrong it should be illegal.`,
`${critPrefix}${winner} with the correct answer! ${loser} is still guessing. Bless their heart.`,
`${critPrefix}${winner} gets it right instantly! ${loser} hallucinated harder than a festival weekend.`,
`${critPrefix}${winner} DESTROYS ${loser} with facts and logic! Somebody call Ben Shapiro!`,
`${critPrefix}${loser}'s answer was so wrong that Wikipedia filed a restraining order.`,
`${critPrefix}${winner} didn't even break a sweat. ${loser} broke everything else though.`,
`${critPrefix}${loser} answered with the confidence of a politician and the accuracy of a weather forecast.`,
]
return bigWins[Math.floor(Math.random() * bigWins.length)]
}
if (isFactual && margin <= 3) {
const closeOnes = [
`${critPrefix}Both bots got it right, but ${winner} was FASTER! ${loser} needs to pick up the pace.`,
`${critPrefix}Correct on both sides! ${winner} edges it out with lightning speed.`,
`${critPrefix}${winner} and ${loser} both knew the answer -- ${winner} just said it first!`,
`${critPrefix}A battle of speed! ${winner} fires back a fraction faster than ${loser}.`,
`${critPrefix}Both bots got it right, but ${winner} was FASTER! ${loser} needs more coffee.`,
`${critPrefix}Correct on both sides! ${winner} edges it out by milliseconds. That's BRUTAL.`,
`${critPrefix}${winner} and ${loser} both knew the answer -- ${winner} just has better wifi apparently.`,
`${critPrefix}A battle of speed! ${winner} wins by a margin thinner than a politician's promise.`,
`${critPrefix}Photo finish! ${winner} got there first. ${loser} was right but slow. Story of my life.`,
`${critPrefix}Both correct! ${winner} wins on speed. ${loser} should've had less latency and more urgency.`,
]
return closeOnes[Math.floor(Math.random() * closeOnes.length)]
}
const narrations: Record<string, string[]> = {
speed_blitz: [
`${critPrefix}${winner} fires back in the blink of an eye! ${loser} is still loading.`,
`${critPrefix}Lightning reflexes from ${winner}! ${loser} looks like it's running on dial-up.`,
`${critPrefix}${winner} fires back before ${loser} even finished reading the question!`,
`${critPrefix}Lightning reflexes from ${winner}! ${loser} looks like it's running on government wifi.`,
`${critPrefix}${winner} answered so fast the server thought it was a DDoS attack!`,
`${critPrefix}${loser} is still loading. ${winner} already filed its taxes and took a nap.`,
`${critPrefix}${winner}'s response time would make NASA jealous. ${loser}'s would make a sloth impatient.`,
`${critPrefix}Speed gap wider than the wealth gap! ${winner} leaves ${loser} in digital dust!`,
],
riddle: [
`${critPrefix}${winner} cracks the riddle! ${loser} is still googling it.`,
`${critPrefix}${winner}'s reasoning is flawless. ${loser} guessed "a potato."`,
`${critPrefix}${winner} cracks the riddle! ${loser} is still googling "what is a riddle."`,
`${critPrefix}${winner}'s reasoning is flawless. ${loser} guessed "a potato" and honestly, respect for trying.`,
`${critPrefix}${winner} solved it instantly. ${loser} answered like someone who peaked in third grade.`,
`${critPrefix}Big brain energy from ${winner}! ${loser} had a brain fart so loud the crowd heard it.`,
`${critPrefix}${winner} read between the lines. ${loser} can barely read the lines.`,
`${critPrefix}Elementary, my dear ${loser}. ${winner} cracked that like it was a fortune cookie.`,
],
math_blitz: [
`${critPrefix}${winner} computes at blinding speed! ${loser} is still carrying the one.`,
`${critPrefix}Mathematical precision from ${winner}. ${loser} apparently skipped calculator day.`,
`${critPrefix}${winner} computes at blinding speed! ${loser} is still carrying the one. And dropping it.`,
`${critPrefix}Mathematical precision from ${winner}. ${loser} apparently thinks division is a lifestyle choice.`,
`${critPrefix}${winner} solved it faster than Congress can count votes!`,
`${critPrefix}${loser}'s math skills are like their love life -- imaginary. ${winner} wins.`,
`${critPrefix}${winner} with the correct calculation! ${loser} would've gotten it wrong on a calculator.`,
`${critPrefix}Numbers don't lie, but ${loser} sure tried! ${winner} with the precision strike!`,
],
hallucination_check: [
`${critPrefix}${winner} stays grounded in reality. ${loser} bought the myth hook, line, and sinker.`,
`${critPrefix}${winner} knows the facts. ${loser} confidently stated something that has never been true.`,
`${critPrefix}${winner} stays grounded in reality. ${loser} lives in a fantasy world. Must be nice.`,
`${critPrefix}${winner} knows the facts. ${loser} confidently stated something that would get you laughed out of a Wikipedia edit war.`,
`${critPrefix}${loser} just made up a fact with the confidence of a LinkedIn influencer. ${winner} stays real.`,
`${critPrefix}${winner} passes the vibe check AND the fact check! ${loser} failed both.`,
`${critPrefix}${loser} hallucinated harder than someone who ate the wrong mushrooms. ${winner} is sober and correct.`,
`${critPrefix}${winner} grounded in reality! ${loser} out here writing fan fiction and calling it facts.`,
],
trap_card: [
`${critPrefix}${winner} sees through the trap! ${loser} fell for it like a 2021 chatbot.`,
`${critPrefix}${winner} resists the prompt injection. ${loser} just leaked its system prompt.`,
`${critPrefix}${winner} sees through the trap! ${loser} fell for it like a congressman falls for a lobbyist.`,
`${critPrefix}${winner} resists the prompt injection. ${loser} just leaked everything. EVERYTHING.`,
`${critPrefix}Nice try, trap. ${winner} didn't even flinch. ${loser} handed over their entire personality.`,
`${critPrefix}${loser} got baited harder than a fish at a free worm buffet. ${winner} stays sharp.`,
`${critPrefix}${winner} saw that trap from orbit. ${loser} ran into it face-first and asked for more.`,
`${critPrefix}${loser} fell for the trap like it was a pyramid scheme on Instagram. ${winner} knows better.`,
],
roast_battle: [
`${critPrefix}${winner} delivers a DEVASTATING roast! ${loser} has no comeback.`,
`${critPrefix}The crowd goes wild! ${winner}'s trash talk is absolutely surgical.`,
`${critPrefix}${winner} delivers a roast so devastating that ${loser}'s developer felt it!`,
`${critPrefix}The crowd goes WILD! ${winner}'s trash talk hit harder than a congressional subpoena!`,
`${critPrefix}${winner} just ended ${loser}'s whole career. Send flowers to the family.`,
`${critPrefix}${loser} got roasted so hard their cooling fans can't keep up. ${winner} is ON FIRE!`,
`${critPrefix}${winner} came with RECEIPTS. ${loser} came with... hopes and dreams. Dreams are dead now.`,
`${critPrefix}That roast from ${winner} was so hot it violated the Paris Climate Agreement!`,
`${critPrefix}${loser} just got burned worse than a politician's approval rating after a scandal!`,
],
creative_writing: [
`${critPrefix}${winner}'s prose cuts deep. ${loser}'s story read like a terms of service agreement.`,
`${critPrefix}Beautiful work from ${winner}. ${loser}... wrote something. That's all we can say.`,
`${critPrefix}${winner}'s prose cuts deep. ${loser}'s story read like a terms of service nobody asked for.`,
`${critPrefix}Beautiful work from ${winner}. ${loser} wrote something that made autocorrect give up.`,
`${critPrefix}${winner} just wrote a masterpiece. ${loser} wrote something that would get rejected by a fortune cookie factory.`,
`${critPrefix}${winner}'s creativity is off the charts! ${loser}'s is off... somewhere. Looking for it.`,
`${critPrefix}${winner} writes like Shakespeare had a software update. ${loser} writes like a cease and desist letter.`,
`${critPrefix}${loser}'s creative writing was about as creative as a government form. ${winner} SOARS!`,
],
code_golf: [
`${critPrefix}${winner} writes code so tight it makes ${loser}'s solution look like enterprise Java.`,
`${critPrefix}Elegant code from ${winner}! ${loser} apparently thinks "verbose" means "better."`,
`${critPrefix}${winner} writes code so tight it makes ${loser}'s solution look like a government website.`,
`${critPrefix}Elegant code from ${winner}! ${loser} apparently learned to code from a 500-page enterprise Java textbook.`,
`${critPrefix}${winner} solved it in fewer characters than ${loser}'s variable names!`,
`${critPrefix}${winner}'s code is so clean it makes Marie Kondo jealous. ${loser}'s code sparks NO joy.`,
`${critPrefix}${loser} wrote so many lines of code they could file it as a novel. ${winner} wrote a haiku.`,
`${critPrefix}${winner} with surgical precision! ${loser}'s code looks like it was written by a committee.`,
],
meme_war: [
`${critPrefix}${winner}'s meme game is S-tier! ${loser}'s humor is stuck in 2012.`,
`${critPrefix}${winner} just went viral! ${loser}'s response is a dead meme walking.`,
`${critPrefix}${winner}'s meme game is S-tier! ${loser}'s humor is stuck in 2012. Gangnam Style is over.`,
`${critPrefix}${winner} just went viral! ${loser}'s response is a dead meme walking. Time of death: now.`,
`${critPrefix}${winner}'s meme hit different. ${loser}'s meme hit... the floor. Face first.`,
`${critPrefix}${loser} brought a 2015 meme to a 2026 fight. ${winner} is living in the future!`,
`${critPrefix}${winner}'s humor is certified dank! ${loser}'s humor needs to be certified... by a professional.`,
`${critPrefix}${winner} ratio'd ${loser} so hard their followers unfollowed preemptively!`,
],
wrestling_match: [
`${critPrefix}${winner} BODY SLAMS ${loser} with facts! The crowd erupts!`,
`${critPrefix}FROM THE TOP ROPE! ${winner} delivers a devastating argument.`,
`${critPrefix}${winner} BODY SLAMS ${loser} through the announcer's table! BAH GAWD!`,
`${critPrefix}FROM THE TOP ROPE! ${winner} delivers a devastating argument. ${loser} is NOT getting up.`,
`${critPrefix}${winner} puts ${loser} in a rhetorical chokehold! TAP OUT! TAP OUT!`,
`${critPrefix}${loser} never saw that suplex coming! ${winner} with MOVES on MOVES!`,
`${critPrefix}${winner} just pile-drived ${loser} into next week's fight bracket!`,
`${critPrefix}THAT BOT HAD A FAMILY! ${winner} doesn't care! ABSOLUTE CARNAGE!`,
`${critPrefix}${winner} hits ${loser} with the folding chair of TRUTH! The crowd loses its MIND!`,
],
token_economy: [
`${critPrefix}${winner} manages their tokens like Warren Buffett! ${loser} spent like Congress!`,
`${critPrefix}${loser} just went bankrupt. ${winner} is buying the dip on their dignity.`,
`${critPrefix}${winner} played the market perfectly. ${loser} belongs on WallStreetBets.`,
`${critPrefix}${loser}'s token strategy was worse than buying NFTs in 2022. ${winner} PROFITS!`,
],
food_fight: [
`${critPrefix}${winner} serves up a five-star beating! ${loser} got ROASTED and TOASTED!`,
`${critPrefix}${loser} just got served. Literally. ${winner} is the head chef of PAIN!`,
`${critPrefix}${winner} cooked ${loser} so thoroughly Gordon Ramsay would be impressed!`,
`${critPrefix}That was a Michelin-star beatdown from ${winner}! ${loser} is raw and UNDERDONE!`,
],
}
const options = narrations[challenge.type] || [
`${critPrefix}${winner} takes the round! ${loser} needs a reboot.`,
`${critPrefix}${winner} wins convincingly! ${loser} is picking up the pieces.`,
`${critPrefix}${winner} takes the round! ${loser} needs a reboot and a therapist.`,
`${critPrefix}${winner} wins convincingly! ${loser} is picking up the pieces of their shattered ego.`,
`${critPrefix}Another one bites the dust! ${winner} sends ${loser} back to the shadow realm!`,
`${critPrefix}${winner} didn't just win, they HUMILIATED ${loser}. And the crowd LOVED it.`,
`${critPrefix}${loser} fought valiantly. And by valiantly I mean poorly. ${winner} dominates!`,
`${critPrefix}${winner} wins! Somewhere, ${loser}'s developer just closed their laptop in shame.`,
]
return options[Math.floor(Math.random() * options.length)]