fix: enhance auto-rolling logic to handle ties and improve participant roll announcements
This commit is contained in:
@@ -298,12 +298,25 @@ export async function submitRoll(matchId: string, participantId: string, value:
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (allRolled) {
|
if (allRolled) {
|
||||||
const sorted = [...participants]
|
const finalRolls = participants.map((p) => ({
|
||||||
.map((p) => ({
|
|
||||||
...p,
|
...p,
|
||||||
rollValue: p.id === participantId ? value : p.rollValue!
|
rollValue: p.id === participantId ? value : p.rollValue!
|
||||||
}))
|
}));
|
||||||
.sort((a, b) => b.rollValue - a.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++) {
|
for (let i = 0; i < sorted.length; i++) {
|
||||||
await db
|
await db
|
||||||
@@ -317,6 +330,7 @@ export async function submitRoll(matchId: string, participantId: string, value:
|
|||||||
.set({ state: MATCH_STATES.PICKING })
|
.set({ state: MATCH_STATES.PICKING })
|
||||||
.where(eq(match.id, matchId));
|
.where(eq(match.id, matchId));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return getMatchFull(matchId);
|
return getMatchFull(matchId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,14 +122,20 @@ export function onMatchStateChange(matchId: string, newState: string) {
|
|||||||
if (m.state !== MATCH_STATES.ROLLING) return;
|
if (m.state !== MATCH_STATES.ROLLING) return;
|
||||||
|
|
||||||
const lobby = getLobby(matchId);
|
const lobby = getLobby(matchId);
|
||||||
const unrolled = m.participants.filter((p) => p.rollValue === null);
|
const initialUnrolled = m.participants.filter((p) => p.rollValue === null);
|
||||||
if (unrolled.length === 0) return;
|
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) {
|
if (lobby?.isAlive) {
|
||||||
await lobby.chat(`⏰ Rolling timed out — auto-rolling for: ${names}`);
|
await lobby.chat(`⏰ Rolling timed out — auto-rolling for: ${names}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
for (const p of unrolled) {
|
||||||
const autoRoll = Math.floor(Math.random() * 100) + 1;
|
const autoRoll = Math.floor(Math.random() * 100) + 1;
|
||||||
await submitRoll(matchId, p.id, autoRoll);
|
await submitRoll(matchId, p.id, autoRoll);
|
||||||
@@ -138,12 +144,20 @@ export function onMatchStateChange(matchId: string, newState: string) {
|
|||||||
await lobby.chat(`${p.team.name} auto-rolled ${autoRoll}`);
|
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
|
// After auto-rolling, check if state advanced to PICKING
|
||||||
const updated = await getMatchFull(matchId);
|
if (current.state === MATCH_STATES.PICKING) {
|
||||||
if (updated.state === MATCH_STATES.PICKING) {
|
|
||||||
onMatchStateChange(matchId, 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)
|
(a, b) => (a.pickOrder ?? 99) - (b.pickOrder ?? 99)
|
||||||
);
|
);
|
||||||
if (lobby?.isAlive) {
|
if (lobby?.isAlive) {
|
||||||
@@ -414,10 +428,12 @@ function setupChatHandlers(matchId: string, lobby: TournamentLobby) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLastRoll = m.participants.filter((p) => p.rollValue === null).length === 1;
|
||||||
|
|
||||||
await submitRoll(matchId, targetParticipant.id, value);
|
await submitRoll(matchId, targetParticipant.id, value);
|
||||||
console.log(`[Orchestrator] IRC roll: ${username} = ${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 updated = await getMatchFull(matchId);
|
||||||
const remaining = updated.participants.filter((p) => p.rollValue === null);
|
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. ` +
|
`Rolls complete! ${sorted[0]?.team.name} picks first. ` +
|
||||||
`Use !pick <slot> (e.g. !pick NM1). You have 2 minutes.`
|
`Use !pick <slot> (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) {
|
} else if (remaining.length > 0) {
|
||||||
// Tell the other player they still need to roll
|
// Tell the other player they still need to roll
|
||||||
await lobby.chat(
|
await lobby.chat(
|
||||||
|
|||||||
@@ -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;
|
const value = Math.floor(Math.random() * 100) + 1;
|
||||||
await submitRoll(params.id, myParticipant.id, value);
|
await submitRoll(params.id, myParticipant.id, value);
|
||||||
|
|
||||||
@@ -160,7 +161,7 @@ export const actions: Actions = {
|
|||||||
if (lobby) {
|
if (lobby) {
|
||||||
lobby.chat(`${myParticipant.team?.name ?? 'Player'} rolled ${value} (via web)`).catch(() => {});
|
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);
|
const updated = await getMatchFull(params.id);
|
||||||
if (updated.state === 'PICKING') {
|
if (updated.state === 'PICKING') {
|
||||||
const sorted = [...updated.participants].sort(
|
const sorted = [...updated.participants].sort(
|
||||||
@@ -169,6 +170,8 @@ export const actions: Actions = {
|
|||||||
lobby.chat(
|
lobby.chat(
|
||||||
`Rolls complete! ${sorted[0]?.team.name} picks first. Use !pick <slot> (e.g. !pick NM1) or pick in web UI.`
|
`Rolls complete! ${sorted[0]?.team.name} picks first. Use !pick <slot> (e.g. !pick NM1) or pick in web UI.`
|
||||||
).catch(() => {});
|
).catch(() => {});
|
||||||
|
} else if (isLastRoll && updated.participants.every((p: any) => p.rollValue === null)) {
|
||||||
|
lobby.chat(`Tie! All players rolled ${value}. Please !roll again.`).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user