Match management view
This commit is contained in:
@@ -59,6 +59,15 @@ public partial class MainViewModel : ViewModelBase
|
||||
Title = "Tournament Management";
|
||||
await tournamentsVm.LoadTournaments();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task NavigateToMatches()
|
||||
{
|
||||
var matchesVm = new MatchesViewModel();
|
||||
CurrentView = matchesVm;
|
||||
Title = "Match Management";
|
||||
await matchesVm.LoadEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class HomeViewModel : ViewModelBase
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TournamentOrganizer;
|
||||
using TournamentOrganizer.Models;
|
||||
|
||||
namespace TournamentOrganizer.ViewModels;
|
||||
|
||||
public partial class MatchesViewModel : ViewModelBase
|
||||
{
|
||||
private readonly TournamentContext _context;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<EventOption> _availableEvents = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private EventOption? _selectedEvent;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<TournamentOption> _availableTournaments = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private TournamentOption? _selectedTournament;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MatchDisplay> _matches = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private MatchDisplay? _selectedMatch;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<TeamParticipantDisplay> _matchTeams = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<RoundDisplay> _rounds = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private RoundDisplay? _selectedRound;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _eventFilterSearch = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _tournamentFilterSearch = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEventDropdownOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isTournamentDropdownOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _statusMessage = string.Empty;
|
||||
|
||||
private readonly ObservableCollection<EventOption> _filteredEvents = [];
|
||||
public ObservableCollection<EventOption> FilteredEvents => _filteredEvents;
|
||||
|
||||
private readonly ObservableCollection<TournamentOption> _filteredTournaments = [];
|
||||
public ObservableCollection<TournamentOption> FilteredTournaments => _filteredTournaments;
|
||||
|
||||
public bool IsTournamentFilterEnabled => SelectedEvent != null;
|
||||
|
||||
public bool CanCreateRound => Rounds.Count == 0 || Rounds.All(r => r.State == RoundState.Finished);
|
||||
|
||||
public bool CanStartRound => SelectedRound != null && SelectedRound.State == RoundState.Waiting;
|
||||
|
||||
public bool CanAwardVictory => SelectedRound != null && SelectedRound.State == RoundState.Started;
|
||||
|
||||
public MatchesViewModel()
|
||||
{
|
||||
_context = new TournamentContext();
|
||||
}
|
||||
|
||||
public async Task LoadEvents()
|
||||
{
|
||||
var events = await _context.Events
|
||||
.Include(e => e.Tournaments)
|
||||
.ThenInclude(t => t.Game)
|
||||
.ToListAsync();
|
||||
|
||||
AvailableEvents.Clear();
|
||||
_filteredEvents.Clear();
|
||||
foreach (var e in events)
|
||||
{
|
||||
var option = new EventOption(e);
|
||||
AvailableEvents.Add(option);
|
||||
_filteredEvents.Add(option);
|
||||
}
|
||||
|
||||
UpdateAvailableTournaments();
|
||||
UpdateFilteredTournaments();
|
||||
}
|
||||
|
||||
private void UpdateAvailableTournaments()
|
||||
{
|
||||
AvailableTournaments.Clear();
|
||||
if (SelectedEvent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var t in SelectedEvent.Event.Tournaments)
|
||||
{
|
||||
AvailableTournaments.Add(new TournamentOption(t));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadMatches()
|
||||
{
|
||||
if (SelectedTournament == null)
|
||||
{
|
||||
Matches.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
int? selectedMatchId = SelectedMatch?.Id;
|
||||
|
||||
var matches = await _context.Matches
|
||||
.Where(m => m.TournamentId == SelectedTournament.Tournament.Id)
|
||||
.Include(m => m.Teams)
|
||||
.ThenInclude(tp => tp.Team)
|
||||
.Include(m => m.Rounds)
|
||||
.ToListAsync();
|
||||
|
||||
Matches.Clear();
|
||||
MatchDisplay? newSelectedMatch = null;
|
||||
foreach (var match in matches)
|
||||
{
|
||||
var matchDisplay = new MatchDisplay(match);
|
||||
Matches.Add(matchDisplay);
|
||||
if (match.Id == selectedMatchId)
|
||||
{
|
||||
newSelectedMatch = matchDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
if (newSelectedMatch != null)
|
||||
{
|
||||
SelectedMatch = newSelectedMatch;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadMatchDetails()
|
||||
{
|
||||
if (SelectedMatch == null)
|
||||
{
|
||||
MatchTeams.Clear();
|
||||
Rounds.Clear();
|
||||
_lastLoadedMatchId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_lastLoadedMatchId = SelectedMatch.Id;
|
||||
|
||||
var match = await _context.Matches
|
||||
.Where(m => m.Id == SelectedMatch.Id)
|
||||
.Include(m => m.Teams)
|
||||
.ThenInclude(tp => tp.Team)
|
||||
.Include(m => m.Rounds)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (match == null)
|
||||
{
|
||||
MatchTeams.Clear();
|
||||
Rounds.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
MatchTeams.Clear();
|
||||
foreach (var team in match.Teams)
|
||||
{
|
||||
MatchTeams.Add(new TeamParticipantDisplay(team));
|
||||
}
|
||||
|
||||
Rounds.Clear();
|
||||
foreach (var round in match.Rounds)
|
||||
{
|
||||
Rounds.Add(new RoundDisplay(round));
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(CanCreateRound));
|
||||
OnPropertyChanged(nameof(CanStartRound));
|
||||
OnPropertyChanged(nameof(CanAwardVictory));
|
||||
}
|
||||
|
||||
partial void OnSelectedEventChanged(EventOption? value)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsTournamentFilterEnabled));
|
||||
UpdateAvailableTournaments();
|
||||
UpdateFilteredTournaments();
|
||||
SelectedTournament = null;
|
||||
Matches.Clear();
|
||||
MatchTeams.Clear();
|
||||
Rounds.Clear();
|
||||
}
|
||||
|
||||
partial void OnSelectedTournamentChanged(TournamentOption? value)
|
||||
{
|
||||
_ = LoadMatches();
|
||||
MatchTeams.Clear();
|
||||
Rounds.Clear();
|
||||
}
|
||||
|
||||
private int? _lastLoadedMatchId;
|
||||
|
||||
partial void OnSelectedMatchChanged(MatchDisplay? value)
|
||||
{
|
||||
if (value?.Id == _lastLoadedMatchId)
|
||||
return;
|
||||
_ = LoadMatchDetails();
|
||||
}
|
||||
|
||||
partial void OnSelectedRoundChanged(RoundDisplay? value)
|
||||
{
|
||||
OnPropertyChanged(nameof(CanStartRound));
|
||||
OnPropertyChanged(nameof(CanAwardVictory));
|
||||
}
|
||||
|
||||
partial void OnEventFilterSearchChanged(string value) => UpdateFilteredEvents();
|
||||
|
||||
partial void OnTournamentFilterSearchChanged(string value) => UpdateFilteredTournaments();
|
||||
|
||||
private void UpdateFilteredEvents()
|
||||
{
|
||||
_filteredEvents.Clear();
|
||||
foreach (var e in AvailableEvents.Where(e => string.IsNullOrWhiteSpace(EventFilterSearch) || e.DisplayName.ToLower().Contains(EventFilterSearch.ToLower())))
|
||||
{
|
||||
_filteredEvents.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFilteredTournaments()
|
||||
{
|
||||
_filteredTournaments.Clear();
|
||||
if (SelectedEvent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var t in AvailableTournaments.Where(t => string.IsNullOrWhiteSpace(TournamentFilterSearch) || t.DisplayName.ToLower().Contains(TournamentFilterSearch.ToLower())))
|
||||
{
|
||||
_filteredTournaments.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectEventFilter(EventOption option)
|
||||
{
|
||||
SelectedEvent = option;
|
||||
IsEventDropdownOpen = false;
|
||||
EventFilterSearch = string.Empty;
|
||||
}
|
||||
|
||||
public void ClearEventFilter()
|
||||
{
|
||||
SelectedEvent = null;
|
||||
EventFilterSearch = string.Empty;
|
||||
}
|
||||
|
||||
public void SelectTournamentFilter(TournamentOption option)
|
||||
{
|
||||
SelectedTournament = option;
|
||||
IsTournamentDropdownOpen = false;
|
||||
TournamentFilterSearch = string.Empty;
|
||||
}
|
||||
|
||||
public void ClearTournamentFilter()
|
||||
{
|
||||
SelectedTournament = null;
|
||||
TournamentFilterSearch = string.Empty;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CreateRound()
|
||||
{
|
||||
if (SelectedMatch == null)
|
||||
{
|
||||
StatusMessage = "Select a match first";
|
||||
return;
|
||||
}
|
||||
|
||||
var match = await _context.Matches
|
||||
.Where(m => m.Id == SelectedMatch.Id)
|
||||
.Include(m => m.Rounds)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (match == null)
|
||||
{
|
||||
StatusMessage = "Match not found";
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.Rounds.Any(r => r.State != RoundState.Finished))
|
||||
{
|
||||
StatusMessage = "All rounds must be finished before creating a new round";
|
||||
return;
|
||||
}
|
||||
|
||||
var round = new Round
|
||||
{
|
||||
MatchId = match.Id,
|
||||
Match = match,
|
||||
State = RoundState.Waiting,
|
||||
Players = []
|
||||
};
|
||||
|
||||
match.Rounds.Add(round);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
StatusMessage = "Round created";
|
||||
await LoadMatches();
|
||||
await LoadMatchDetails();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task StartRound()
|
||||
{
|
||||
if (SelectedRound == null)
|
||||
{
|
||||
StatusMessage = "Select a round to start";
|
||||
return;
|
||||
}
|
||||
|
||||
var round = await _context.Rounds.FindAsync(SelectedRound.Id);
|
||||
if (round == null)
|
||||
{
|
||||
StatusMessage = "Round not found";
|
||||
return;
|
||||
}
|
||||
|
||||
if (round.State != RoundState.Waiting)
|
||||
{
|
||||
StatusMessage = "Round must be in Waiting state to start";
|
||||
return;
|
||||
}
|
||||
|
||||
round.State = RoundState.Started;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
StatusMessage = "Round started";
|
||||
await LoadMatches();
|
||||
await LoadMatchDetails();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AwardVictory(object? parameter)
|
||||
{
|
||||
if (SelectedRound == null)
|
||||
{
|
||||
StatusMessage = "Select a round first";
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedMatch == null)
|
||||
{
|
||||
StatusMessage = "No match selected";
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedRound.State != RoundState.Started)
|
||||
{
|
||||
StatusMessage = "Round must be in Started state to award victory";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(parameter?.ToString(), out int teamIndex))
|
||||
{
|
||||
StatusMessage = "Invalid team index";
|
||||
return;
|
||||
}
|
||||
|
||||
var round = await _context.Rounds.FindAsync(SelectedRound.Id);
|
||||
if (round == null)
|
||||
{
|
||||
StatusMessage = "Round not found";
|
||||
return;
|
||||
}
|
||||
|
||||
round.State = RoundState.Finished;
|
||||
|
||||
var match = await _context.Matches
|
||||
.Where(m => m.Id == SelectedMatch.Id)
|
||||
.Include(m => m.Teams)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (match != null && teamIndex >= 0 && teamIndex < match.Teams.Count)
|
||||
{
|
||||
match.Teams[teamIndex].Score++;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Promote winner and loser to next matches
|
||||
await PromoteTeams(match);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var teamName = teamIndex < MatchTeams.Count ? MatchTeams[teamIndex].TeamName : "Unknown";
|
||||
StatusMessage = $"Victory awarded to {teamName}";
|
||||
await LoadMatches();
|
||||
await LoadMatchDetails();
|
||||
}
|
||||
|
||||
private async Task PromoteTeams(Match? match)
|
||||
{
|
||||
if (match == null || match.Teams.Count < 2)
|
||||
return;
|
||||
|
||||
// Determine winner (higher score) and loser
|
||||
var sortedTeams = match.Teams.OrderByDescending(t => t.Score).ToList();
|
||||
var winner = sortedTeams[0];
|
||||
var loser = sortedTeams[1];
|
||||
|
||||
// Promote winner to WinnerMatch
|
||||
if (match.WinnerMatchId.HasValue)
|
||||
{
|
||||
var winnerMatch = await _context.Matches.FindAsync(match.WinnerMatchId.Value);
|
||||
if (winnerMatch != null && !winnerMatch.Teams.Any(t => t.TeamId == winner.TeamId))
|
||||
{
|
||||
winnerMatch.Teams.Add(new TeamParticipant
|
||||
{
|
||||
TeamId = winner.TeamId,
|
||||
Seed = winner.Seed,
|
||||
Score = 0,
|
||||
Team = winner.Team,
|
||||
MatchId = winnerMatch.Id,
|
||||
Round = null!
|
||||
});
|
||||
|
||||
// Check if winnerMatch now has a bye (only 1 team) and needs recursive promotion
|
||||
if (winnerMatch.Teams.Count == 1 && winnerMatch.WinnerMatchId.HasValue)
|
||||
{
|
||||
await PromoteRecursive(winnerMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Promote loser to LoserMatch (for double elimination)
|
||||
// Only if this match has a LoserMatchId (meaning it's a winner bracket match)
|
||||
if (match.LoserMatchId.HasValue)
|
||||
{
|
||||
var loserMatch = await _context.Matches.FindAsync(match.LoserMatchId.Value);
|
||||
if (loserMatch != null && !loserMatch.Teams.Any(t => t.TeamId == loser.TeamId))
|
||||
{
|
||||
loserMatch.Teams.Add(new TeamParticipant
|
||||
{
|
||||
TeamId = loser.TeamId,
|
||||
Seed = loser.Seed,
|
||||
Score = 0,
|
||||
Team = loser.Team,
|
||||
MatchId = loserMatch.Id,
|
||||
Round = null!
|
||||
});
|
||||
|
||||
// Check if loserMatch now has a bye and needs recursive promotion
|
||||
if (loserMatch.Teams.Count == 1 && loserMatch.WinnerMatchId.HasValue)
|
||||
{
|
||||
await PromoteRecursive(loserMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PromoteRecursive(Match match)
|
||||
{
|
||||
if (match.Teams.Count != 1 || !match.WinnerMatchId.HasValue)
|
||||
return;
|
||||
|
||||
var team = match.Teams.First();
|
||||
var nextMatch = await _context.Matches.FindAsync(match.WinnerMatchId.Value);
|
||||
|
||||
if (nextMatch != null && !nextMatch.Teams.Any(t => t.TeamId == team.TeamId))
|
||||
{
|
||||
nextMatch.Teams.Add(new TeamParticipant
|
||||
{
|
||||
TeamId = team.TeamId,
|
||||
Seed = team.Seed,
|
||||
Score = 0,
|
||||
Team = team.Team,
|
||||
MatchId = nextMatch.Id,
|
||||
Round = null!
|
||||
});
|
||||
|
||||
// Continue recursive promotion if needed
|
||||
if (nextMatch.Teams.Count == 1 && nextMatch.WinnerMatchId.HasValue)
|
||||
{
|
||||
await PromoteRecursive(nextMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TeamParticipantDisplay
|
||||
{
|
||||
public int TeamId { get; set; }
|
||||
public string TeamName { get; set; } = string.Empty;
|
||||
public int Score { get; set; }
|
||||
public int Seed { get; set; }
|
||||
|
||||
public TeamParticipantDisplay() { }
|
||||
|
||||
public TeamParticipantDisplay(TeamParticipant tp)
|
||||
{
|
||||
TeamId = tp.TeamId;
|
||||
TeamName = tp.Team.Name;
|
||||
Score = tp.Score;
|
||||
Seed = tp.Seed;
|
||||
}
|
||||
}
|
||||
|
||||
public class RoundDisplay
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public RoundState State { get; set; }
|
||||
|
||||
public RoundDisplay() { }
|
||||
|
||||
public RoundDisplay(Round round)
|
||||
{
|
||||
Id = round.Id;
|
||||
State = round.State;
|
||||
}
|
||||
}
|
||||
@@ -489,9 +489,111 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
break;
|
||||
}
|
||||
|
||||
// Auto-promote teams with byes (TBD opponents with no parent match)
|
||||
await AutoPromoteByeTeams(tournament.Id);
|
||||
|
||||
await LoadMatches();
|
||||
}
|
||||
|
||||
private async Task AutoPromoteByeTeams(int tournamentId)
|
||||
{
|
||||
// Find matches where a team has a TBD opponent (only 1 team in match) and no parent match
|
||||
var matchesToPromote = await _context.Matches
|
||||
.Where(m => m.TournamentId == tournamentId && m.Teams.Count == 1)
|
||||
.Include(m => m.Teams)
|
||||
.Include(m => m.WinnerMatch)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var match in matchesToPromote)
|
||||
{
|
||||
// Check if this match has no incoming teams (no parent matches pointing to it)
|
||||
var hasParent = await _context.Matches
|
||||
.AnyAsync(m => m.WinnerMatchId == match.Id || m.LoserMatchId == match.Id);
|
||||
|
||||
if (!hasParent && match.WinnerMatchId.HasValue)
|
||||
{
|
||||
// This is a bye match - promote the team to the next match
|
||||
var team = match.Teams.First();
|
||||
var winnerMatch = match.WinnerMatch;
|
||||
if (winnerMatch != null)
|
||||
{
|
||||
// Add team to the winner match (position doesn't matter)
|
||||
if (!winnerMatch.Teams.Any(t => t.TeamId == team.TeamId))
|
||||
{
|
||||
winnerMatch.Teams.Add(new TeamParticipant
|
||||
{
|
||||
TeamId = team.TeamId,
|
||||
Seed = team.Seed,
|
||||
Score = 0,
|
||||
Team = team.Team,
|
||||
MatchId = winnerMatch.Id,
|
||||
Round = null!
|
||||
});
|
||||
}
|
||||
|
||||
// Mark the current match's round as finished since it was a bye
|
||||
if (match.Rounds.Any())
|
||||
{
|
||||
match.Rounds.Last().State = RoundState.Finished;
|
||||
}
|
||||
|
||||
// Recursively check if the destination match now has all teams and is also a bye
|
||||
if (winnerMatch.Teams.Count == 1)
|
||||
{
|
||||
var winnerHasParent = await _context.Matches
|
||||
.AnyAsync(m => m.WinnerMatchId == winnerMatch.Id || m.LoserMatchId == winnerMatch.Id);
|
||||
|
||||
if (!winnerHasParent && winnerMatch.WinnerMatchId.HasValue)
|
||||
{
|
||||
// Recursively promote
|
||||
await PromoteRecursive(winnerMatch, tournamentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task PromoteRecursive(Match match, int tournamentId)
|
||||
{
|
||||
if (match.Teams.Count != 1 || !match.WinnerMatchId.HasValue)
|
||||
return;
|
||||
|
||||
var team = match.Teams.First();
|
||||
var winnerMatch = await _context.Matches
|
||||
.FindAsync(match.WinnerMatchId.Value);
|
||||
|
||||
if (winnerMatch != null)
|
||||
{
|
||||
// Add team to the winner match (position doesn't matter)
|
||||
if (!winnerMatch.Teams.Any(t => t.TeamId == team.TeamId))
|
||||
{
|
||||
winnerMatch.Teams.Add(new TeamParticipant
|
||||
{
|
||||
TeamId = team.TeamId,
|
||||
Seed = team.Seed,
|
||||
Score = 0,
|
||||
Team = team.Team,
|
||||
MatchId = winnerMatch.Id,
|
||||
Round = null!
|
||||
});
|
||||
}
|
||||
|
||||
if (match.Rounds.Any())
|
||||
{
|
||||
match.Rounds.Last().State = RoundState.Finished;
|
||||
}
|
||||
|
||||
// Check if we need to continue promoting
|
||||
if (winnerMatch.Teams.Count == 1 && winnerMatch.WinnerMatchId.HasValue)
|
||||
{
|
||||
await PromoteRecursive(winnerMatch, tournamentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateSingleElimination(Tournament tournament, List<TeamParticipant> teams)
|
||||
{
|
||||
int numTeams = teams.Count;
|
||||
@@ -504,6 +606,7 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
int totalRounds = (int)Math.Log2(nextPowerOf2);
|
||||
var allMatches = new List<Match>();
|
||||
|
||||
// Create all matches first
|
||||
for (int round = 0; round < totalRounds; round++)
|
||||
{
|
||||
int matchesInRound = nextPowerOf2 / (int)Math.Pow(2, round + 1);
|
||||
@@ -511,7 +614,24 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
for (int i = 0; i < matchesInRound; i++)
|
||||
{
|
||||
var match = CreateMatch(tournament);
|
||||
allMatches.Add(match);
|
||||
}
|
||||
}
|
||||
|
||||
_context.Matches.AddRange(allMatches);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Now set up bracket navigation
|
||||
int matchIndex = 0;
|
||||
for (int round = 0; round < totalRounds; round++)
|
||||
{
|
||||
int matchesInRound = nextPowerOf2 / (int)Math.Pow(2, round + 1);
|
||||
|
||||
for (int i = 0; i < matchesInRound; i++)
|
||||
{
|
||||
var match = allMatches[matchIndex];
|
||||
|
||||
// Set up teams for first round
|
||||
if (round == 0)
|
||||
{
|
||||
int highSeed = i + 1;
|
||||
@@ -523,11 +643,25 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
AddTeamToMatch(match, teams[lowSeed - 1], lowSeed);
|
||||
}
|
||||
|
||||
allMatches.Add(match);
|
||||
// Set winner goes to next round match
|
||||
if (round < totalRounds - 1)
|
||||
{
|
||||
int nextRoundMatches = nextPowerOf2 / (int)Math.Pow(2, round + 2);
|
||||
int nextMatchIndex = 0;
|
||||
for (int r = 0; r <= round; r++)
|
||||
{
|
||||
nextMatchIndex += nextPowerOf2 / (int)Math.Pow(2, r + 1);
|
||||
}
|
||||
int nextMatchInRound = i / 2;
|
||||
int targetMatchIndex = nextMatchIndex + nextMatchInRound;
|
||||
|
||||
match.WinnerMatchId = allMatches[targetMatchIndex].Id;
|
||||
}
|
||||
|
||||
matchIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
_context.Matches.AddRange(allMatches);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -543,6 +677,7 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
int wbRounds = (int)Math.Log2(nextPowerOf2);
|
||||
var allMatches = new List<Match>();
|
||||
|
||||
// Create winners bracket matches
|
||||
var wbMatchesByRound = new List<List<Match>>();
|
||||
for (int r = 0; r < wbRounds; r++)
|
||||
{
|
||||
@@ -556,9 +691,10 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
wbMatchesByRound.Add(roundMatches);
|
||||
}
|
||||
|
||||
// Create losers bracket matches
|
||||
var lbMatchesByRound = new List<List<Match>>();
|
||||
if (numTeams > 2)
|
||||
{
|
||||
var lbMatchesByRound = new List<List<Match>>();
|
||||
for (int r = 0; r < wbRounds - 1; r++)
|
||||
{
|
||||
int matchesInRound = nextPowerOf2 / 4;
|
||||
@@ -573,11 +709,89 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
|
||||
var lbFinal = CreateMatch(tournament);
|
||||
allMatches.Add(lbFinal);
|
||||
lbMatchesByRound.Add(new List<Match> { lbFinal });
|
||||
|
||||
var grandFinals = CreateMatch(tournament);
|
||||
allMatches.Add(grandFinals);
|
||||
}
|
||||
|
||||
// Save all matches first to get IDs
|
||||
_context.Matches.AddRange(allMatches);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Set up winners bracket navigation
|
||||
int wbMatchIndex = 0;
|
||||
for (int r = 0; r < wbRounds; r++)
|
||||
{
|
||||
int matchesInRound = nextPowerOf2 / (int)Math.Pow(2, r + 1);
|
||||
for (int i = 0; i < matchesInRound; i++)
|
||||
{
|
||||
var match = wbMatchesByRound[r][i];
|
||||
|
||||
// Winner goes to next WB round
|
||||
if (r < wbRounds - 1)
|
||||
{
|
||||
var nextMatch = wbMatchesByRound[r + 1][i / 2];
|
||||
match.WinnerMatchId = nextMatch.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// WB final winner goes to grand finals
|
||||
var grandFinals = allMatches.Last();
|
||||
match.WinnerMatchId = grandFinals.Id;
|
||||
}
|
||||
|
||||
// Loser drops to losers bracket
|
||||
if (r > 0 && lbMatchesByRound.Count > 0)
|
||||
{
|
||||
int lbRound = r - 1;
|
||||
if (lbRound < lbMatchesByRound.Count - 1) // Not LB final
|
||||
{
|
||||
int lbMatchIndex = i / 2;
|
||||
if (lbMatchIndex < lbMatchesByRound[lbRound].Count)
|
||||
{
|
||||
var lbMatch = lbMatchesByRound[lbRound][lbMatchIndex];
|
||||
match.LoserMatchId = lbMatch.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (r == 0 && lbMatchesByRound.Count > 0)
|
||||
{
|
||||
// First round losers go to first LB round
|
||||
int lbMatchIndex = i / 2;
|
||||
if (lbMatchIndex < lbMatchesByRound[0].Count)
|
||||
{
|
||||
var lbMatch = lbMatchesByRound[0][lbMatchIndex];
|
||||
match.LoserMatchId = lbMatch.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up losers bracket navigation
|
||||
for (int r = 0; r < lbMatchesByRound.Count; r++)
|
||||
{
|
||||
for (int i = 0; i < lbMatchesByRound[r].Count; i++)
|
||||
{
|
||||
var match = lbMatchesByRound[r][i];
|
||||
|
||||
if (r < lbMatchesByRound.Count - 2)
|
||||
{
|
||||
// Winner goes to next LB round
|
||||
var nextMatch = lbMatchesByRound[r + 1][i / 2];
|
||||
match.WinnerMatchId = nextMatch.Id;
|
||||
}
|
||||
else if (r == lbMatchesByRound.Count - 2)
|
||||
{
|
||||
// LB final winner goes to grand finals
|
||||
var grandFinals = allMatches.Last();
|
||||
match.WinnerMatchId = grandFinals.Id;
|
||||
}
|
||||
// LB losers are eliminated - no LoserMatchId
|
||||
}
|
||||
}
|
||||
|
||||
// Set up teams for first round
|
||||
var firstRoundMatches = wbMatchesByRound[0];
|
||||
for (int i = 0; i < nextPowerOf2 / 2; i++)
|
||||
{
|
||||
@@ -591,7 +805,6 @@ public partial class TournamentsViewModel : ViewModelBase
|
||||
AddTeamToMatch(match, teams[lowSeed - 1], lowSeed);
|
||||
}
|
||||
|
||||
_context.Matches.AddRange(allMatches);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user