cross mix knockout seeds

This commit is contained in:
yohlo
2026-03-01 19:36:26 -06:00
parent c5b3ad50ca
commit 873ca3e4c4
4 changed files with 84 additions and 20 deletions

View File

@@ -37,16 +37,31 @@ export const MatchCard: React.FC<MatchCardProps> = ({
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(

View File

@@ -94,13 +94,23 @@ const SetupGroupStage: React.FC<SetupGroupStageProps> = ({
const groupNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
const seedLabels: Record<number, string> = {};
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<number, number> = {};

View File

@@ -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({

View File

@@ -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<string, GroupConfigOption>();
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;
}