diff --git a/src/features/bracket/components/match-card.tsx b/src/features/bracket/components/match-card.tsx index 923ac17..97baa5b 100644 --- a/src/features/bracket/components/match-card.tsx +++ b/src/features/bracket/components/match-card.tsx @@ -37,16 +37,31 @@ export const MatchCard: React.FC = ({ if (!seed || !groupConfig) return undefined; const groupNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; - const teamsPerRank = groupConfig.num_groups; + const numGroups = groupConfig.num_groups; + const advancePerGroup = groupConfig.advance_per_group; - const rankIndex = Math.floor((seed - 1) / teamsPerRank); - const groupIndex = (seed - 1) % teamsPerRank; + const pairIndex = Math.floor((seed - 1) / 2); + const isFirstInPair = (seed - 1) % 2 === 0; - const rank = rankIndex + 1; - const groupName = groupNames[groupIndex] || `${groupIndex + 1}`; - const rankSuffix = rank === 1 ? '1st' : rank === 2 ? '2nd' : rank === 3 ? '3rd' : `${rank}th`; + if (isFirstInPair) { + const groupIndex = pairIndex % numGroups; + const rankIndex = Math.floor(pairIndex / numGroups); - return `${groupName} ${rankSuffix}`; + const rank = rankIndex + 1; + const groupName = groupNames[groupIndex] || `${groupIndex + 1}`; + const rankSuffix = rank === 1 ? '1st' : rank === 2 ? '2nd' : rank === 3 ? '3rd' : `${rank}th`; + + return `${groupName} ${rankSuffix}`; + } else { + const groupIndex = (pairIndex + 1) % numGroups; + const rankIndex = advancePerGroup - 1 - Math.floor(pairIndex / numGroups); + + const rank = rankIndex + 1; + const groupName = groupNames[groupIndex] || `${groupIndex + 1}`; + const rankSuffix = rank === 1 ? '1st' : rank === 2 ? '2nd' : rank === 3 ? '3rd' : `${rank}th`; + + return `${groupName} ${rankSuffix}`; + } }, [groupConfig]); const homeSlot = useMemo( diff --git a/src/features/tournaments/components/setup-group-stage.tsx b/src/features/tournaments/components/setup-group-stage.tsx index fd327bf..928ffa7 100644 --- a/src/features/tournaments/components/setup-group-stage.tsx +++ b/src/features/tournaments/components/setup-group-stage.tsx @@ -94,13 +94,23 @@ const SetupGroupStage: React.FC = ({ const groupNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; const seedLabels: Record = {}; + const totalTeams = selectedConfig.num_groups * selectedConfig.advance_per_group; let seedIndex = 1; - for (let rank = 1; rank <= selectedConfig.advance_per_group; rank++) { - for (let groupIdx = 0; groupIdx < selectedConfig.num_groups; groupIdx++) { - const groupName = groupNames[groupIdx] || `Group ${groupIdx + 1}`; - const rankSuffix = rank === 1 ? '1st' : rank === 2 ? '2nd' : rank === 3 ? '3rd' : `${rank}th`; - seedLabels[seedIndex++] = `${groupName} ${rankSuffix}`; - } + + for (let i = 0; i < totalTeams / 2; i++) { + const group1 = i % selectedConfig.num_groups; + const rankIndex1 = Math.floor(i / selectedConfig.num_groups); + const rank1 = rankIndex1 + 1; + const groupName1 = groupNames[group1] || `Group ${group1 + 1}`; + const rankSuffix1 = rank1 === 1 ? '1st' : rank1 === 2 ? '2nd' : rank1 === 3 ? '3rd' : `${rank1}th`; + seedLabels[seedIndex++] = `${groupName1} ${rankSuffix1}`; + + const group2 = (i + 1) % selectedConfig.num_groups; + const rankIndex2 = selectedConfig.advance_per_group - 1 - rankIndex1; + const rank2 = rankIndex2 + 1; + const groupName2 = groupNames[group2] || `Group ${group2 + 1}`; + const rankSuffix2 = rank2 === 1 ? '1st' : rank2 === 2 ? '2nd' : rank2 === 3 ? '3rd' : `${rank2}th`; + seedLabels[seedIndex++] = `${groupName2} ${rankSuffix2}`; } const ordersMap: Record = {}; diff --git a/src/features/tournaments/server.ts b/src/features/tournaments/server.ts index f720745..8fdbf81 100644 --- a/src/features/tournaments/server.ts +++ b/src/features/tournaments/server.ts @@ -502,12 +502,30 @@ export const generateKnockoutBracket = createServerFn() const orderedTeamIds: string[] = []; const maxRank = tournament.group_config.advance_per_group; + const numGroups = tournament.group_config.num_groups; - for (let rank = 1; rank <= maxRank; rank++) { - const teamsAtRank = qualifiedTeams - .filter(t => t.rank === rank) - .sort((a, b) => a.groupOrder - b.groupOrder); - orderedTeamIds.push(...teamsAtRank.map(t => t.teamId)); + const teamsByGroup: string[][] = []; + for (let g = 0; g < numGroups; g++) { + teamsByGroup[g] = []; + } + + for (const qualified of qualifiedTeams) { + teamsByGroup[qualified.groupOrder][qualified.rank - 1] = qualified.teamId; + } + + const totalTeams = numGroups * maxRank; + for (let i = 0; i < totalTeams / 2; i++) { + const group1 = i % numGroups; + const rankIndex1 = Math.floor(i / numGroups); + + const group2 = (i + 1) % numGroups; + const rankIndex2 = maxRank - 1 - rankIndex1; + + const team1 = teamsByGroup[group1]?.[rankIndex1]; + const team2 = teamsByGroup[group2]?.[rankIndex2]; + + if (team1) orderedTeamIds.push(team1); + if (team2) orderedTeamIds.push(team2); } const teamCount = orderedTeamIds.length; @@ -673,6 +691,7 @@ export const generateGroupStage = createServerFn() createdGroups.push(group); const teamIds = assignment.teamIds; + for (let i = 0; i < teamIds.length; i++) { for (let j = i + 1; j < teamIds.length; j++) { groupStageMatches.push({ diff --git a/src/features/tournaments/utils/group-config.ts b/src/features/tournaments/utils/group-config.ts index 96963a8..0b02e82 100644 --- a/src/features/tournaments/utils/group-config.ts +++ b/src/features/tournaments/utils/group-config.ts @@ -34,7 +34,10 @@ export function calculateGroupConfigurations(teamCount: number): GroupConfigOpti if (remainder > numGroups) continue; const groupsWithExtra = remainder; - const matchesGuaranteed = teamsPerGroup - 1; + + const groupsAtBaseSize = numGroups - groupsWithExtra; + const minGroupSize = groupsAtBaseSize > 0 ? teamsPerGroup : teamsPerGroup + 1; + const matchesGuaranteed = minGroupSize - 1; for (let advancePerGroup = 1; advancePerGroup <= Math.min(3, teamsPerGroup - 1); advancePerGroup++) { const teamsAdvancing = numGroups * advancePerGroup; @@ -77,7 +80,24 @@ export function calculateGroupConfigurations(teamCount: number): GroupConfigOpti } } - return configs.sort((a, b) => { + const uniqueConfigs = new Map(); + + for (const config of configs) { + const groupSizes: number[] = []; + for (let i = 0; i < config.num_groups; i++) { + const size = config.teams_per_group + (i < config.groups_with_extra ? 1 : 0); + groupSizes.push(size); + } + groupSizes.sort((a, b) => b - a); + + const key = `${groupSizes.join(',')}_advance${config.advance_per_group}`; + + if (!uniqueConfigs.has(key)) { + uniqueConfigs.set(key, config); + } + } + + return Array.from(uniqueConfigs.values()).sort((a, b) => { if (a.matches_guaranteed !== b.matches_guaranteed) { return b.matches_guaranteed - a.matches_guaranteed; }