2.9 KiB
tournament-bracket.internal
Purpose
Pure bracket-generation helpers used by TournamentManager. Extracted to
a sibling internal module to keep tournament-manager.ts under the
project's 300-line hard limit while preserving full coverage of the
generation paths through the manager's public surface.
The helpers are deliberately framework-free — they accept an injectable
createMatch factory so the manager retains exclusive ownership of match
ids, OTel counters, and other side-effecting machinery, leaving these
helpers as deterministic pure functions over the participant + match
type domain.
Features
roundName(round, total)— human-readable round labels.shuffleWithRng(arr, rng)— in-place Fisher-Yates via injectableRng.padToPowerOfTwo(participants)— null-padding for single-elim.buildSingleElim(...)— bracket with first-round byes.buildRoundRobin(...)— circle-method scheduling.
Usage: Basic
import { buildSingleElim } from "./tournament-bracket.internal.js";
const rounds = buildSingleElim(participants, (round, position) =>
createMatch(round, position)
);
Usage: Advanced
import {
buildRoundRobin,
shuffleWithRng,
} from "./tournament-bracket.internal.js";
shuffleWithRng(participants, rng);
const rounds = buildRoundRobin(participants, (round, position) => ({
id: nextId(),
round,
position,
participant1: null,
participant2: null,
status: "pending",
result: null,
nextMatchId: null,
}));
API Reference
roundName(round: number, total: number): stringshuffleWithRng<T>(arr: T[], rng: Rng | undefined): voidpadToPowerOfTwo<T>(participants: Participant<T>[]): (Participant<T> | null)[]buildSingleElim<TP, TR>(participants, createMatch): Round<TP, TR>[]buildRoundRobin<TP, TR>(participants, createMatch): Round<TP, TR>[]
Implementation Notes
buildSingleElim throws INVALID_BRACKET_SIZE if the padded size is not
a power of two — this should be unreachable in practice since
padToPowerOfTwo always pads to a power of two, but the guard catches
caller misuse (e.g. handing in a pre-padded list) without silent
corruption of the bracket shape.
buildRoundRobin uses the standard circle method: one participant is
fixed and the others rotate around it. For an odd participant count a
null slot is injected so each round contains exactly floor(n/2)
matches; pairs with null are filtered out before emitting matches so
they don't appear in the bracket. This yields n - 1 rounds for n
even and n rounds for n odd, matching the conventional schedule.
Shuffle is unbiased Fisher-Yates and uses Rng.nextIntInRange —
implementations of Rng that do not provide it (it is optional in the
core contract) leave the order untouched, which is the conservative
default for any tournament harness that prefers explicit error over silent
non-determinism.