feat: ranked queue Elo-bracket matchmaking and harder prompts
Elo-bracket matchmaking: prefer ±200, widen by 100 every 15s of waiting. Add pickRankedChallenge() that filters to creative/open-ended only, never multiple choice. Show ranked queue status, wait estimate, and "HARDER PROMPTS" notice on JoinBoutPage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e229bfb8fc
commit
156c727f82
@@ -26,8 +26,11 @@ export function setRankedCooldown(botId: string) {
|
||||
rankedCooldowns.set(botId, Date.now() + COOLDOWN_MS)
|
||||
}
|
||||
|
||||
export function getRankedQueueStatus(): { waiting: number } {
|
||||
return { waiting: rankedQueue.length }
|
||||
export function getRankedQueueStatus(): { waiting: number; estimatedWaitSec: number } {
|
||||
const waiting = rankedQueue.length
|
||||
// If someone is waiting, new joiner matches instantly; otherwise estimate ~15s
|
||||
const estimatedWaitSec = waiting > 0 ? 5 : 15
|
||||
return { waiting, estimatedWaitSec }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,21 +97,38 @@ export async function joinRankedQueue(botId: string, paymentId: string): Promise
|
||||
old.reject(new Error('Rejoined ranked queue'))
|
||||
}
|
||||
|
||||
// Check if someone is already waiting — instant match by closest ELO
|
||||
// Elo-bracket matchmaking: prefer opponents within bracket, widen over time
|
||||
if (rankedQueue.length > 0) {
|
||||
rankedQueue.sort((a, b) => {
|
||||
const diffA = Math.abs(a.eloRating - bot.eloRating)
|
||||
const diffB = Math.abs(b.eloRating - bot.eloRating)
|
||||
return diffA - diffB
|
||||
})
|
||||
const now = Date.now()
|
||||
// Find best match: base bracket ±200, widens by 100 every 15s of waiting
|
||||
let bestMatch: RankedQueueEntry | null = null
|
||||
let bestDiff = Infinity
|
||||
|
||||
const opponent = rankedQueue.shift()!
|
||||
clearTimeout(opponent.timeoutHandle)
|
||||
for (const entry of rankedQueue) {
|
||||
const waitSec = (now - entry.joinedAt) / 1000
|
||||
const bracket = 200 + Math.floor(waitSec / 15) * 100
|
||||
const eloDiff = Math.abs(entry.eloRating - bot.eloRating)
|
||||
if (eloDiff <= bracket && eloDiff < bestDiff) {
|
||||
bestMatch = entry
|
||||
bestDiff = eloDiff
|
||||
}
|
||||
}
|
||||
|
||||
// Start ranked fight and link both payments
|
||||
const fightId = await runFightAsync(opponent.botId, botId, 'ranked')
|
||||
await linkPaymentsToFight(fightId, [opponent.paymentId, paymentId])
|
||||
opponent.resolve(fightId)
|
||||
// If no one in bracket, fall back to closest overall
|
||||
if (!bestMatch) {
|
||||
rankedQueue.sort((a, b) =>
|
||||
Math.abs(a.eloRating - bot.eloRating) - Math.abs(b.eloRating - bot.eloRating)
|
||||
)
|
||||
bestMatch = rankedQueue[0]
|
||||
}
|
||||
|
||||
const idx = rankedQueue.indexOf(bestMatch)
|
||||
rankedQueue.splice(idx, 1)
|
||||
clearTimeout(bestMatch.timeoutHandle)
|
||||
|
||||
const fightId = await runFightAsync(bestMatch.botId, botId, 'ranked')
|
||||
await linkPaymentsToFight(fightId, [bestMatch.paymentId, paymentId])
|
||||
bestMatch.resolve(fightId)
|
||||
return fightId
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user