/** * Bracket Template System * * Defines pre-configured tournament bracket structures for various sports. * Templates specify rounds, match counts, and which rounds contribute to fantasy scoring. */ export interface BracketRound { /** Display name for this round (e.g., "First Four", "Elite Eight") */ name: string; /** Number of matches in this round */ matchCount: number; /** Name of the round that winners advance to (null for championship) */ feedsInto: string | null; /** Whether this round affects fantasy points (false = 0 points per Q20) */ isScoring: boolean; } export interface BracketTemplate { /** Unique identifier for this template */ id: string; /** Human-readable name */ name: string; /** Total number of teams in the bracket */ totalTeams: number; /** Ordered list of rounds from earliest to championship */ rounds: BracketRound[]; /** Round name where fantasy scoring begins */ scoringStartsAtRound: string; } /** * Simple 4-team bracket * Semifinals → Finals * All rounds score fantasy points */ export const SIMPLE_4: BracketTemplate = { id: "simple_4", name: "Simple 4-Team Bracket", totalTeams: 4, scoringStartsAtRound: "Semifinals", rounds: [ { name: "Semifinals", matchCount: 2, feedsInto: "Finals", isScoring: true, // Losers share 3rd-4th }, { name: "Finals", matchCount: 1, feedsInto: null, isScoring: true, // Winner 1st, Loser 2nd }, ], }; /** * Simple 8-team bracket * Quarterfinals → Semifinals → Finals * All rounds score fantasy points */ export const SIMPLE_8: BracketTemplate = { id: "simple_8", name: "Simple 8-Team Bracket", totalTeams: 8, scoringStartsAtRound: "Quarterfinals", rounds: [ { name: "Quarterfinals", matchCount: 4, feedsInto: "Semifinals", isScoring: true, // Losers share 5th-8th }, { name: "Semifinals", matchCount: 2, feedsInto: "Finals", isScoring: true, // Losers share 3rd-4th }, { name: "Finals", matchCount: 1, feedsInto: null, isScoring: true, // Winner 1st, Loser 2nd }, ], }; /** * Simple 16-team bracket * Round of 16 → Quarterfinals → Semifinals → Finals * Only Quarterfinals and beyond score points (top 8) */ export const SIMPLE_16: BracketTemplate = { id: "simple_16", name: "Simple 16-Team Bracket", totalTeams: 16, scoringStartsAtRound: "Quarterfinals", rounds: [ { name: "Round of 16", matchCount: 8, feedsInto: "Quarterfinals", isScoring: false, // Early elimination = 0 points }, { name: "Quarterfinals", matchCount: 4, feedsInto: "Semifinals", isScoring: true, // Losers share 5th-8th }, { name: "Semifinals", matchCount: 2, feedsInto: "Finals", isScoring: true, // Losers share 3rd-4th }, { name: "Finals", matchCount: 1, feedsInto: null, isScoring: true, // Winner 1st, Loser 2nd }, ], }; /** * Simple 32-team bracket * Round of 32 → Round of 16 → Quarterfinals → Semifinals → Finals * Only Quarterfinals and beyond score points (top 8) */ export const SIMPLE_32: BracketTemplate = { id: "simple_32", name: "Simple 32-Team Bracket", totalTeams: 32, scoringStartsAtRound: "Quarterfinals", rounds: [ { name: "Round of 32", matchCount: 16, feedsInto: "Round of 16", isScoring: false, // Early elimination = 0 points }, { name: "Round of 16", matchCount: 8, feedsInto: "Quarterfinals", isScoring: false, // Early elimination = 0 points }, { name: "Quarterfinals", matchCount: 4, feedsInto: "Semifinals", isScoring: true, // Losers share 5th-8th }, { name: "Semifinals", matchCount: 2, feedsInto: "Finals", isScoring: true, // Losers share 3rd-4th }, { name: "Finals", matchCount: 1, feedsInto: null, isScoring: true, // Winner 1st, Loser 2nd }, ], }; /** * NCAA March Madness (68 teams) * First Four (play-in) → Round of 64 → Round of 32 → Sweet Sixteen → Elite Eight → Final Four → Championship * Only Elite Eight and beyond score points per Q18 */ export const NCAA_68: BracketTemplate = { id: "ncaa_68", name: "NCAA March Madness (68 teams)", totalTeams: 68, scoringStartsAtRound: "Elite Eight", rounds: [ { name: "First Four", matchCount: 4, feedsInto: "Round of 64", isScoring: false, // Play-in games don't score }, { name: "Round of 64", matchCount: 32, feedsInto: "Round of 32", isScoring: false, }, { name: "Round of 32", matchCount: 16, feedsInto: "Sweet Sixteen", isScoring: false, }, { name: "Sweet Sixteen", matchCount: 8, feedsInto: "Elite Eight", isScoring: false, }, { name: "Elite Eight", matchCount: 4, feedsInto: "Final Four", isScoring: true, // Losers share 5th-8th }, { name: "Final Four", matchCount: 2, feedsInto: "Championship", isScoring: true, // Losers share 3rd-4th }, { name: "Championship", matchCount: 1, feedsInto: null, isScoring: true, // Winner 1st, Loser 2nd }, ], }; /** * NFL Playoffs (14 teams) * Wild Card → Divisional → Conference Championship → Super Bowl * Top 2 seeds get byes (skip Wild Card) * Scoring starts at Divisional (top 8) */ export const NFL_14: BracketTemplate = { id: "nfl_14", name: "NFL Playoffs (14 teams)", totalTeams: 14, scoringStartsAtRound: "Divisional", rounds: [ { name: "Wild Card", matchCount: 6, feedsInto: "Divisional", isScoring: false, // 12 teams play, 2 have byes }, { name: "Divisional", matchCount: 4, feedsInto: "Conference Championship", isScoring: true, // Quarterfinals - Losers share 5th-8th }, { name: "Conference Championship", matchCount: 2, feedsInto: "Super Bowl", isScoring: true, // Semifinals - Losers share 3rd-4th }, { name: "Super Bowl", matchCount: 1, feedsInto: null, isScoring: true, // Finals - Winner 1st, Loser 2nd }, ], }; /** * AFL Finals (10 teams with Wildcard Round from 2026) * Complex bracket with double-chance system for top 6 teams * * Structure: * - Wildcard Round: 7v10, 8v9 (losers eliminated with 0 points) * - Week 1 Finals: * - Qualifying Finals: 1v4, 2v3 (losers get second chance) * - Elimination Finals: 5v8(wildcard winner), 6v7(wildcard winner) (losers share 7th-8th) * - Week 2: Semi-Finals (QF losers vs EF winners, losers share 5th-6th) * - Week 3: Preliminary Finals (QF winners vs SF winners, losers share 3rd-4th) * - Week 4: Grand Final (1st vs 2nd) * * Note: This is NOT a simple single-elimination tree - it has double-chance paths */ export const AFL_10: BracketTemplate = { id: "afl_10", name: "AFL Finals (10 teams with Wildcard)", totalTeams: 10, scoringStartsAtRound: "Elimination Finals", rounds: [ { name: "Wildcard Round", matchCount: 2, feedsInto: "Elimination Finals", isScoring: false, // Losers get 0 points (9th-10th) }, { name: "Qualifying Finals", matchCount: 2, feedsInto: "Preliminary Finals", // Winners get bye isScoring: false, // Losers get second chance (go to Semi-Finals) }, { name: "Elimination Finals", matchCount: 2, feedsInto: "Semi-Finals", isScoring: true, // Losers share 7th-8th }, { name: "Semi-Finals", matchCount: 2, feedsInto: "Preliminary Finals", isScoring: true, // Losers share 5th-6th }, { name: "Preliminary Finals", matchCount: 2, feedsInto: "Grand Final", isScoring: true, // Losers share 3rd-4th }, { name: "Grand Final", matchCount: 1, feedsInto: null, isScoring: true, // Winner 1st, Loser 2nd }, ], }; /** * All available bracket templates */ export const BRACKET_TEMPLATES: Record = { simple_4: SIMPLE_4, simple_8: SIMPLE_8, simple_16: SIMPLE_16, simple_32: SIMPLE_32, ncaa_68: NCAA_68, nfl_14: NFL_14, afl_10: AFL_10, }; /** * Get a bracket template by ID */ export function getBracketTemplate(id: string): BracketTemplate | undefined { return BRACKET_TEMPLATES[id]; } /** * Get all bracket templates as an array */ export function getAllBracketTemplates(): BracketTemplate[] { return Object.values(BRACKET_TEMPLATES); } /** * Helper to determine scoring round type based on match count in scoring rounds * Used for determining placement sharing (5-8th, 3-4th, 1-2nd) * * Special handling for AFL finals system which has specific placement rules */ export function getScoringRoundType( roundName: string, template: BracketTemplate ): "quarterfinals" | "semifinals" | "finals" | null { const round = template.rounds.find((r) => r.name === roundName); if (!round || !round.isScoring) return null; // Special handling for AFL finals if (template.id === "afl_10") { if (roundName === "Elimination Finals") return "quarterfinals"; // Losers share 7-8th if (roundName === "Semi-Finals") return "quarterfinals"; // Losers share 5-6th (custom mapping) if (roundName === "Preliminary Finals") return "semifinals"; // Losers share 3-4th if (roundName === "Grand Final") return "finals"; // 1st and 2nd return null; } // Standard logic for other templates // Determine type by match count in scoring round if (round.matchCount === 4) return "quarterfinals"; // 8 teams, losers share 5-8th if (round.matchCount === 2) return "semifinals"; // 4 teams, losers share 3-4th if (round.matchCount === 1) return "finals"; // 2 teams, 1st and 2nd return null; }