123 lines
5.4 KiB
TypeScript
123 lines
5.4 KiB
TypeScript
|
|
/**
|
|||
|
|
* The official 2026 LLBWS bracket (Williamsport, Aug 19–30), transcribed from the PDF.
|
|||
|
|
*
|
|||
|
|
* The printed bracket numbers its games 1–38. Both the routing tests and the layout
|
|||
|
|
* tests check themselves against these numbers, so the transcription lives here rather
|
|||
|
|
* than in either one.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// ── PDF game number ↔ (round, match number) ──────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// Transcribed directly from the 2026 LLBWS bracket. U.S. games take the low match
|
|||
|
|
// numbers in each round, International the high ones.
|
|||
|
|
export const GAME_TO_MATCH: Record<number, { round: string; matchNumber: number }> = {
|
|||
|
|
// Opening Round — U.S. G2,4,6,8 (M1–4); Intl G1,3,5,7 (M5–8)
|
|||
|
|
2: { round: "Opening Round", matchNumber: 1 },
|
|||
|
|
4: { round: "Opening Round", matchNumber: 2 },
|
|||
|
|
6: { round: "Opening Round", matchNumber: 3 },
|
|||
|
|
8: { round: "Opening Round", matchNumber: 4 },
|
|||
|
|
1: { round: "Opening Round", matchNumber: 5 },
|
|||
|
|
3: { round: "Opening Round", matchNumber: 6 },
|
|||
|
|
5: { round: "Opening Round", matchNumber: 7 },
|
|||
|
|
7: { round: "Opening Round", matchNumber: 8 },
|
|||
|
|
// Winners Round 2 — U.S. G10,12; Intl G9,11
|
|||
|
|
10: { round: "Winners Round 2", matchNumber: 1 },
|
|||
|
|
12: { round: "Winners Round 2", matchNumber: 2 },
|
|||
|
|
9: { round: "Winners Round 2", matchNumber: 3 },
|
|||
|
|
11: { round: "Winners Round 2", matchNumber: 4 },
|
|||
|
|
// Elimination Round 1 — U.S. G14,16; Intl G13,15
|
|||
|
|
14: { round: "Elimination Round 1", matchNumber: 1 },
|
|||
|
|
16: { round: "Elimination Round 1", matchNumber: 2 },
|
|||
|
|
13: { round: "Elimination Round 1", matchNumber: 3 },
|
|||
|
|
15: { round: "Elimination Round 1", matchNumber: 4 },
|
|||
|
|
// Winners Semifinals — U.S. G17,19; Intl G18,20
|
|||
|
|
17: { round: "Winners Semifinals", matchNumber: 1 },
|
|||
|
|
19: { round: "Winners Semifinals", matchNumber: 2 },
|
|||
|
|
18: { round: "Winners Semifinals", matchNumber: 3 },
|
|||
|
|
20: { round: "Winners Semifinals", matchNumber: 4 },
|
|||
|
|
// Elimination Round 2 — U.S. G22,24; Intl G21,23
|
|||
|
|
22: { round: "Elimination Round 2", matchNumber: 1 },
|
|||
|
|
24: { round: "Elimination Round 2", matchNumber: 2 },
|
|||
|
|
21: { round: "Elimination Round 2", matchNumber: 3 },
|
|||
|
|
23: { round: "Elimination Round 2", matchNumber: 4 },
|
|||
|
|
// Elimination Round 3 — U.S. G26,28; Intl G25,27
|
|||
|
|
26: { round: "Elimination Round 3", matchNumber: 1 },
|
|||
|
|
28: { round: "Elimination Round 3", matchNumber: 2 },
|
|||
|
|
25: { round: "Elimination Round 3", matchNumber: 3 },
|
|||
|
|
27: { round: "Elimination Round 3", matchNumber: 4 },
|
|||
|
|
// Winners Final — U.S. G30; Intl G29
|
|||
|
|
30: { round: "Winners Final", matchNumber: 1 },
|
|||
|
|
29: { round: "Winners Final", matchNumber: 2 },
|
|||
|
|
// Elimination Round 4 — U.S. G32; Intl G31
|
|||
|
|
32: { round: "Elimination Round 4", matchNumber: 1 },
|
|||
|
|
31: { round: "Elimination Round 4", matchNumber: 2 },
|
|||
|
|
// Elimination Final — U.S. G34; Intl G33
|
|||
|
|
34: { round: "Elimination Final", matchNumber: 1 },
|
|||
|
|
33: { round: "Elimination Final", matchNumber: 2 },
|
|||
|
|
// Bracket Championship — U.S. G36; Intl G35
|
|||
|
|
36: { round: "Bracket Championship", matchNumber: 1 },
|
|||
|
|
35: { round: "Bracket Championship", matchNumber: 2 },
|
|||
|
|
// Finals
|
|||
|
|
37: { round: "Consolation Third Place", matchNumber: 1 },
|
|||
|
|
38: { round: "World Championship", matchNumber: 1 },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export const MATCH_TO_GAME = new Map<string, number>(
|
|||
|
|
Object.entries(GAME_TO_MATCH).map(([game, m]) => [
|
|||
|
|
`${m.round}#${m.matchNumber}`,
|
|||
|
|
Number(game),
|
|||
|
|
])
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
export function gameNumberFor(round: string, matchNumber: number): number {
|
|||
|
|
const game = MATCH_TO_GAME.get(`${round}#${matchNumber}`);
|
|||
|
|
if (game === undefined) throw new Error(`No PDF game for ${round} #${matchNumber}`);
|
|||
|
|
return game;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** Narrows a destination that the test expects to exist. */
|
|||
|
|
export function required<T>(destination: T | null): T {
|
|||
|
|
if (destination === null) throw new Error("Expected a destination, got null");
|
|||
|
|
return destination;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** PDF game number a destination points at. */
|
|||
|
|
export function destinationGame(
|
|||
|
|
destination: { round: string; matchNumber: number } | null
|
|||
|
|
): number {
|
|||
|
|
const d = required(destination);
|
|||
|
|
return gameNumberFor(d.round, d.matchNumber);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* The official bracket printed as feed labels: for each game, which prior game's
|
|||
|
|
* winner (W) or loser (L) fills each slot. `null` = a team seeded in directly.
|
|||
|
|
*
|
|||
|
|
* Transcribed from the PDF. This is the source of truth the routing must reproduce.
|
|||
|
|
*/
|
|||
|
|
export const EXPECTED_SLOTS: Record<number, [string | null, string | null]> = {
|
|||
|
|
// Opening Round — all directly seeded
|
|||
|
|
1: [null, null], 2: [null, null], 3: [null, null], 4: [null, null],
|
|||
|
|
5: [null, null], 6: [null, null], 7: [null, null], 8: [null, null],
|
|||
|
|
// Winners Round 2 — bye team, then an Opening Round winner
|
|||
|
|
9: [null, "W1"], 10: [null, "W2"], 11: [null, "W3"], 12: [null, "W4"],
|
|||
|
|
// Elimination Round 1
|
|||
|
|
13: ["L3", "L5"], 14: ["L4", "L6"], 15: ["L1", "L7"], 16: ["L2", "L8"],
|
|||
|
|
// Winners Semifinals
|
|||
|
|
17: ["W6", "W10"], 18: ["W5", "W9"], 19: ["W12", "W8"], 20: ["W11", "W7"],
|
|||
|
|
// Elimination Round 2
|
|||
|
|
21: ["L9", "W13"], 22: ["L10", "W14"], 23: ["L11", "W15"], 24: ["L12", "W16"],
|
|||
|
|
// Elimination Round 3 — cross-over
|
|||
|
|
25: ["L18", "W23"], 26: ["L17", "W24"], 27: ["L20", "W21"], 28: ["L19", "W22"],
|
|||
|
|
// Winners Final
|
|||
|
|
29: ["W18", "W20"], 30: ["W17", "W19"],
|
|||
|
|
// Elimination Round 4
|
|||
|
|
31: ["W27", "W25"], 32: ["W28", "W26"],
|
|||
|
|
// Elimination Final
|
|||
|
|
33: ["L29", "W31"], 34: ["L30", "W32"],
|
|||
|
|
// Bracket Championship
|
|||
|
|
35: ["W29", "W33"], 36: ["W30", "W34"],
|
|||
|
|
// Finals
|
|||
|
|
37: ["L36", "L35"], 38: ["W36", "W35"],
|
|||
|
|
};
|