feat(TournamentService, TournamentConfigTab): enhance tournament advancement logic and knockout stage handling
- Introduced a new function to compare advancement candidates based on multiple criteria, improving the selection process for tournament participants. - Updated participant data structure to include additional metrics for better ranking and comparison. - Enhanced the TournamentConfigTab to automatically configure knockout stage settings when applicable, ensuring a smoother user experience during tournament setup.
This commit is contained in:
@@ -217,6 +217,38 @@ function nextPowerOfTwo(n) {
|
||||
return p;
|
||||
}
|
||||
|
||||
function compareAdvancementCandidates(a, b) {
|
||||
const posA = Number(a.position || a.place || 999);
|
||||
const posB = Number(b.position || b.place || 999);
|
||||
if (posA !== posB) return posA - posB;
|
||||
|
||||
const pointsA = Number(a.points || 0);
|
||||
const pointsB = Number(b.points || 0);
|
||||
if (pointsB !== pointsA) return pointsB - pointsA;
|
||||
|
||||
const setDiffA = Number(a.setDiff || 0);
|
||||
const setDiffB = Number(b.setDiff || 0);
|
||||
if (setDiffB !== setDiffA) return setDiffB - setDiffA;
|
||||
|
||||
const setsWonA = Number(a.setsWon || 0);
|
||||
const setsWonB = Number(b.setsWon || 0);
|
||||
if (setsWonB !== setsWonA) return setsWonB - setsWonA;
|
||||
|
||||
const pointsDiffA = Number(a.pointsDiff || 0);
|
||||
const pointsDiffB = Number(b.pointsDiff || 0);
|
||||
if (pointsDiffB !== pointsDiffA) return pointsDiffB - pointsDiffA;
|
||||
|
||||
const pointsWonA = Number(a.pointsWon || 0);
|
||||
const pointsWonB = Number(b.pointsWon || 0);
|
||||
if (pointsWonB !== pointsWonA) return pointsWonB - pointsWonA;
|
||||
|
||||
const groupNumberA = Number(a.groupNumber || 999);
|
||||
const groupNumberB = Number(b.groupNumber || 999);
|
||||
if (groupNumberA !== groupNumberB) return groupNumberA - groupNumberB;
|
||||
|
||||
return Number(a.id || 0) - Number(b.id || 0);
|
||||
}
|
||||
|
||||
const THIRD_PLACE_ROUND = 'Spiel um Platz 3';
|
||||
class TournamentService {
|
||||
/**
|
||||
@@ -494,6 +526,7 @@ class TournamentService {
|
||||
|
||||
const perGroupRanked = relevantStage1Groups.map(g => ({
|
||||
groupId: g.groupId,
|
||||
groupNumber: g.groupNumber,
|
||||
classId: g.classId ?? null,
|
||||
// WICHTIG:
|
||||
// - Für interne Teilnehmer brauchen wir die ClubMember-ID (Member.id / TournamentMember.clubMemberId),
|
||||
@@ -502,6 +535,14 @@ class TournamentService {
|
||||
participants: (g.participants || []).map(p => ({
|
||||
id: p.isExternal ? p.id : (p.clubMemberId ?? p.member?.id ?? p.id),
|
||||
isExternal: !!p.isExternal,
|
||||
position: Number(p.position || 999),
|
||||
points: Number(p.points || 0),
|
||||
setDiff: Number(p.setDiff || 0),
|
||||
setsWon: Number(p.setsWon || 0),
|
||||
pointsDiff: Number(p.pointsDiff || 0),
|
||||
pointsWon: Number(p.pointsWon || 0),
|
||||
groupId: g.groupId,
|
||||
groupNumber: g.groupNumber,
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -722,16 +763,40 @@ class TournamentService {
|
||||
}
|
||||
}
|
||||
const uniqueEntrants = Array.from(seen.values());
|
||||
const selectedKeys = new Set(uniqueEntrants.map(entry => `${entry.isExternal ? 'E' : 'M'}:${entry.id}`));
|
||||
|
||||
const allRankedCandidates = perGroupRanked
|
||||
.filter(group => (group.classId ?? null) === (classId ?? null))
|
||||
.flatMap(group => (group.participants || []).map(participant => ({
|
||||
id: Number(participant.id),
|
||||
isExternal: !!participant.isExternal,
|
||||
classId,
|
||||
position: Number(participant.position || 999),
|
||||
points: Number(participant.points || 0),
|
||||
setDiff: Number(participant.setDiff || 0),
|
||||
setsWon: Number(participant.setsWon || 0),
|
||||
pointsDiff: Number(participant.pointsDiff || 0),
|
||||
pointsWon: Number(participant.pointsWon || 0),
|
||||
groupId: group.groupId,
|
||||
groupNumber: group.groupNumber,
|
||||
})));
|
||||
|
||||
const desiredBracketSize = nextPowerOfTwo(uniqueEntrants.length);
|
||||
if (desiredBracketSize > uniqueEntrants.length) {
|
||||
const bestAdditionalCandidates = allRankedCandidates
|
||||
.filter(candidate => !selectedKeys.has(`${candidate.isExternal ? 'E' : 'M'}:${candidate.id}`))
|
||||
.sort(compareAdvancementCandidates)
|
||||
.slice(0, desiredBracketSize - uniqueEntrants.length);
|
||||
|
||||
bestAdditionalCandidates.forEach(candidate => {
|
||||
selectedKeys.add(`${candidate.isExternal ? 'E' : 'M'}:${candidate.id}`);
|
||||
uniqueEntrants.push(candidate);
|
||||
});
|
||||
}
|
||||
|
||||
const thirdPlace = wantsThirdPlace;
|
||||
if (uniqueEntrants.length >= 2) {
|
||||
// Sortiere nach Platz: beste Plätze zuerst, dann schlechtere
|
||||
// Wenn mehrere Teilnehmer den gleichen Platz haben, behalte die ursprüngliche Reihenfolge
|
||||
uniqueEntrants.sort((a, b) => {
|
||||
const placeA = a.place || 999;
|
||||
const placeB = b.place || 999;
|
||||
return placeA - placeB;
|
||||
});
|
||||
uniqueEntrants.sort(compareAdvancementCandidates);
|
||||
|
||||
// Paare: Bester gegen Schlechtesten, Zweiter gegen Vorletzten, etc.
|
||||
// Reverse die zweite Hälfte, um das gewünschte Pairing zu erreichen
|
||||
|
||||
Reference in New Issue
Block a user