/// <summary> /// Resets the Bracket. /// Affects Matches, Rankings, and bracket status. /// Also resets the private Matchups and PlayerByes lists. /// </summary> protected override void ResetBracketData() { base.ResetBracketData(); ActiveRound = 0; if (null == Matchups) { Matchups = new List <Matchup>(); } if (null == PlayerByes) { PlayerByes = new List <int>(); } Matchups.Clear(); PlayerByes.Clear(); }
/// <summary> /// Places players with matching W/L scores into groups. /// These groups are used to create & weigh potential matchups. /// </summary> /// <returns>List of groups (each is a list of Player indexes)</returns> private List <List <int> > CreateGroups() { // If playercount is odd, find the top-ranked player to give a Bye // (no player should have >1 bye) int currentByeId = -1; if (Players.Count % 2 > 0) { foreach (int id in Rankings.Select(r => r.Id)) { if (!PlayerByes.Contains(id)) { PlayerByes.Add(id); currentByeId = id; break; } } } // Create score-brackets (groups) of players, separated by their MatchScore: List <List <int> > groups = new List <List <int> >(); for (int i = 0; i < Rankings.Count; ++i) { if (PlayerByes.Count > 0 && currentByeId == Rankings[i].Id) { // This player has a bye this round. Do not add him to groups! continue; } int prevIndex = i - 1; if (PlayerByes.Count > 0 && prevIndex >= 0 && currentByeId == Rankings[prevIndex].Id) { // Prev player has a bye this round. Decrement the index: --prevIndex; } if (prevIndex < 0 || Rankings[i].CalculateScore(MatchWinValue, MatchTieValue, 0) < Rankings[prevIndex].CalculateScore(MatchWinValue, MatchTieValue, 0)) { // New MatchPoints value, so we add a new group: groups.Add(new List <int>()); } // Add player's index in the Players array: // this value represents the player in these heuristic methods. groups[groups.Count - 1].Add (Players.FindIndex(p => p.Id == Rankings[i].Id)); } // Sort the players within each group according to their accumulated opponents' scores #if false foreach (List <int> group in groups) { group.Sort( (first, second) => Rankings.FindIndex(r => r.Id == first) .CompareTo(Rankings.FindIndex(r => r.Id == second))); } #endif // Make sure each group has an even playercount. // This is done for matchmaking purposes. for (int i = 0; i < groups.Count; ++i) { if (groups[i].Count % 2 > 0) { // If group.count is odd, take top player out of next group, // and shift him up to current group: int playerIndex = groups[i + 1][0]; groups[i].Add(playerIndex); groups[i + 1].RemoveAt(0); } } return(groups); }
public SwissBracket(BracketModel _model) : base(_model) { /* * This constructor extends the parent (round robin) version. * By the time we're "here," we already have our main data restored: * Players, Matches, and inherited Bracket fields. * Now, we need to recreate the Swiss-specific fields: * Matchups, PlayerByes, and PairingMethod. */ for (int r = 1; r <= NumberOfRounds; ++r) { List <IMatch> round = GetRound(r); if (round.Any(m => m.Players.Contains(null))) { // This round isn't populated yet. Break out: break; } // playersInRound is a list of all players (by index) // in matchups this round. We check for the missing player // to know who got a bye. List <int> playersInRound = new List <int>(); foreach (IMatch match in round) { // For each populated Match, add a Matchup to the private list: int defIndex = Players.FindIndex(p => p.Id == match.Players[(int)PlayerSlot.Defender].Id); int chalIndex = Players.FindIndex(p => p.Id == match.Players[(int)PlayerSlot.Challenger].Id); playersInRound.Add(defIndex); playersInRound.Add(chalIndex); Matchups.Add(new Matchup(defIndex, chalIndex, r)); } if (playersInRound.Count < Players.Count) { // Find the player with a bye this round: int byePlayerIndex = Enumerable .Range(0, Players.Count).ToList() .Except(playersInRound).First(); // Add him to Byes list and award a "Win" Outcome: PlayerByes.Add(Players[byePlayerIndex].Id); Rankings.Find(p => p.Id == Players[byePlayerIndex].Id) .AddMatchOutcome(Outcome.Win, true); } // Set ActiveRound (this finds the last populated round): ActiveRound = r; } // Determine the Pairing Method from examining round 1: // (default is Slide) this.PairingMethod = PairingMethod.Slide; if (NumberOfMatches > 0 && ActiveRound > 0) { #if CHOOSE_PAIRING // Find the top seed's first Match: int firstPlayerIndex = (0 == PlayerByes.Count) ? 0 : 1; IMatch firstPlayerMatch = GetRound(1) .Where(m => m.Players.Select(p => p.Id).Contains(this.Players[firstPlayerIndex].Id)) .First(); // Find the ID of the Player the top seed is matched against: int secondPlayerId = firstPlayerMatch.Players .Select(p => p.Id) .Where(i => i != this.Players[firstPlayerIndex].Id) .First(); if (Players[1 + firstPlayerIndex].Id == secondPlayerId) { // The second Player is also the second seed. // This must be Adjancent pairings. PairingMethod = PairingMethod.Adjacent; } else if (Players.Last().Id == secondPlayerId) { // The second Player is the last seed. // This must be Fold pairings. PairingMethod = PairingMethod.Fold; } #endif if (PlayerByes.Count > 0) { // If we added points for byes, we need to update rankings. // Call the parent (round robin) method: UpdateRankings(); } } }
/// <summary> /// Resets every round after the given round index. /// This removes the Players from every affected Match, /// in addition to resetting their Games and scores. /// Will NOT remove the Players from round 1, even if passed a 0. /// Sets ActiveRound to the given round index. /// May fire MatchesModified and GamesDeleted events, if updates occur. /// If _currentRoundIndex is out of range, nothing is modified. /// </summary> /// <param name="_currentRoundIndex">Reset all rounds after this index</param> /// <returns>List of Models of altered Matches</returns> private List <MatchModel> RemoveFutureRounds(int _currentRoundIndex) { List <MatchModel> clearedMatches = new List <MatchModel>(); List <int> deletedGameIDs = new List <int>(); int nextRoundIndex = 1 + _currentRoundIndex; if (nextRoundIndex > NumberOfRounds) { // Recursive exit: we've reached the final round. return(clearedMatches); } // Recursive call on all rounds after this one: clearedMatches.AddRange(RemoveFutureRounds(nextRoundIndex)); if (_currentRoundIndex >= 0) { // Reset all Matches in this round: List <IMatch> nextRound = GetRound(nextRoundIndex); foreach (Match match in nextRound) { if (!(match.Players.Contains(null)) || match.Games.Count > 0 || match.IsManualWin) { deletedGameIDs.AddRange(match.Games.Select(g => g.Id)); if (nextRoundIndex > 1) { // Remove the Players (and reset score & Games): match.ResetPlayers(); } else { // Keep Players if Round = 1 (still reset score): match.ResetScore(); } // Save a Model of the updated Match: clearedMatches.Add(GetMatchModel(match)); } } if (nextRoundIndex > 1) { // For all rounds after 1, // delete associated Matchups and Bye: Matchups.RemoveAll(m => m.RoundNumber == nextRoundIndex); if (PlayerByes.Count == nextRoundIndex) { // Remove the last Bye in the list: PlayerByes.RemoveAt(PlayerByes.Count - 1); } // Update bracket Properties: ActiveRound = _currentRoundIndex; } // Fire notification event: Games were deleted. OnGamesDeleted(deletedGameIDs); } // Return a list of modified MatchModels: return(clearedMatches); }