buildInitialBracket<P> static method
Automatically generates the complete tournament round tree (List of Match Lists) starting from a raw list of players/competitors.
players: List of participants. Can be of type Player or custom types.seeds: Optional list of seeded player names or objects in order of priority (Seed 1, Seed 2...). Seeded players are placed in ideal bracket positions so they do not meet early.matchLabelPrefix: Prefix for match labels (e.g. "Trận" or "Match").
Implementation
static List<List<MatchModel<P>>> buildInitialBracket<P>({
required List<P> players,
List<P>? seeds,
String matchLabelPrefix = 'M',
String Function(P)? getPlayerName,
bool includeFinal = true,
}) {
final nameGetter =
getPlayerName ??
(p) {
if (p is Player) return p.name;
try {
return (p as dynamic).name.toString();
} catch (_) {
return p.toString();
}
};
final int totalCount = players.length;
if (totalCount == 0) return [];
final int bracketSize = nextPowerOfTwo(totalCount);
final int numByes = bracketSize - totalCount;
// Create a copy of seeds and normal players
final List<P> seededList = seeds != null ? List<P>.from(seeds) : [];
final List<P> unseededList = List<P>.from(players);
for (final seed in seededList) {
unseededList.removeWhere((p) => nameGetter(p) == nameGetter(seed));
}
// Generate bracket position map using seeding order
// seedingOrder contains values like [1, 8, 5, 4, 3, 6, 7, 2]
final List<int> seedingOrder = generateSeedingOrder(bracketSize);
// Prepare list of size bracketSize
// We place seeded players at their corresponding seed positions first.
// The rest of the spots are filled with unseeded players, and the remaining spots are BYEs.
// BYEs are paired with the highest seeds. So the spots that correspond to lowest seeds (which pair with highest seeds) are filled with BYE.
final List<P?> bracketSlots = List<P?>.filled(bracketSize, null);
// Let's identify which seed rank gets a BYE.
// If there are B BYEs, they should be paired with the highest seeds.
// For a seed S, its opponent in Round 1 is at seed rank (bracketSize + 1 - S).
// So the B lowest seed ranks (from bracketSize - B + 1 to bracketSize) should be BYEs.
final Set<int> byeSeedRanks = {};
for (int i = 0; i < numByes; i++) {
byeSeedRanks.add(bracketSize - i);
}
// Now distribute players into the slots
int unseededIndex = 0;
for (int i = 0; i < bracketSize; i++) {
final int seedRank = seedingOrder[i];
if (byeSeedRanks.contains(seedRank)) {
// This slot is a BYE
bracketSlots[i] = null;
} else {
// Place seeded player if available
if (seedRank - 1 < seededList.length) {
bracketSlots[i] = seededList[seedRank - 1];
} else {
// Place unseeded player
if (unseededIndex < unseededList.length) {
bracketSlots[i] = unseededList[unseededIndex++];
} else {
bracketSlots[i] = null;
}
}
}
}
// Generate Round 1 Matches
final List<MatchModel<P>> round1Matches = [];
int matchId = 1;
for (int i = 0; i < bracketSize; i += 2) {
final comp1 = bracketSlots[i];
final comp2 = bracketSlots[i + 1];
final competitors = <P>[];
final scores = <int>[];
MatchStatus status = MatchStatus.scheduled;
if (comp1 != null) competitors.add(comp1);
if (comp2 != null) competitors.add(comp2);
// Walkover logic if one competitor is BYE
if (comp1 != null && comp2 == null) {
scores.addAll([1, 0]); // 1-0 for comp1
status = MatchStatus.completed;
} else if (comp1 == null && comp2 != null) {
scores.addAll([0, 1]); // 0-1 for comp2
status = MatchStatus.completed;
} else {
scores.addAll([0, 0]);
}
round1Matches.add(
MatchModel<P>(
id: matchId++,
label: '$matchLabelPrefix$matchId',
table: 'Tàn ${((matchId - 1) / 2).ceil()}',
time: 'TBD',
competitors: competitors,
scores: scores,
status: status,
),
);
}
final List<List<MatchModel<P>>> rounds = [round1Matches];
// Generate subsequent rounds with placeholder matches
int currentRoundMatchCount = round1Matches.length;
final int limit = includeFinal ? 1 : 2;
while (currentRoundMatchCount > limit) {
currentRoundMatchCount ~/= 2;
final List<MatchModel<P>> nextRoundMatches = [];
for (int i = 0; i < currentRoundMatchCount; i++) {
nextRoundMatches.add(
MatchModel<P>(
id: matchId++,
label: '$matchLabelPrefix$matchId',
table: 'TBD',
time: 'TBD',
competitors: const [],
scores: const [0, 0],
status: MatchStatus.scheduled,
),
);
}
rounds.add(nextRoundMatches);
}
return rounds;
}