/** * AFL Wildcard Round → Elimination Finals re-seeding. * * The Wildcard Round is drawn 7 v 10 and 8 v 9, and its two winners fill the open slots * in the Elimination Finals opposite the 5th and 6th seeds. Those slots are NOT a fixed * crossover: the winners are re-seeded by ladder position, exactly as the classic final * eight pairs 5 v 8 and 6 v 7 — the higher seed of the two hosts meets the lower-ranked * winner. So 5th plays whichever winner finished further down the ladder and 6th plays * the other, whichever Wildcard game each came out of. * * Worked example: 10th beats 7th and 9th beats 8th. A fixed crossover would send the * 7v10 winner (10th) to 6th and the 8v9 winner (9th) to 5th — handing the higher host * the better opponent. Re-seeded, 5th plays 10th and 6th plays 9th. */ /** Seeds drawn into each Wildcard Round match, in [participant1, participant2] order. */ export const AFL_WILDCARD_DRAW: Readonly> = { 1: [7, 10], 2: [8, 9], }; /** Seed hosting each Elimination Finals match (its participant1 slot). */ export const AFL_ELIMINATION_HOSTS: Readonly> = { 1: 5, 2: 6, }; export interface AflWildcardResult { matchNumber: number; /** Slot the winner occupied, or null while the match is still to be played. */ winnerSlot: 1 | 2 | null; } export interface AflWildcardPlacement { wildcardMatchNumber: number; /** Seed of the Wildcard winner being placed. */ seed: number; eliminationMatchNumber: number; } /** * Decide which Elimination Final each decided Wildcard winner belongs in. * * A winner is only placed once its destination is settled whichever way the other * Wildcard game falls, so results can be entered in either order: * - 7th winning match 1 outranks both possible match 2 winners → always meets 6th. * - 10th winning match 1 is outranked by both → always meets 5th. * - A match 2 winner (8th or 9th) sits between them, so it is held back until match 1 * is decided rather than being placed and then moved. * * Undecided winners are simply omitted; the caller fills the slots it is handed and * leaves the rest TBD. */ export function resolveAflWildcardPlacements( results: readonly AflWildcardResult[] ): AflWildcardPlacement[] { const entries = results.map((result) => { const draw = AFL_WILDCARD_DRAW[result.matchNumber]; if (!draw) { throw new Error(`Unknown AFL Wildcard Round match number ${result.matchNumber}`); } return { matchNumber: result.matchNumber, seed: result.winnerSlot === null ? null : draw[result.winnerSlot - 1], // Every seed the match could still send through — one entry once it is decided. possibleSeeds: result.winnerSlot === null ? [...draw] : [draw[result.winnerSlot - 1]], }; }); // Best-ranked winner takes the weakest host, so order the hosts worst seed first. const hostsWorstFirst = Object.keys(AFL_ELIMINATION_HOSTS) .map(Number) .toSorted((a, b) => AFL_ELIMINATION_HOSTS[b] - AFL_ELIMINATION_HOSTS[a]); const placements: AflWildcardPlacement[] = []; for (const entry of entries) { const seed = entry.seed; if (seed === null) continue; const others = entries.filter((other) => other !== entry); const outranks = (other: (typeof entries)[number]) => other.possibleSeeds.every((s) => s < seed); const outrankedBy = (other: (typeof entries)[number]) => other.possibleSeeds.every((s) => s > seed); // This winner's rank is only knowable while every other one sits wholly above or // wholly below it — an undecided game straddling this seed leaves it unplaceable. if (!others.every((other) => outranks(other) || outrankedBy(other))) continue; const rank = others.filter(outranks).length; const eliminationMatchNumber = hostsWorstFirst[rank]; if (eliminationMatchNumber === undefined) { throw new Error(`No Elimination Finals slot for AFL Wildcard winner ranked ${rank + 1}`); } placements.push({ wildcardMatchNumber: entry.matchNumber, seed, eliminationMatchNumber }); } return placements; }