public void Should_Be_Awarded_No_Match_Points(SwissStatisticsProvider sut, IEnumerable<Round> rounds, Player A, Player B) { var actual = sut .GetMatchPoints(rounds, A); Assert.Equal(0.0m, actual); }
public IEnumerable <Round> BuildRounds(SwissTournamentContext context, int?throughRoundNumber = null) { // Round 1 pairing is completely random. // Subsequent rounds have players grouped by match points and randomly // ordered within that group. // If there's an uneven number of players, the player with the lowest // match points who has not yet had a bye is given a bye. // Then, going from the highest match points to the lowest, the topmost // player is paired with the second topmost player. If they've had a match // before, the third topmost player is paired, continuing on until a // player is found that has not had a match with the topmost. Both players // are then removed from the pool of available players and the process // repeats. // We accomplish random pairing in round 1 by all players having zero // match points and using the above algorithm. // Byes are assigned first, then pairings. I haven't been able to devise // a scenario where two players end up getting paired twice because a player // was given a bye, but this system does introduce the potential for it. var roundNumber = throughRoundNumber ?? context.ActiveRound ?? 1; var roundSeed = context.TournamentSeed ^ roundNumber; var roundRng = new Random(roundSeed); var previousRounds = roundNumber == 1 ? Enumerable.Empty <Round>() : BuildRounds(context, roundNumber - 1); var pairingPool = context .Players .Where(player => player.RoundDropped == null || player.RoundDropped > roundNumber) .Select(player => new { Player = player, MatchPoints = StatisticsProvider.GetMatchPoints(previousRounds, player), Randomizer = roundRng.Next(), }) .OrderByDescending(o => o.MatchPoints) .ThenBy(o => o.Randomizer) .Select(o => o.Player) .ToArray() // Flatten to ensure the RNG is only called once per player .AsEnumerable(); var roundMatchResults = context .MatchResults .Where(mr => mr.RoundNumber == roundNumber) .ToArray(); var previousByes = previousRounds .SelectMany(round => round.Matches) .Where(match => match.Players.Count() == 1) .SelectMany(match => match.Players) .ToArray(); var previousPairings = previousRounds .SelectMany(round => round.Matches) .Select(match => match .Players .OrderBy(player => player.Identifier)) .ToArray(); // Find the first match set that doesn't include a rematch var matchSets = GenerateMatchSets(pairingPool, previousByes, roundMatchResults); var selectedMatchSet = matchSets .Where(matchSet => matchSet .Select(match => match .Players .OrderBy(player => player.Identifier) ) .All(players => !previousPairings .Any(previous => previous .SequenceEqual(players) ) ) ) .FirstOrDefault() ?? matchSets.First(); return(previousRounds .Concat(new Round( roundNumber, selectedMatchSet .OrderBy(match => match.Number) .ToArray() )) .ToArray()); }