fix bracket

shop
Razmig Sarkissian 8 months ago
parent 067e5351fa
commit 60dd2ca335
  1. 8
      tournaments/models/match.py
  2. 7
      tournaments/models/tournament.py
  3. 1
      tournaments/static/tournaments/css/style.css
  4. 105
      tournaments/templates/tournaments/tournament_bracket.html
  5. 1
      tournaments/urls.py
  6. 89
      tournaments/views.py

@ -430,7 +430,10 @@ class Match(models.Model):
class Team: class Team:
def __init__(self, id, image, names, scores, weight, is_winner, walk_out, is_lucky_loser): def __init__(self, id, image, names, scores, weight, is_winner, walk_out, is_lucky_loser):
# print(f"image = {image}, names= {names}, scores ={scores}, weight={weight}, win={is_winner}") # print(f"image = {image}, names= {names}, scores ={scores}, weight={weight}, win={is_winner}")
self.id = str(id) if id is not None:
self.id = str(id)
else:
self.id = None
self.image = image self.image = image
self.names = names self.names = names
self.scores = scores self.scores = scores
@ -451,7 +454,8 @@ class Team:
"is_winner": self.is_winner, "is_winner": self.is_winner,
"walk_out": self.walk_out, "walk_out": self.walk_out,
"is_walk_out": self.is_walk_out(), "is_walk_out": self.is_walk_out(),
"is_lucky_loser": self.is_lucky_loser "is_lucky_loser": self.is_lucky_loser,
"id": self.id
} }
class LiveMatch: class LiveMatch:

@ -534,7 +534,7 @@ class Tournament(models.Model):
return groups return groups
def create_match_group(self, name, matches): def create_match_group(self, name, matches, round_id=None):
matches = list(matches) matches = list(matches)
live_matches = [match.live_match() for match in matches] live_matches = [match.live_match() for match in matches]
# Filter out matches that have a start_date of None # Filter out matches that have a start_date of None
@ -551,7 +551,7 @@ class Tournament(models.Model):
time_format = 'l d M' time_format = 'l d M'
formatted_schedule = f" - {formats.date_format(local_start, format=time_format)}" formatted_schedule = f" - {formats.date_format(local_start, format=time_format)}"
return MatchGroup(name, live_matches, formatted_schedule) return MatchGroup(name, live_matches, formatted_schedule, round_id)
def live_group_stages(self): def live_group_stages(self):
group_stages = self.get_computed_group_stage() group_stages = self.get_computed_group_stage()
@ -1330,10 +1330,11 @@ class Tournament(models.Model):
class MatchGroup: class MatchGroup:
def __init__(self, name, matches, formatted_schedule): def __init__(self, name, matches, formatted_schedule, round_id=None):
self.name = name self.name = name
self.matches = matches self.matches = matches
self.formatted_schedule = formatted_schedule self.formatted_schedule = formatted_schedule
self.round_id = round_id
def add_match(self, match): def add_match(self, match):
self.matches.append(match) self.matches.append(match)

@ -818,7 +818,6 @@ h-margin {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
display: block; display: block;
padding: 2px 8px;
border-radius: 6px; border-radius: 6px;
} }

@ -20,6 +20,7 @@
data-disabled="{{ match.disabled|lower }}" data-disabled="{{ match.disabled|lower }}"
data-match-group-name="{{ match_group.name }}" data-match-group-name="{{ match_group.name }}"
data-match-format="{{ match.format }}" data-match-format="{{ match.format }}"
data-round-id="{{ match_group.round_id }}"
class="match-template"> class="match-template">
{% include 'tournaments/bracket_match_cell.html' %} {% include 'tournaments/bracket_match_cell.html' %}
</div> </div>
@ -29,13 +30,15 @@
</div> </div>
<script> <script>
const tournamentId = "{{ tournament.id }}";
function renderBracket() { function renderBracket() {
const bracket = document.getElementById('bracket'); const bracket = document.getElementById('bracket');
const matchTemplates = document.getElementById('match-templates').children; const matchTemplates = document.getElementById('match-templates').children;
const rounds = []; const rounds = [];
const matchPositions = []; const matchPositions = [];
const doubleButterflyMode = {{ double_butterfly_mode|lower }};
const displayLoserFinal = {{ display_loser_final|lower }};
// Group matches by round // Group matches by round
Array.from(matchTemplates).forEach(template => { Array.from(matchTemplates).forEach(template => {
@ -56,7 +59,10 @@ function renderBracket() {
const baseDistance = matchHeight + matchSpacing; const baseDistance = matchHeight + matchSpacing;
bracket.innerHTML = ''; bracket.innerHTML = '';
const roundCount = rounds.length; const roundCount = rounds.length;
const finalRoundIndex = (roundCount - 1) / 2; let finalRoundIndex = (roundCount - 1);
if (doubleButterflyMode == true) {
finalRoundIndex = finalRoundIndex / 2;
}
let nextMatchDistance = baseDistance; let nextMatchDistance = baseDistance;
let minimumMatchDistance = 1; let minimumMatchDistance = 1;
@ -77,10 +83,22 @@ function renderBracket() {
const firstMatchTemplate = roundMatches[0].closest('.match-template'); const firstMatchTemplate = roundMatches[0].closest('.match-template');
const matchGroupName = firstMatchTemplate.dataset.matchGroupName; const matchGroupName = firstMatchTemplate.dataset.matchGroupName;
const matchFormat = firstMatchTemplate.dataset.matchFormat; const matchFormat = firstMatchTemplate.dataset.matchFormat;
const roundId = firstMatchTemplate.dataset.roundId; // Add this line
const nameSpan = document.createElement('div'); let nameSpan = document.createElement('div');
nameSpan.className = 'round-name'; nameSpan.className = 'round-name';
nameSpan.textContent = matchGroupName; nameSpan.textContent = matchGroupName;
if (roundIndex == finalRoundIndex) {
} else {
nameSpan = document.createElement('a');
nameSpan.className = 'round-name btn small-button';
nameSpan.textContent = matchGroupName;
if (roundId) {
nameSpan.href = `/tournament/${tournamentId}/round/${roundId}/bracket/`;
nameSpan.style.cursor = 'pointer';
}
nameSpan.style.textDecoration = 'None';
}
const formatSpan = document.createElement('div'); const formatSpan = document.createElement('div');
formatSpan.className = 'round-format'; formatSpan.className = 'round-format';
@ -92,13 +110,13 @@ function renderBracket() {
// Create matches container // Create matches container
const matchesContainer = document.createElement('div'); const matchesContainer = document.createElement('div');
matchesContainer.className = 'matches-container'; matchesContainer.className = 'matches-container';
if (roundIndex >= finalRoundIndex - 1) { if (roundCount > 5 && doubleButterflyMode == true) {
if (roundCount > 5) { if (roundIndex >= finalRoundIndex - 1) {
matchesContainer.style.transform = `translateX(-50%)`; matchesContainer.style.transform = `translateX(-50%)`;
if (roundIndex >= finalRoundIndex + 2) { if (roundIndex >= finalRoundIndex + 2) {
matchesContainer.style.transform = `translateX(-100%)`; matchesContainer.style.transform = `translateX(-100%)`;
}
} }
}
} }
roundDiv.appendChild(matchesContainer); roundDiv.appendChild(matchesContainer);
@ -119,27 +137,51 @@ function renderBracket() {
} }
if (roundIndex === 0) { if (roundIndex === 0) {
const nextMatchesCount = rounds[roundIndex + 1].length;
if (currentMatchesCount == nextMatchesCount) { if (roundCount > 1) {
const nextMatchesCount = rounds[roundIndex + 1].length;
if (currentMatchesCount == nextMatchesCount) {
nextMatchDistance = 0;
}
} else {
nextMatchDistance = 0; nextMatchDistance = 0;
} }
top = matchIndex * (matchHeight + matchSpacing) * minimumMatchDistance; top = matchIndex * (matchHeight + matchSpacing) * minimumMatchDistance;
} else if (roundIndex === roundCount - 1) {
if (roundCount == 3 && doubleButterflyMode) {
top = top + (matchHeight + matchSpacing) / 2
}
} else if (roundIndex === roundCount - 1 && doubleButterflyMode == true) {
const nextMatchesCount = rounds[roundIndex - 1].length; const nextMatchesCount = rounds[roundIndex - 1].length;
if (currentMatchesCount == nextMatchesCount) { if (currentMatchesCount == nextMatchesCount) {
nextMatchDistance = 0; nextMatchDistance = 0;
} }
} else if (roundIndex == finalRoundIndex) { } else if (roundIndex == finalRoundIndex) {
if (doubleButterflyMode == true) {
let lgth = matchPositions[0].length / 2; let lgth = matchPositions[0].length / 2;
let index = lgth + matchIndex - 1; let index = lgth + matchIndex - 1;
// If index goes negative, use 0 instead // If index goes negative, use 0 instead
if (matchIndex == 0) { if (displayLoserFinal == true) {
top = matchPositions[roundIndex - 1][0] - baseDistance / 2; if (matchIndex == 0) {
top = matchPositions[roundIndex - 1][0] - baseDistance / 2;
} else {
top = matchPositions[roundIndex - 1][0] + baseDistance / 2;
}
nextMatchDistance = baseDistance;
} else { } else {
top = matchPositions[roundIndex - 1][0] + baseDistance / 2; top = matchPositions[roundIndex - 1][0];
nextMatchDistance = 0;
} }
nextMatchDistance = baseDistance; } else {
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 = 0;
}
} else if (roundIndex < finalRoundIndex) { } else if (roundIndex < finalRoundIndex) {
const previousMatchesCount = rounds[roundIndex - 1].length; const previousMatchesCount = rounds[roundIndex - 1].length;
const nextMatchesCount = rounds[roundIndex + 1].length; const nextMatchesCount = rounds[roundIndex + 1].length;
@ -172,8 +214,8 @@ function renderBracket() {
} }
} }
if (roundIndex >= finalRoundIndex - 2) { if (roundCount > 5 && doubleButterflyMode == true) {
if (roundCount > 5) { if (roundIndex >= finalRoundIndex - 2) {
if (roundIndex == finalRoundIndex - 1) { if (roundIndex == finalRoundIndex - 1) {
matchDiv.classList.add('inward'); matchDiv.classList.add('inward');
} }
@ -184,11 +226,12 @@ function renderBracket() {
nextMatchDistance = nextMatchDistance - baseDistance; nextMatchDistance = nextMatchDistance - baseDistance;
} }
} }
} }
if (roundIndex == finalRoundIndex - 1 || roundIndex == finalRoundIndex + 1) { if (displayLoserFinal == true) {
nextMatchDistance = baseDistance; if (doubleButterflyMode == true && (roundIndex == finalRoundIndex - 1 || roundIndex == finalRoundIndex + 1)) {
nextMatchDistance = baseDistance;
}
} }
matchDiv.style.setProperty('--next-match-distance', `${nextMatchDistance}px`); matchDiv.style.setProperty('--next-match-distance', `${nextMatchDistance}px`);
@ -212,12 +255,13 @@ function renderBracket() {
// Position title above the first match // Position title above the first match
titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed
titleDiv.style.position = 'absolute'; titleDiv.style.position = 'absolute';
if (roundIndex == finalRoundIndex - 1) { if (roundCount >= 5 && doubleButterflyMode == true) {
titleDiv.style.marginLeft = '50px'; if (roundIndex == finalRoundIndex - 1) {
} else if (roundIndex == finalRoundIndex + 1) { titleDiv.style.marginLeft = '50px';
titleDiv.style.marginLeft = '-50px'; } else if (roundIndex == finalRoundIndex + 1) {
titleDiv.style.marginLeft = '-50px';
}
} }
matchesContainer.appendChild(titleDiv); matchesContainer.appendChild(titleDiv);
} }
@ -226,7 +270,7 @@ function renderBracket() {
<div class="match-content ${isDisabled ? 'disabled' : ''}">${matchTemplate.innerHTML}</div> <div class="match-content ${isDisabled ? 'disabled' : ''}">${matchTemplate.innerHTML}</div>
`; `;
if (roundIndex == finalRoundIndex - 1) { if (roundIndex == finalRoundIndex - 1 && displayLoserFinal == true) {
const matchDiv2 = document.createElement('div'); const matchDiv2 = document.createElement('div');
matchDiv2.className = 'butterfly-match'; matchDiv2.className = 'butterfly-match';
matchDiv2.classList.add('inward'); matchDiv2.classList.add('inward');
@ -295,8 +339,13 @@ function renderBracket() {
} }
.round-name { .round-name {
font-size: 1.5em; /* Make the round name bigger */ font-size: 1.5em;
margin-bottom: 5px; margin-bottom: 5px;
transition: color 0.2s;
}
.round-name a:hover {
color: orange;
} }
.round-format { .round-format {

@ -18,6 +18,7 @@ urlpatterns = [
path('teams/', views.tournament_teams, name='tournament-teams'), path('teams/', views.tournament_teams, name='tournament-teams'),
path('info/', views.tournament_info, name='tournament-info'), path('info/', views.tournament_info, name='tournament-info'),
path('bracket/', views.tournament_bracket, name='tournament-bracket'), path('bracket/', views.tournament_bracket, name='tournament-bracket'),
path('round/<str:round_id>/bracket/', views.round_bracket, name='round-bracket'),
path('prog/', views.tournament_prog, name='tournament-prog'), path('prog/', views.tournament_prog, name='tournament-prog'),
path('summons/', views.tournament_summons, name='tournament-summons'), path('summons/', views.tournament_summons, name='tournament-summons'),
path('broadcast/summons/', views.tournament_broadcasted_summons, name='broadcasted-summons'), path('broadcast/summons/', views.tournament_broadcasted_summons, name='broadcasted-summons'),

@ -960,26 +960,47 @@ def tournament_bracket(request, tournament_id):
View to display tournament bracket structure. View to display tournament bracket structure.
""" """
tournament = get_object_or_404(Tournament, pk=tournament_id) tournament = get_object_or_404(Tournament, pk=tournament_id)
context = get_bracket(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=False)
return render(request, 'tournaments/tournament_bracket.html', context)
def round_bracket(request, tournament_id, round_id):
"""
View to display round bracket structure.
"""
tournament = get_object_or_404(Tournament, pk=tournament_id)
round = get_object_or_404(Round, pk=round_id)
context = get_bracket(tournament, round, double_butterfly_mode=False, display_loser_final=False)
return render(request, 'tournaments/tournament_bracket.html', context)
def get_bracket(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=False):
loser_final = None
main_rounds_reversed = []
if parent_round:
double_butterfly_mode = False
display_loser_final = False
# Get main bracket rounds (excluding children/ranking matches) # Get main bracket rounds (excluding children/ranking matches)
main_rounds = tournament.round_set.filter( main_rounds = tournament.round_set.filter(
parent=None, parent=parent_round,
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 double_butterfly_mode:
if len(main_rounds_reversed) >= 1: main_rounds_reversed = tournament.round_set.filter(
semi = main_rounds_reversed[1] parent=parent_round,
loser_round = tournament.round_set.filter(
parent=semi,
group_stage_loser_bracket=False group_stage_loser_bracket=False
).order_by('index') ).order_by('index')
if len(loser_round) >= 1:
loser_final = loser_round[0] if display_loser_final:
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 = []
@ -989,7 +1010,7 @@ def tournament_bracket(request, tournament_id):
matches = round.match_set.filter(disabled=False).order_by('index') matches = round.match_set.filter(disabled=False).order_by('index')
if matches: if matches:
if len(matches) > 1: if len(matches) > 1 and double_butterfly_mode:
midpoint = int(len(matches) / 2) midpoint = int(len(matches) / 2)
first_half_matches = matches[:midpoint] first_half_matches = matches[:midpoint]
else: else:
@ -1001,31 +1022,41 @@ def tournament_bracket(request, tournament_id):
if first_half_matches: if first_half_matches:
name = round.name()
if parent_round:
name = first_half_matches[0].computed_name()
match_group = tournament.create_match_group( match_group = tournament.create_match_group(
name=round.name(), name=name,
matches=first_half_matches matches=first_half_matches,
round_id=round.id
) )
serializable_match_groups.append(match_group) serializable_match_groups.append(match_group)
for round in main_rounds_reversed: if double_butterfly_mode:
matches = round.match_set.filter(disabled=False).order_by('index') for round in main_rounds_reversed:
if matches: matches = round.match_set.filter(disabled=False).order_by('index')
if len(matches) > 1: if matches:
midpoint = int(len(matches) / 2) if len(matches) > 1:
first_half_matches = matches[midpoint:] midpoint = int(len(matches) / 2)
first_half_matches = matches[midpoint:]
match_group = tournament.create_match_group( name = round.name()
name=round.name(), if parent_round:
matches=first_half_matches name = first_half_matches[0].computed_name()
) match_group = tournament.create_match_group(
serializable_match_groups.append(match_group) name=name,
matches=first_half_matches,
round_id=round.id
)
serializable_match_groups.append(match_group)
context = { context = {
'tournament': tournament, 'tournament': tournament,
'match_groups': serializable_match_groups 'match_groups': serializable_match_groups,
'double_butterfly_mode': double_butterfly_mode,
'display_loser_final': display_loser_final
} }
return render(request, 'tournaments/tournament_bracket.html', context) return context
def tournament_prog(request, tournament_id): def tournament_prog(request, tournament_id):
tournament = get_object_or_404(Tournament, id=tournament_id) tournament = get_object_or_404(Tournament, id=tournament_id)

Loading…
Cancel
Save