BracketBuilder
app/features/calendar/core/BracketBuilder.tsNo overview comment at the top of the file.
- Imports
- ProgressionSkippedRounds
- Used by
- 1 component
Types
The brackets + progression form values the builder edits.
export interface BuilderValues { brackets: BracketFormValue[]; progression: ProgressionFormValue[]; }
A line of the builder: teams of fromIdx moving on to toIdx, stored as the sourceIdxth source of the destination.
export interface Connection { fromIdx: number; toIdx: number; sourceIdx: number; placements: string; }
One setting shown on a bracket card, see {@link cardFacts}.
export type CardFact = | { type: "GROUPS"; count: number } | { type: "TEAMS_PER_GROUP"; count: number } | { type: "AB_DIVISIONS" } | { type: "ROUNDS"; count: number } | { type: "EARLY_ADVANCE"; wins: number } | { type: "SKIPPED"; round: SkippableRound };
Reason a connection could not be made, see {@link connect}.
export type ConnectError = | "SAME_BRACKET" | "FIRST_BRACKET" | "EARLIER_BRACKET" | "CYCLE";
A group of teams of a bracket sharing a placement, e.g. both semifinal losers of a single elimination bracket.
export interface PlacementTier { placement: number; kind: | "WON_FINAL" | "LOST_FINAL" | "WON_THIRD_PLACE_MATCH" | "LOST_THIRD_PLACE_MATCH" | "LOST_SEMIFINALS" | "LOST_QUARTERFINALS" | "LOST_ROUND_OF" | "WON_GRAND_FINALS" | "LOST_GRAND_FINALS" | "LOST_LOSERS_FINAL" | "LOST_LOSERS_SEMIFINAL" | "OUT_IN_LOSERS_BRACKET" | "GROUP_PLACEMENT" | "STILL_IN" | "UNBEATEN" | "LOST_WINNERS_FINAL" | "ALIVE_WITH_ONE_LOSS"; /** Most teams sharing the placement, in round robin per group. */ maxTeams: number; /** (Elimination split into groups) Most teams sharing the placement in one group. */ maxTeamsPerGroup?: number; /** For `LOST_ROUND_OF`, e.g. 16 for the round of 16. */ roundOf?: number; }
Pixel sizes of the builder's board.
export interface BoardDimensions { cardWidth: number; /** Cards grow from this to fit their content */ minCardHeight: number; columnGap: number; rowGap: number; headerHeight: number; padding: number; /** Height of the empty slot a line skipping a column passes through */ laneHeight: number; }
export interface BoardLayout { /** Top left corner and height of each bracket's card */ cards: Array<Point & { height: number }>; /** Per line of {@link connections}, in the same order */ lines: Array<{ /** Start port, both ends of each lane the line passes, end port */ points: Point[]; labelAt: Point; }>; /** Bottom of the lowest card or lane */ height: number; }
Functions & values
A bracket without sources, teams join it from sign-up. The first bracket always is one.
export function isStartingBracket(values: BuilderValues, bracketIdx: number)
Every line of the builder. Sources pointing to a non-existent bracket are left out, the schema reports them.
export function connections(values: BuilderValues): Connection[]
Builder column of every bracket: starting brackets 0, follow-ups right of all their sources. minColumns keeps a follow-up where it was dropped even before it has any lines.
export function columns( values: BuilderValues, minColumns: ReadonlyArray<number | undefined> = [], ): number[]
Unit tests (2)
Appends a new bracket to the last column holding follow-ups, or to the first follow-up column if there are none. Returned column is where it goes, it has no lines yet to place it there. Without brackets it becomes the first, starting, one.
export function addBracket( values: BuilderValues, name: string, minColumns: ReadonlyArray<number | undefined> = [], ): { values: BuilderValues; column: number }
Unit tests (4)
Default name for a new bracket, nameOf the lowest number not already taken by another bracket's name. Numbering starts from 2 as the first bracket exists already, unless there are no brackets.
export function defaultBracketName( values: BuilderValues, nameOf: (number: number) => string, )
Unit tests (1)
Moves a bracket to a column, the first one making it a starting bracket, and in it above the bracket beforeIdx (null = to the bottom). Brackets of a column are in index order, so the bracket gets a new index if needed for its place, newIdxOf mapping each old index to its new one. Lines from brackets that would no longer be on its left are removed, the count of them is returned. The first bracket always stays the first starting bracket.
export function moveBracket( values: BuilderValues, bracketIdx: number, target: { column: number; beforeIdx: number | null }, minColumns: ReadonlyArray<number | undefined> = [], ): { values: BuilderValues; removedConnectionCount: number; newIdxOf: (idx: number) => number; }
Unit tests (7)
Why teams of fromIdx can't be sent on to toIdx, or null if they can. Lines go from left to right, so the destination has to be in the same column (it then moves right) or a later one.
export function connectError( values: BuilderValues, fromIdx: number, toIdx: number, minColumns: ReadonlyArray<number | undefined> = [], ): ConnectError | null
Sends teams of fromIdx on to toIdx, preselecting the next placements no other line from fromIdx takes.
export function connect( values: BuilderValues, fromIdx: number, toIdx: number, minColumns: ReadonlyArray<number | undefined> = [], ): { values: BuilderValues; error?: ConnectError }
Unit tests (4)
Removes a line. The destination stays a follow-up, without sources until it gets a new line.
export function disconnect( values: BuilderValues, toIdx: number, sourceIdx: number, ): BuilderValues
Deletes a bracket with its lines, shifting the source indexes of the brackets after it. The first bracket can't be deleted.
export function removeBracket( values: BuilderValues, bracketIdx: number, ): BuilderValues
Drops placements of lines their source bracket no longer has, e.g. a 4th place after its groups shrunk to 3 or knocked out teams after it changed to round robin. A line left with none gets the default picks instead. Returns values as is when every placement still exists.
export function fitPlacementsToSources(values: BuilderValues): BuilderValues
Unit tests (5)
Placement tiers of a bracket a line can pick from. Placements of the progression count these tiers, not team positions: in single elimination without a third place match 3 means both semifinal losers. Swiss lists none, its standings are picked as a range. maxTeams limits elimination tiers to what fits.
export function placementTiers( bracket: BracketFormValue, maxTeams: number | null = null, ): PlacementTier[]
Unit tests (5)
Single or double elimination split into groups.
export function isGrouped(bracket: BracketFormValue)
Settings of a bracket worth showing on its card: the shape of its format and rounds left unplayed. A skipped round is listed only when the rounds it depends on are played, skipping the semifinals says enough without the finals.
export function cardFacts( bracket: BracketFormValue, { isStarting }: { isStarting: boolean }, ): CardFact[]
Unit tests (1)
- $why× 5
Losers rounds an "knocked out early" line can take, -1 being the first round.
export function knockedOutRoundOptions(bracket: BracketFormValue)
Most teams each bracket can get, or null when it depends on how many teams sign up. A follow-up fed the top 8 of another bracket gets at most 8, one fed group winners as many as there end up being groups.
export function maxTeamCounts(values: BuilderValues): Array<number | null>
Unit tests (2)
Positions of the cards and lines of the board. A line skipping columns passes each through a lane, an empty slot between the column's cards, so that it never crosses a card. A lane goes where the line comes in, pushing the cards below it down. Cards grow to fit their content, cardHeights being their measured heights.
export function boardLayout( values: BuilderValues, bracketColumns: number[], dimensions: BoardDimensions, cardHeights: Array<number | undefined> = [], ): BoardLayout
Unit tests (3)
Unit tests not about one export (4)
Not shown, only used by tests: defaultPlacements