import { ANNEX_C } from "./annexc-data"; import { GroupId, GroupTable, ThirdPlaceRow } from "./types"; // Die festen Paarungen der Runde der letzten 32 (FIFA-Spielplan, Annex zur Auslosung). // Quelle: FIFA WM 2026 Wettbewerbsregeln, Spiele 73-88. // "W"=Sieger, "R"=Zweiter, "3"=bester Drittplatzierter (Gruppe per Annex C bestimmt). export interface BracketSlot { type: "W" | "R" | "3"; group?: GroupId; // bei W/R: feste Gruppe thirdPool?: GroupId[]; // bei 3: mögliche Quellgruppen (Info / UI) } export interface R32Match { matchNumber: number; home: BracketSlot; away: BracketSlot; } // Die acht Gruppensieger, die einen Drittplatzierten bekommen — in der // Spaltenreihenfolge von Annex C: 1A, 1B, 1D, 1E, 1G, 1I, 1K, 1L. export const ANNEX_WINNER_ORDER: GroupId[] = ["A", "B", "D", "E", "G", "I", "K", "L"]; // Statische R32-Struktur. thirdPool dient nur der Anzeige vor Turnierende. export const R32: R32Match[] = [ { matchNumber: 73, home: { type: "R", group: "A" }, away: { type: "R", group: "B" } }, { matchNumber: 74, home: { type: "W", group: "E" }, away: { type: "3", thirdPool: ["A","B","C","D","F"] } }, { matchNumber: 75, home: { type: "W", group: "F" }, away: { type: "R", group: "C" } }, { matchNumber: 76, home: { type: "W", group: "C" }, away: { type: "R", group: "F" } }, { matchNumber: 77, home: { type: "W", group: "I" }, away: { type: "3", thirdPool: ["C","D","F","G","H"] } }, { matchNumber: 78, home: { type: "R", group: "E" }, away: { type: "R", group: "I" } }, { matchNumber: 79, home: { type: "W", group: "A" }, away: { type: "3", thirdPool: ["C","E","F","H","I"] } }, { matchNumber: 80, home: { type: "W", group: "L" }, away: { type: "3", thirdPool: ["E","H","I","J","K"] } }, { matchNumber: 81, home: { type: "W", group: "D" }, away: { type: "3", thirdPool: ["B","E","F","I","J"] } }, { matchNumber: 82, home: { type: "W", group: "G" }, away: { type: "3", thirdPool: ["A","E","H","I","J"] } }, { matchNumber: 83, home: { type: "R", group: "K" }, away: { type: "R", group: "L" } }, { matchNumber: 84, home: { type: "W", group: "H" }, away: { type: "R", group: "J" } }, { matchNumber: 85, home: { type: "W", group: "B" }, away: { type: "3", thirdPool: ["E","F","G","I","J"] } }, { matchNumber: 86, home: { type: "W", group: "J" }, away: { type: "R", group: "H" } }, { matchNumber: 87, home: { type: "W", group: "K" }, away: { type: "3", thirdPool: ["D","E","I","J","L"] } }, { matchNumber: 88, home: { type: "R", group: "D" }, away: { type: "R", group: "G" } }, ]; // Folgerunden: jedes Spiel speist sich aus zwei Vorspielen (FIFA-Spielplan). export interface KnockoutMatch { matchNumber: number; stage: "R16" | "QF" | "SF" | "3RD" | "FINAL"; fromHome: number; // Match-Nr des Vorspiels (Sieger), bei 3RD: Verlierer fromAway: number; losers?: boolean; // true beim Spiel um Platz 3 } export const LATER_ROUNDS: KnockoutMatch[] = [ { matchNumber: 89, stage: "R16", fromHome: 74, fromAway: 77 }, { matchNumber: 90, stage: "R16", fromHome: 73, fromAway: 75 }, { matchNumber: 91, stage: "R16", fromHome: 76, fromAway: 78 }, { matchNumber: 92, stage: "R16", fromHome: 79, fromAway: 80 }, { matchNumber: 93, stage: "R16", fromHome: 83, fromAway: 84 }, { matchNumber: 94, stage: "R16", fromHome: 81, fromAway: 82 }, { matchNumber: 95, stage: "R16", fromHome: 86, fromAway: 88 }, { matchNumber: 96, stage: "R16", fromHome: 85, fromAway: 87 }, { matchNumber: 97, stage: "QF", fromHome: 89, fromAway: 90 }, { matchNumber: 98, stage: "QF", fromHome: 93, fromAway: 94 }, { matchNumber: 99, stage: "QF", fromHome: 91, fromAway: 92 }, { matchNumber: 100, stage: "QF", fromHome: 95, fromAway: 96 }, { matchNumber: 101, stage: "SF", fromHome: 97, fromAway: 98 }, { matchNumber: 102, stage: "SF", fromHome: 99, fromAway: 100 }, { matchNumber: 103, stage: "3RD", fromHome: 101, fromAway: 102, losers: true }, { matchNumber: 104, stage: "FINAL", fromHome: 101, fromAway: 102 }, ]; // Ergebnis der Annex-C-Auflösung: pro Gruppensieger der zugeordnete Dritte. export type ThirdAssignment = Partial>; // Normalisiert die qualifizierten Gruppen zum Annex-C-Schlüssel (alphabetisch). export function annexKey(qualifiedGroups: GroupId[]): string { return [...qualifiedGroups].sort().join(""); } // Schlägt die Zuordnung in Annex C nach. Gibt {Gruppensieger -> 3.Gruppe} zurück. // Liefert null, wenn nicht genau 8 Gruppen oder kein Treffer. export function resolveAnnexC(qualifiedGroups: GroupId[]): ThirdAssignment | null { if (qualifiedGroups.length !== 8) return null; const key = annexKey(qualifiedGroups); const value = ANNEX_C[key]; if (!value) return null; const assignment: ThirdAssignment = {}; for (let i = 0; i < ANNEX_WINNER_ORDER.length; i++) { assignment[ANNEX_WINNER_ORDER[i]] = value[i] as GroupId; } return assignment; } // Bestimmt die qualifizierten Drittplatzierten-Gruppen aus der Tabelle. export function qualifiedThirdGroups(thirds: ThirdPlaceRow[]): GroupId[] { return thirds.filter((t) => t.qualifies).map((t) => t.group); } // Hilfslabel für einen Slot, abhängig davon ob die Annex-C-Zuordnung schon feststeht. export function slotLabel( slot: BracketSlot, assignment: ThirdAssignment | null, winnerGroupForThisMatch?: GroupId, ): string { if (slot.type === "W") return `Sieger ${slot.group}`; if (slot.type === "R") return `Zweiter ${slot.group}`; // 3. Platz if (assignment && winnerGroupForThisMatch && assignment[winnerGroupForThisMatch]) { return `3. der Gruppe ${assignment[winnerGroupForThisMatch]}`; } return `3. ${slot.thirdPool?.join("/") ?? "?"}`; }