/// <summary> /// Attempts to create a new round of (legal) Swiss matchups. /// This takes all prior results into account for the following: /// - Match strong players together, and weak players together. /// - No rematches. /// - No repeat byes. /// If a new legal round is created, the next Bracket round is populated. /// </summary> /// <param name="_gamesPerMatch">Max Games for each Match</param> /// <returns>true on success, false on failure</returns> private bool AddSwissRound(int _gamesPerMatch) { // Don't try to populate nonexistent rounds: if (ActiveRound >= NumberOfRounds) { return(false); } #region Swiss Algorithm // CreateGroups() divides players into "groups" // based on their W/L record. List <List <int> > scoreBrackets = CreateGroups(); // GetHeuristicEdges() generates a graph representing // matchup preferences between every player. // Players with the same record and no prior matchups // will have a very low heuristic value here. List <int[]> possibleMatches = GetHeuristicEdges(scoreBrackets); // Access the Python weight-matching script: var engine = IronPython.Hosting.Python.CreateEngine(); var scope = engine.CreateScope(); engine.ExecuteFile("mwmatching.py", scope); dynamic maxWeightMatching = scope.GetVariable("maxWeightMatching"); // Calling maxWeightMatching returns a Swiss matchup solution (if possible). // The resulting list is a list of legal matchups: IronPython.Runtime.List pySolution = maxWeightMatching(possibleMatches, true); #endregion #region Legal Checks // Double-check that all the new matchups are Swiss-legal. // Create a list of all the new Matchups: List <Matchup> newRoundMatchups = new List <Matchup>(); for (int i = 0; i < pySolution.Count; ++i) { // 'i' = 'Defender index' // 'pySolution[i]' = 'Challenger index' int challengerIndex = Convert.ToInt32(pySolution[i]); if (i == challengerIndex) { // Player is matched against himself! return(false); } else if (i > challengerIndex) { continue; } Matchup newMatchup = new Matchup(i, challengerIndex, (1 + ActiveRound)); foreach (Matchup m in Matchups) { if (m.HasMatchingPlayers(newMatchup)) { // This is a rematch from a previous round! return(false); } } foreach (Matchup m in newRoundMatchups) { if (m.ContainsInt(newMatchup.DefenderIndex) || m.ContainsInt(newMatchup.ChallengerIndex)) { // A player has multiple matchups this round! return(false); } } newRoundMatchups.Add(newMatchup); } if (newRoundMatchups.Count < (int)(Players.Count * 0.5)) { // Not enough Matchups were created! // (probably means: unable to create enough legal matches) return(false); } #endregion #region New Round Populating // CONFIRMED LEGAL! // Populate the next round of Matches with these Players: for (int i = 0; i < newRoundMatchups.Count; ++i) { int matchNum = (i + 1 + (newRoundMatchups.Count * ActiveRound)); Matches[matchNum].AddPlayer(Players[newRoundMatchups[i].DefenderIndex]); Matches[matchNum].AddPlayer(Players[newRoundMatchups[i].ChallengerIndex]); } ++ActiveRound; // Add the new Matchups to our matchups list: Matchups.AddRange(newRoundMatchups); // Now that we have a new legal round... // Award points to the player with a bye, if there is one: if (PlayerByes.Count > 0) { int rIndex = Rankings.FindIndex(r => r.Id == PlayerByes[PlayerByes.Count - 1]); Rankings[rIndex].AddMatchOutcome(Outcome.Win, true); UpdateRankings(); } #endregion return(true); }