bracket-feature
Raz 9 months ago
parent 8de5758650
commit a83c0782eb
  1. 13
      tournaments/models/match.py
  2. 141
      tournaments/templates/tournaments/tournament_bracket.html
  3. 55
      tournaments/views.py

@ -157,7 +157,18 @@ class Match(models.Model):
return teams return teams
if self.round and self.round.parent: if self.round and self.round.parent:
return teams print("self.round.parent.index", self.round.parent.index)
print("self.round.parent.parent", self.round.parent.parent)
if self.round.parent.index == 1 and self.round.parent.parent is None:
names = ["Perdant Demi #1", '']
team = self.default_live_team(names)
teams.append(team)
names = ["Perdant Demi #2", '']
team = self.default_live_team(names)
teams.append(team)
return teams
else:
return teams
# No team scores at all # No team scores at all
if previous_top_match: if previous_top_match:

@ -50,7 +50,10 @@ function renderBracket() {
bracket.appendChild(firstMatch); bracket.appendChild(firstMatch);
const matchHeight = firstMatch.offsetHeight; const matchHeight = firstMatch.offsetHeight;
const matchSpacing = 10; const matchSpacing = 10;
const baseDistance = matchHeight + matchSpacing;
bracket.innerHTML = ''; bracket.innerHTML = '';
const roundCount = rounds.length;
const finalRoundIndex = (roundCount - 1) / 2;
rounds.forEach((roundMatches, roundIndex) => { rounds.forEach((roundMatches, roundIndex) => {
const roundDiv = document.createElement('div'); const roundDiv = document.createElement('div');
@ -63,19 +66,74 @@ function renderBracket() {
matchDiv.style.position = 'absolute'; matchDiv.style.position = 'absolute';
const isDisabled = matchTemplate.dataset.disabled === 'true'; const isDisabled = matchTemplate.dataset.disabled === 'true';
let top; let top;
if (roundIndex === 0) { let left;
let right;
let nextMatchDistance = baseDistance;
// Check if this round has only one match
const isSingleMatchRound = roundMatches.length === 1;
if (isSingleMatchRound) {
matchDiv.classList.add('single-match-round');
nextMatchDistance = 0;
}
if (roundIndex === 0 || roundIndex === rounds.length - 1) {
top = matchIndex * (matchHeight + matchSpacing); top = matchIndex * (matchHeight + matchSpacing);
if (roundIndex === rounds.length - 1) {
nextMatchDistance = baseDistance * Math.pow(2, rounds.length - roundIndex - 1);
matchDiv.classList.add('reverse-bracket');
}
} else { } else {
const parentIndex1 = matchIndex * 2; const previousRoundMatches = rounds[roundIndex - 1].length;
const parentIndex2 = parentIndex1 + 1; const currentRoundMatches = roundMatches.length;
const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; if (currentRoundMatches === previousRoundMatches || roundIndex == finalRoundIndex + 1) {
const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; // Same number of matches as previous round
top = (parentPos1 + parentPos2) / 2; top = matchPositions[roundIndex - 2][matchIndex];
nextMatchDistance = 0;
} else if (roundIndex == finalRoundIndex) {
top = matchPositions[roundIndex - 3][1 + matchIndex];
nextMatchDistance = baseDistance;
} else if (currentRoundMatches > previousRoundMatches) {
// Bracket is reversed - matches are splitting instead of combining
const parentIndex = Math.floor(matchIndex / 2);
if (parentIndex < matchPositions[roundIndex - 1].length) {
const parentPos = matchPositions[roundIndex - 1][parentIndex];
// Calculate offset based on whether it's an even or odd match
const offset = (matchIndex % 2 === 0) ? -baseDistance : baseDistance;
top = parentPos + offset;
}
nextMatchDistance = baseDistance * Math.pow(2, rounds.length - roundIndex - 1);
matchDiv.classList.add('reverse-bracket');
} else {
// Normal case where matches are combining
const parentIndex1 = matchIndex * 2;
const parentIndex2 = parentIndex1 + 1;
const parentPos1 = matchPositions[roundIndex - 1][parentIndex1];
const parentPos2 = matchPositions[roundIndex - 1][parentIndex2];
top = (parentPos1 + parentPos2) / 2;
nextMatchDistance = !isSingleMatchRound ? baseDistance * Math.pow(2, roundIndex) : 0;
}
} }
const baseDistance = matchHeight + matchSpacing; if (roundIndex >= finalRoundIndex - 1) {
const distance = baseDistance * Math.pow(2, roundIndex); matchDiv.style.transform = `translateX(-50%)`;
matchDiv.style.setProperty('--next-match-distance', `${distance}px`); if (roundIndex >= finalRoundIndex + 2) {
matchDiv.style.transform = `translateX(-100%)`;
}
if (roundIndex == finalRoundIndex - 1) {
matchDiv.classList.add('inward');
}
if (roundIndex == finalRoundIndex + 1) {
matchDiv.classList.add('outward');
}
}
if (roundIndex == 1 || roundIndex == roundCount - 2) {
nextMatchDistance = nextMatchDistance / 2;
}
matchDiv.style.setProperty('--next-match-distance', `${nextMatchDistance}px`);
matchDiv.style.top = `${top}px`; matchDiv.style.top = `${top}px`;
matchPositions[roundIndex][matchIndex] = top; matchPositions[roundIndex][matchIndex] = top;
@ -84,6 +142,19 @@ function renderBracket() {
<div class="match-content ${isDisabled ? 'disabled' : ''}">${matchTemplate.innerHTML}</div> <div class="match-content ${isDisabled ? 'disabled' : ''}">${matchTemplate.innerHTML}</div>
`; `;
if (roundIndex == finalRoundIndex - 1) {
const matchDiv2 = document.createElement('div');
matchDiv2.className = 'butterfly-match';
matchDiv2.style.position = 'absolute';
matchDiv2.style.transform = `translateX(-50%)`;
matchDiv2.classList.add('inward');
matchDiv2.classList.add('semi-final');
matchDiv2.style.setProperty('--next-match-distance', `${baseDistance}px`);
matchDiv2.style.top = `${top}px`;
matchDiv2.innerHTML = `
`;
roundDiv.appendChild(matchDiv2);
}
roundDiv.appendChild(matchDiv); roundDiv.appendChild(matchDiv);
}); });
@ -95,6 +166,13 @@ function renderBracket() {
</script> </script>
<style> <style>
.butterfly-match.same-level::before {
display: none;
}
/* Adjust styling for matches with single parent */
.match-content.disabled { .match-content.disabled {
visibility: hidden; visibility: hidden;
} }
@ -113,7 +191,8 @@ function renderBracket() {
.butterfly-round { .butterfly-round {
position: relative; position: relative;
width: 25%; /* 300px for match + 20px on each side for lines */ width: 15%; /* 300px for match + 20px on each side for lines */
flex-shrink: 0; /* Prevents rounds from shrinking */
} }
.butterfly-match { .butterfly-match {
@ -132,6 +211,16 @@ function renderBracket() {
background: #666; background: #666;
} }
.semi-final::after {
content: "";
position: absolute;
left: calc(100% + 20px); /* After horizontal line */
top: -4px;
width: 2px;
height: calc(var(--next-match-distance));
background: #666;
}
/* Vertical line connecting pair of matches */ /* Vertical line connecting pair of matches */
.butterfly-match:nth-child(2n+1)::before { .butterfly-match:nth-child(2n+1)::before {
content: ""; content: "";
@ -153,6 +242,27 @@ function renderBracket() {
background: #666; background: #666;
} }
/* Vertical line connecting pair of matches */
.butterfly-match.reverse-bracket:nth-child(2n+1)::before {
content: "";
position: absolute;
left: calc(0% - 20px); /* After horizontal line */
top: calc(50% + 2px);
width: 2px;
height: calc((var(--next-match-distance)) / 2);
background: #666;
}
.butterfly-match.reverse-bracket:nth-child(2n)::before {
content: "";
position: absolute;
left: calc(0% - 20px);
bottom: 50%; /* Account for half of horizontal line height */
width: 2px;
height: calc((var(--next-match-distance)) / 2); /* Add half of horizontal line height */
background: #666;
}
/* Horizontal line to next round match */ /* Horizontal line to next round match */
.butterfly-match .incoming-line { .butterfly-match .incoming-line {
position: absolute; position: absolute;
@ -163,17 +273,18 @@ function renderBracket() {
background: #666; background: #666;
} }
/* Hide incoming line for first round */ .inward .incoming-line {
.butterfly-round:first-child .incoming-line {
display: none; display: none;
} }
/* Hide outgoing lines for last round */ .butterfly-match.outward::after {
.butterfly-round:last-child .butterfly-match::after,
.butterfly-round:last-child .butterfly-match::before {
display: none; display: none;
} }
.butterfly-round:last-child .butterfly-match::after,
.butterfly-round:first-child .incoming-line {
display: none;
}
</style> </style>

@ -966,18 +966,61 @@ def tournament_bracket(request, tournament_id):
group_stage_loser_bracket=False group_stage_loser_bracket=False
).order_by('-index') ).order_by('-index')
main_rounds_reversed = tournament.round_set.filter(
parent=None,
group_stage_loser_bracket=False
).order_by('index') # Removed the minus sign before 'index'
loser_final = None
if len(main_rounds_reversed) >= 1:
semi = main_rounds_reversed[1]
loser_round = tournament.round_set.filter(
parent=semi,
group_stage_loser_bracket=False
).order_by('index')
if len(loser_round) >= 1:
loser_final = loser_round[0]
# Create serializable match groups data # Create serializable match groups data
serializable_match_groups = [] serializable_match_groups = []
# Add first half of each round (from last to semi-finals)
for round in main_rounds: for round in main_rounds:
matches = round.match_set.all() matches = round.match_set.all()
if matches: if matches:
# Create MatchGroup midpoint = len(matches) // 2
match_group = tournament.create_match_group( if len(matches) > 1:
name=round.name(), first_half_matches = matches[:midpoint]
matches=matches else:
) first_half_matches = list(matches) # Convert QuerySet to a list
serializable_match_groups.append(match_group) if loser_final:
loser_matches = loser_final.match_set.all()
if len(loser_matches) >= 1:
print(loser_matches[0])
first_half_matches.append(loser_matches[0])
if first_half_matches:
match_group = tournament.create_match_group(
name=round.name(),
matches=first_half_matches
)
serializable_match_groups.append(match_group)
for round in main_rounds_reversed:
matches = round.match_set.all()
if matches:
midpoint = len(matches) // 2
if len(matches) > 1:
first_half_matches = matches[midpoint:]
match_group = tournament.create_match_group(
name=round.name(),
matches=first_half_matches
)
serializable_match_groups.append(match_group)
context = { context = {
'tournament': tournament, 'tournament': tournament,

Loading…
Cancel
Save