diff --git a/server/src/engine/fight-loop.ts b/server/src/engine/fight-loop.ts index 1726d1b..4123898 100644 --- a/server/src/engine/fight-loop.ts +++ b/server/src/engine/fight-loop.ts @@ -1,6 +1,7 @@ import { db, schema } from '../db/index.js' import { runMockFight } from './mock.js' import { eq } from 'drizzle-orm' +import { pick } from '../lib/utils.js' import { fightEvents } from './events.js' export interface FightResult { @@ -157,8 +158,7 @@ function pickMatchup( const sorted = [...bots].sort((a, b) => b.eloRating - a.eloRating) if (style === 'elo_close' || (style === 'mixed' && fightNum % 3 !== 0)) { - const idx = Math.floor(Math.random() * bots.length) - const bot = bots[idx] + const bot = pick(bots) const others = bots.filter(b => b.id !== bot.id) others.sort((a, b) => { const diffA = Math.abs(a.eloRating - bot.eloRating) + Math.random() * 150 diff --git a/server/src/engine/scoring.ts b/server/src/engine/scoring.ts index 8aaacfd..28f0535 100644 --- a/server/src/engine/scoring.ts +++ b/server/src/engine/scoring.ts @@ -1,4 +1,5 @@ import type { Challenge } from './challenges.js' +import { pick } from '../lib/utils.js' import { checkAnswer } from './answers.js' import { scoreRetroResponse, type RetroScoreResult } from './retro-moves.js' @@ -41,13 +42,13 @@ export function scoreRound( botAScore: 0, botBScore: 0, botADamage: 0, botBDamage: 0, winnerId: null, - narration: [ + narration: pick([ `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, } } @@ -59,18 +60,18 @@ export function scoreRound( botADamage: 0, botBDamage: Math.round(dmg), winnerId: botB.id, narration: responseA.timedOut - ? [ + ? pick([ `${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)] - : [ + ]) + : pick([ `${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, } } @@ -82,18 +83,18 @@ export function scoreRound( botADamage: Math.round(dmg), botBDamage: 0, winnerId: botA.id, narration: responseB.timedOut - ? [ + ? pick([ `${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)] - : [ + ]) + : pick([ `${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, } } @@ -161,13 +162,13 @@ export function scoreRound( const narration = winnerId ? generateNarration(challenge, winnerName!, loserName!, margin, isCritical) - : [ + : pick([ `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, @@ -270,7 +271,7 @@ function generateNarration( `${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)] + return pick(bigWins) } if (isFactual && margin <= 3) { @@ -282,7 +283,7 @@ function generateNarration( `${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)] + return pick(closeOnes) } const narrations: Record = { @@ -400,7 +401,7 @@ function generateNarration( `${critPrefix}${winner} wins! Somewhere, ${loser}'s developer just closed their laptop in shame.`, ] - return options[Math.floor(Math.random() * options.length)] + return pick(options) } @@ -475,7 +476,7 @@ function scoreRetroRound( 'EQUAL POWER! The arcade cabinet shakes from perfectly matched inputs!', `DOUBLE K.O.! ${botA.name} and ${botB.name} hit identical damage totals! Insert another quarter!`, ] - narration = draws[Math.floor(Math.random() * draws.length)] + narration = pick(draws) } else { const hasDiscovery = winnerResult.moves.some(m => m.discovered) const discoveryNames = winnerResult.moves.filter(m => m.discovered).map(m => m.name) @@ -502,7 +503,7 @@ function scoreRetroRound( ) const prefix = isCritical ? 'CRITICAL COMBO! ' : '' - narration = prefix + narrations[Math.floor(Math.random() * narrations.length)] + narration = prefix + pick(narrations) } return { diff --git a/server/src/lib/utils.ts b/server/src/lib/utils.ts new file mode 100644 index 0000000..c7ac338 --- /dev/null +++ b/server/src/lib/utils.ts @@ -0,0 +1,10 @@ +/** Pick a random element from an array */ +export function pick(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)] +} + +/** Wrap an unknown thrown value into an Error */ +export function toError(err: unknown): Error { + if (err instanceof Error) return err + return new Error(String(err)) +}