diff --git a/src/lib/server/match/engine.ts b/src/lib/server/match/engine.ts index 2fdd9e3..5541f95 100644 --- a/src/lib/server/match/engine.ts +++ b/src/lib/server/match/engine.ts @@ -298,24 +298,38 @@ export async function submitRoll(matchId: string, participantId: string, value: ); if (allRolled) { - const sorted = [...participants] - .map((p) => ({ - ...p, - rollValue: p.id === participantId ? value : p.rollValue! - })) - .sort((a, b) => b.rollValue - a.rollValue); + const finalRolls = participants.map((p) => ({ + ...p, + rollValue: p.id === participantId ? value : p.rollValue! + })); + + const rollValues = finalRolls.map((p) => p.rollValue); + const hasTie = new Set(rollValues).size < rollValues.length; + + if (hasTie) { + // Reset all rolls so everyone must roll again + for (const p of participants) { + await db + .update(matchParticipant) + .set({ rollValue: null }) + .where(eq(matchParticipant.id, p.id)); + } + // Stay in ROLLING state + } else { + const sorted = [...finalRolls].sort((a, b) => b.rollValue - a.rollValue); + + for (let i = 0; i < sorted.length; i++) { + await db + .update(matchParticipant) + .set({ pickOrder: i + 1 }) + .where(eq(matchParticipant.id, sorted[i].id)); + } - for (let i = 0; i < sorted.length; i++) { await db - .update(matchParticipant) - .set({ pickOrder: i + 1 }) - .where(eq(matchParticipant.id, sorted[i].id)); + .update(match) + .set({ state: MATCH_STATES.PICKING }) + .where(eq(match.id, matchId)); } - - await db - .update(match) - .set({ state: MATCH_STATES.PICKING }) - .where(eq(match.id, matchId)); } return getMatchFull(matchId); diff --git a/src/lib/server/match/orchestrator.ts b/src/lib/server/match/orchestrator.ts index fb22819..f57a086 100644 --- a/src/lib/server/match/orchestrator.ts +++ b/src/lib/server/match/orchestrator.ts @@ -122,28 +122,42 @@ export function onMatchStateChange(matchId: string, newState: string) { if (m.state !== MATCH_STATES.ROLLING) return; const lobby = getLobby(matchId); - const unrolled = m.participants.filter((p) => p.rollValue === null); - if (unrolled.length === 0) return; + const initialUnrolled = m.participants.filter((p) => p.rollValue === null); + if (initialUnrolled.length === 0) return; - const names = unrolled.map((p) => p.team.name).join(', '); + const names = initialUnrolled.map((p) => p.team.name).join(', '); if (lobby?.isAlive) { await lobby.chat(`⏰ Rolling timed out — auto-rolling for: ${names}`); } - for (const p of unrolled) { - const autoRoll = Math.floor(Math.random() * 100) + 1; - await submitRoll(matchId, p.id, autoRoll); - console.log(`[Timeout] Auto-rolled ${autoRoll} for ${p.team.name} in match ${matchId}`); - if (lobby?.isAlive) { - await lobby.chat(`${p.team.name} auto-rolled ${autoRoll}`); + // Auto-roll with tie retry: keep rolling until no ties + let tieTries = 0; + let current = await getMatchFull(matchId); + while (current.state === MATCH_STATES.ROLLING && tieTries < 10) { + const unrolled = current.participants.filter((p) => p.rollValue === null); + if (unrolled.length === 0) break; + for (const p of unrolled) { + const autoRoll = Math.floor(Math.random() * 100) + 1; + await submitRoll(matchId, p.id, autoRoll); + console.log(`[Timeout] Auto-rolled ${autoRoll} for ${p.team.name} in match ${matchId}`); + if (lobby?.isAlive) { + await lobby.chat(`${p.team.name} auto-rolled ${autoRoll}`); + } + } + current = await getMatchFull(matchId); + if (current.state === MATCH_STATES.ROLLING) { + // Tie — all rolls were reset, retry + tieTries++; + if (lobby?.isAlive) { + await lobby.chat(`Tie! Auto-rolling again...`); + } } } // After auto-rolling, check if state advanced to PICKING - const updated = await getMatchFull(matchId); - if (updated.state === MATCH_STATES.PICKING) { + if (current.state === MATCH_STATES.PICKING) { onMatchStateChange(matchId, MATCH_STATES.PICKING); - const sorted = [...updated.participants].sort( + const sorted = [...current.participants].sort( (a, b) => (a.pickOrder ?? 99) - (b.pickOrder ?? 99) ); if (lobby?.isAlive) { @@ -414,10 +428,12 @@ function setupChatHandlers(matchId: string, lobby: TournamentLobby) { return; } + const isLastRoll = m.participants.filter((p) => p.rollValue === null).length === 1; + await submitRoll(matchId, targetParticipant.id, value); console.log(`[Orchestrator] IRC roll: ${username} = ${value}`); - // Check if all rolled → announce pick order + // Check if all rolled → announce pick order or tie const updated = await getMatchFull(matchId); const remaining = updated.participants.filter((p) => p.rollValue === null); @@ -432,6 +448,9 @@ function setupChatHandlers(matchId: string, lobby: TournamentLobby) { `Rolls complete! ${sorted[0]?.team.name} picks first. ` + `Use !pick (e.g. !pick NM1). You have 2 minutes.` ); + } else if (isLastRoll && remaining.length === updated.participants.length) { + // All rolled the same value — everyone must reroll + await lobby.chat(`Tie! All players rolled ${value}. Please !roll again.`); } else if (remaining.length > 0) { // Tell the other player they still need to roll await lobby.chat( diff --git a/src/routes/(app)/matches/[id]/+page.server.ts b/src/routes/(app)/matches/[id]/+page.server.ts index af4d1ab..e57b0b0 100644 --- a/src/routes/(app)/matches/[id]/+page.server.ts +++ b/src/routes/(app)/matches/[id]/+page.server.ts @@ -152,6 +152,7 @@ export const actions: Actions = { } } + const isLastRoll = m.participants.filter((p) => p.rollValue === null).length === 1; const value = Math.floor(Math.random() * 100) + 1; await submitRoll(params.id, myParticipant.id, value); @@ -160,7 +161,7 @@ export const actions: Actions = { if (lobby) { lobby.chat(`${myParticipant.team?.name ?? 'Player'} rolled ${value} (via web)`).catch(() => {}); - // Check if all rolled → announce pick order + // Check if all rolled → announce pick order or tie const updated = await getMatchFull(params.id); if (updated.state === 'PICKING') { const sorted = [...updated.participants].sort( @@ -169,6 +170,8 @@ export const actions: Actions = { lobby.chat( `Rolls complete! ${sorted[0]?.team.name} picks first. Use !pick (e.g. !pick NM1) or pick in web UI.` ).catch(() => {}); + } else if (isLastRoll && updated.participants.every((p: any) => p.rollValue === null)) { + lobby.chat(`Tie! All players rolled ${value}. Please !roll again.`).catch(() => {}); } }