104 lines
2.6 KiB
TypeScript
104 lines
2.6 KiB
TypeScript
export interface BracketMatch {
|
|
lid: number;
|
|
round: number;
|
|
order: number | null;
|
|
bye: boolean;
|
|
home_seed?: number;
|
|
away_seed?: number;
|
|
home_from_lid: number | null;
|
|
home_from_loser: boolean;
|
|
away_from_lid: number | null;
|
|
away_from_loser: boolean;
|
|
reset: boolean;
|
|
}
|
|
|
|
export interface BracketTemplate {
|
|
winners: BracketMatch[][];
|
|
losers: BracketMatch[][];
|
|
}
|
|
|
|
export function generateSingleEliminationBracket(teamCount: number): BracketTemplate {
|
|
if (teamCount < 2) {
|
|
throw new Error("Need at least 2 teams for a bracket");
|
|
}
|
|
|
|
const nextPowerOf2 = Math.pow(2, Math.ceil(Math.log2(teamCount)));
|
|
const totalRounds = Math.log2(nextPowerOf2);
|
|
|
|
const byesNeeded = nextPowerOf2 - teamCount;
|
|
const firstRoundMatches = Math.floor(teamCount / 2);
|
|
|
|
const winners: BracketMatch[][] = [];
|
|
let currentLid = 0;
|
|
let currentOrder = 1;
|
|
|
|
for (let round = 0; round < totalRounds; round++) {
|
|
const roundMatches: BracketMatch[] = [];
|
|
const matchesInRound = Math.pow(2, totalRounds - round - 1);
|
|
|
|
for (let matchIndex = 0; matchIndex < matchesInRound; matchIndex++) {
|
|
const match: BracketMatch = {
|
|
lid: currentLid++,
|
|
round,
|
|
order: currentOrder++,
|
|
bye: false,
|
|
home_from_lid: null,
|
|
home_from_loser: false,
|
|
away_from_lid: null,
|
|
away_from_loser: false,
|
|
reset: false,
|
|
};
|
|
|
|
if (round === 0) {
|
|
const homePosition = matchIndex * 2;
|
|
const awayPosition = matchIndex * 2 + 1;
|
|
|
|
if (homePosition < teamCount && awayPosition < teamCount) {
|
|
match.home_seed = homePosition + 1;
|
|
match.away_seed = awayPosition + 1;
|
|
} else if (homePosition < teamCount) {
|
|
match.home_seed = homePosition + 1;
|
|
match.bye = true;
|
|
} else {
|
|
match.bye = true;
|
|
}
|
|
} else {
|
|
const prevRound = winners[round - 1];
|
|
const homeFeedIndex = matchIndex * 2;
|
|
const awayFeedIndex = matchIndex * 2 + 1;
|
|
|
|
if (homeFeedIndex < prevRound.length) {
|
|
match.home_from_lid = prevRound[homeFeedIndex].lid;
|
|
}
|
|
if (awayFeedIndex < prevRound.length) {
|
|
match.away_from_lid = prevRound[awayFeedIndex].lid;
|
|
}
|
|
}
|
|
|
|
roundMatches.push(match);
|
|
}
|
|
|
|
winners.push(roundMatches);
|
|
}
|
|
|
|
return {
|
|
winners,
|
|
losers: [],
|
|
};
|
|
}
|
|
|
|
export function generateGroupMismatchSeeding(
|
|
numGroups: number,
|
|
teamsPerGroup: number
|
|
): number[] {
|
|
const seeding: number[] = [];
|
|
|
|
for (let rank = 0; rank < teamsPerGroup; rank++) {
|
|
for (let group = 0; group < numGroups; group++) {
|
|
seeding.push(group * teamsPerGroup + rank);
|
|
}
|
|
}
|
|
|
|
return seeding;
|
|
}
|