public void One_Player_Should_Get_A_Bye(SwissRankingEngine sut, Player A, Player B, Player C) { // Seed 7357 = Round 1 pairing order { B, C, A } var context = new SwissTournamentContext( 7357, TournamentState.TournamentCreated, new[] { A, B, C }, null, null); var actual = sut.GetPairings(context); Assert.Equal(actual.Count(), 2); var firstPairing = actual.ElementAt(0); Assert.Equal(2, firstPairing.Players.Count()); Assert.Contains(B, firstPairing.Players); Assert.Contains(C, firstPairing.Players); var byePairing = actual.ElementAt(1); Assert.Equal(1, byePairing.Players.Count()); Assert.Contains(A, byePairing.Players); }
public void All_Players_Should_Be_Paired(SwissRankingEngine sut, Player A, Player B, Player C, Player D) { // Seed 7357 = Round 1 pairing order { B, C, A, D } var context = new SwissTournamentContext( 7357, TournamentState.TournamentCreated, new[] { A, B, C, D }, null, null); var actual = sut.GetPairings(context); Assert.Equal(actual.Count(), 2); var firstPairing = actual.ElementAt(0); Assert.Equal(2, firstPairing.Players.Count()); Assert.Contains(B, firstPairing.Players); Assert.Contains(C, firstPairing.Players); var secondPairing = actual.ElementAt(1); Assert.Equal(2, secondPairing.Players.Count()); Assert.Contains(A, secondPairing.Players); Assert.Contains(D, secondPairing.Players); }
public void Should_Generate_All_Possible_Pairings() { var generator = new SwissRankingEngine(new SwissStatisticsProvider()); var players = Enumerable .Range(0, 26) .Select(number => new String(new[] { (char)('a' + number) })) .Select(name => new Player(name, null)); // Careful with the playerCount. 12 will take almost a minute to execute. for(int playerCount = 2; playerCount <= 10; playerCount += 2) { var matchSets = generator.GeneratePairedMatches(players.Take(playerCount), Enumerable.Empty<MatchResult>(), 1); var uniquePairings = matchSets .Select(matchSet => matchSet .Select(match => match .Players .OrderBy(player => player.Identifier) .Select(player => player.Identifier) ) .Select(playerIdentifiers => String.Join("-", playerIdentifiers)) .OrderBy(pairing => pairing) ) .Select(playerIdentifiers => String.Join("|", playerIdentifiers)) .OrderBy(matchSetPairings => matchSetPairings) .Distinct() .ToArray(); // Expected number of pairings is (2n - 1)!! where n = playerCount / 2 // See http://en.wikipedia.org/wiki/Perfect_matching and http://en.wikipedia.org/wiki/Double_factorial for reasoning // See http://mathworld.wolfram.com/DoubleFactorial.html for the definition of (2n - 1)!! used here var expectedUniquePairings = (int)(Factorial(playerCount) / (Math.Pow(2, playerCount / 2) * Factorial(playerCount / 2))); var actualUniquePairings = uniquePairings.Count(); Assert.Equal(expectedUniquePairings, actualUniquePairings); } }
public void Should_Generate_All_Possible_Pairings() { var generator = new SwissRankingEngine(new SwissStatisticsProvider()); var players = Enumerable .Range(0, 26) .Select(number => new String(new[] { (char)('a' + number) })) .Select(name => new Player(name, null)); // Careful with the playerCount. 12 will take almost a minute to execute. for (int playerCount = 2; playerCount <= 10; playerCount += 2) { var matchSets = generator.GeneratePairedMatches(players.Take(playerCount), Enumerable.Empty <MatchResult>(), 1); var uniquePairings = matchSets .Select(matchSet => matchSet .Select(match => match .Players .OrderBy(player => player.Identifier) .Select(player => player.Identifier) ) .Select(playerIdentifiers => String.Join("-", playerIdentifiers)) .OrderBy(pairing => pairing) ) .Select(playerIdentifiers => String.Join("|", playerIdentifiers)) .OrderBy(matchSetPairings => matchSetPairings) .Distinct() .ToArray(); // Expected number of pairings is (2n - 1)!! where n = playerCount / 2 // See http://en.wikipedia.org/wiki/Perfect_matching and http://en.wikipedia.org/wiki/Double_factorial for reasoning // See http://mathworld.wolfram.com/DoubleFactorial.html for the definition of (2n - 1)!! used here var expectedUniquePairings = (int)(Factorial(playerCount) / (Math.Pow(2, playerCount / 2) * Factorial(playerCount / 2))); var actualUniquePairings = uniquePairings.Count(); Assert.Equal(expectedUniquePairings, actualUniquePairings); } }
public void It_Just_Freaking_Works() { // Yay happy path! var statisticsProvider = new SwissStatisticsProvider(); var rankingEngine = new SwissRankingEngine(statisticsProvider); var contextBuilder = new SwissTournamanetContextBuilder(statisticsProvider, rankingEngine); var commandEventHandler = new CommandEventHandler <SwissTournamentContext>(contextBuilder); var context = new SwissTournamentContext(0, TournamentState.None, null, null, null); // Create the tournament context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.CreateTournament.ToString(), properties: new Dictionary <string, string> { { "Timestamp", new DateTimeOffset(1982, 7, 22, 01, 14, 22, TimeSpan.FromHours(-7)).ToString() } }) ); Assert.Equal(TournamentState.TournamentCreated, context.State); Assert.NotEqual(0, context.TournamentSeed); // Add players context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary <string, string> { { "Player", "A" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary <string, string> { { "Player", "B" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary <string, string> { { "Player", "C" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary <string, string> { { "Player", "D" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary <string, string> { { "Player", "E" } }) ); Assert.Equal(TournamentState.TournamentStarted, context.State); Assert.NotEmpty(context.Players); var roundOnePairings = rankingEngine.GetPairings(context); // Record match results for round 1 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "A" }, { "Wins", "2" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "B" }, { "Wins", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "D" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "E" }, { "Draws", "1" }, }) ); // End round 1 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.EndRound.ToString(), properties: new Dictionary <string, string> { }) ); // Get standings as of round 1. If we include round 2, we get the bye points immediately. var roundOneStandings = rankingEngine.GetStandings(context, 1); var roundTwoPairings = rankingEngine.GetPairings(context); // Record match results for round 2 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "A" }, { "Draws", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "C" }, { "Wins", "2" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "D" }, { "Wins", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "E" }, }) ); // End round 2 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.EndRound.ToString(), properties: new Dictionary <string, string> { }) ); var roundTwoStandings = rankingEngine.GetStandings(context, 2); // Drop a player context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RemovePlayer.ToString(), properties: new Dictionary <string, string> { { "Player", "E" }, { "Wins", "1" }, }) ); // Record match results for round 3 var roundThreePairings = rankingEngine.GetPairings(context); // Record match results for round 3 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "A" }, { "Wins", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "B" }, { "Draws", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "C" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary <string, string> { { "Player", "D" }, { "Wins", "2" }, }) ); // End round 3 and the tournament context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.EndRound.ToString(), properties: new Dictionary <string, string> { }) ); var finalStandings = rankingEngine.GetStandings(context); }
public void It_Just_Freaking_Works() { // Yay happy path! var statisticsProvider = new SwissStatisticsProvider(); var rankingEngine = new SwissRankingEngine(statisticsProvider); var contextBuilder = new SwissTournamanetContextBuilder(statisticsProvider, rankingEngine); var commandEventHandler = new CommandEventHandler<SwissTournamentContext>(contextBuilder); var context = new SwissTournamentContext(0, TournamentState.None, null, null, null); // Create the tournament context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.CreateTournament.ToString(), properties: new Dictionary<string, string> { { "Timestamp", new DateTimeOffset(1982, 7, 22, 01, 14, 22, TimeSpan.FromHours(-7)).ToString() } }) ); Assert.Equal(TournamentState.TournamentCreated, context.State); Assert.NotEqual(0, context.TournamentSeed); // Add players context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary<string, string> { { "Player", "A" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary<string, string> { { "Player", "B" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary<string, string> { { "Player", "C" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary<string, string> { { "Player", "D" } }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.AddPlayer.ToString(), properties: new Dictionary<string, string> { { "Player", "E" } }) ); Assert.Equal(TournamentState.TournamentStarted, context.State); Assert.NotEmpty(context.Players); var roundOnePairings = rankingEngine.GetPairings(context); // Record match results for round 1 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "A" }, { "Wins", "2" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "B" }, { "Wins", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "D" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "E" }, { "Draws", "1" }, }) ); // End round 1 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.EndRound.ToString(), properties: new Dictionary<string, string> { }) ); // Get standings as of round 1. If we include round 2, we get the bye points immediately. var roundOneStandings = rankingEngine.GetStandings(context, 1); var roundTwoPairings = rankingEngine.GetPairings(context); // Record match results for round 2 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "A" }, { "Draws", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "C" }, { "Wins", "2" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "D" }, { "Wins", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "E" }, }) ); // End round 2 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.EndRound.ToString(), properties: new Dictionary<string, string> { }) ); var roundTwoStandings = rankingEngine.GetStandings(context, 2); // Drop a player context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RemovePlayer.ToString(), properties: new Dictionary<string, string> { { "Player", "E" }, { "Wins", "1" }, }) ); // Record match results for round 3 var roundThreePairings = rankingEngine.GetPairings(context); // Record match results for round 3 context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "A" }, { "Wins", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "B" }, { "Draws", "1" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "C" }, }) ); context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.RecordMatchResults.ToString(), properties: new Dictionary<string, string> { { "Player", "D" }, { "Wins", "2" }, }) ); // End round 3 and the tournament context = commandEventHandler.ProcessCommand( context: context, commandEvent: new CommandEvent( sequence: 0, aggregateId: "T1", name: TournamentCommand.EndRound.ToString(), properties: new Dictionary<string, string> { }) ); var finalStandings = rankingEngine.GetStandings(context); }