private bool NeedToReverseSecond(IPlayerProvider playerProvider) { // Since the snake resets every two passes through the teams, we modulo by that to see which direction the snake is going // on its last leg var numMalePlayers = playerProvider.PresentPlayers().Count(p => p.Gender == Gender.Male) % (NumTeams * 2); var maleGoingRight = numMalePlayers < NumTeams; var numFemalePlayers = playerProvider.PresentPlayers().Count(p => p.Gender == Gender.Female) % (NumTeams * 2) - NumTeams; var femaleGoingRight = numFemalePlayers < NumTeams; // If both going the same direction at the end, we need to reverse the allocation of the second gender to ensure we don't // have too many people on a team return(maleGoingRight == femaleGoingRight); }
public List <Team> CreateTeams(IPlayerProvider playerProvider) { var teams = Enumerable.Range(0, NumTeams).Select(i => new Team(i + 1)).ToList(); // We restart the snaking per gender so that players should have good matchups. However, this has // the possible side effect of creating one team with 2 more players than their opponent (one more // male, one more female). We detect this possibility here and prevent it by iterating through the teams backwards var needToReverseSecond = NeedToReverseSecond(playerProvider); // Randomly spread out all the guru male players // Then the guru female players // Then the experienced males // Then the experienced females // etc. var groupedByGender = playerProvider.PresentPlayers().GroupBy(p => p.Gender); var thenGroupedByExperience = groupedByGender.Select(g => g.GroupBy(p => p.SkillLevel).OrderByDescending(g2 => g2.Key.Value)); foreach (var group in thenGroupedByExperience) { List <Player> players = new List <Player>(); foreach (var subGroup in group) { players.AddRange(subGroup.ToList().Shuffle()); } var teamLoop = teams.Snake().GetEnumerator(); foreach (var player in players) { teamLoop.MoveNext(); teamLoop.Current.AddPlayer(player); } if (needToReverseSecond) { teams.Reverse(); } } return(teams); }
private void PopulateRoundResults(IEnumerable <HatRound> rounds, IPlayerProvider playerProvider) { Log(String.Format("\nLog time: {0}", DateTime.Now.ToString("dd MMM yyyy HH:mm:ss"))); // reset the values var seed = int.Parse(DateTime.Now.ToString("yyyyMMdd")); // seed with today so can reproduce when testing var r = new Random(seed); // tested and this works foreach (var player in playerProvider.AllPlayers) { player.GamesPlayed = 0; player.NumberOfWins = 0; player.RandomForSort = r.Next(); } // calculate win percentages foreach (var hatRound in rounds) { foreach (var team in hatRound.Teams) { foreach (var player in team.Players) { var p = playerProvider.AllPlayers.First(x => x == player); if (p == null) { continue; } p.GamesPlayed++; if (team.GameResult == GameResult.Won) { p.NumberOfWins++; } } } } // calculate adjusted scores var averageScore = 0M; var playersWithAtLeastOneGame = playerProvider.AllPlayers.Where(x => x.GamesPlayed > 0).ToList(); if (playersWithAtLeastOneGame.Count > 0) { averageScore = Convert.ToDecimal(playersWithAtLeastOneGame.Average(x => x.GameScore)); } foreach (var player in playerProvider.AllPlayers) { // adjusted score is so that: // - players who have missed games, but are good (high win%) don't get treated like bad players // - we average the adjusted scores for the handicapping of players getting a long way ahead var experienceFactor = player.SkillLevel.Value / 100M; // ends up being something like: .2, .5, .6, .8, 1 if (player.GamesPlayed == 0 && rounds.Count() > 1) { experienceFactor += experienceFactor; // double the effect if they haven't played any games yet, and we are into the league (not round 1) } var winPercentFactor = (player.WinPercent - 50M) / 100; // this a negative factor for win % < 50, positive for > 50 var winsAndGamesPlayed = winPercentFactor * player.GamesPlayed; // if they haven't played any games - makes no difference var adjustedScore = averageScore + experienceFactor + winsAndGamesPlayed; player.AdjustedScore = Math.Max(player.GameScore, adjustedScore); // whichever is higher } var averageAdjustedScore = 0M; if (playersWithAtLeastOneGame.Count > 0) { averageAdjustedScore = Convert.ToDecimal(playersWithAtLeastOneGame.Average(x => x.AdjustedScore)); } _presentPlayers = Sort(playerProvider.PresentPlayers().ToList(), true); Log("Games played so far: " + rounds.Count() + "\n"); foreach (var player in _presentPlayers) { // figure out handicap var diffToAverage = player.AdjustedScore - averageAdjustedScore; player.Handicap = diffToAverage >= 0 ? (decimal)Math.Pow((double)diffToAverage, HandicapPowerNumber) : (decimal)Math.Pow((double)Math.Abs(diffToAverage), HandicapPowerNumber) * -1; Log(player.ToString()); } }
private void PopulateRoundResults(IEnumerable <HatRound> rounds, IPlayerProvider playerProvider) { Log(String.Format("\nLog time: {0}", DateTime.Now.ToString("dd MMM yyyy HH:mm:ss"))); // reset the values foreach (var player in playerProvider.AllPlayers) { player.GamesPlayed = 0; player.NumberOfWins = 0; } // calculate win percentages foreach (var hatRound in rounds) { foreach (var team in hatRound.Teams) { foreach (var player in team.Players) { var p = playerProvider.AllPlayers.First(x => x == player); if (p == null) { continue; } p.GamesPlayed++; if (team.GameResult == GameResult.Won) { p.NumberOfWins++; } } } } // calculate adjusted scores var averageScore = 0M; var playersWithMoreThanOneGame = playerProvider.AllPlayers.Where(x => x.GamesPlayed > 0).ToList(); if (playersWithMoreThanOneGame.Count > 0) { averageScore = Convert.ToDecimal(playersWithMoreThanOneGame.Average(x => x.GameScore)); } foreach (var player in playerProvider.AllPlayers) { // adjusted score is so that players who have missed games, but are good (high win%) don't get treated like bad players // start with the average of the whole field // based on win percent factor adjust the score up or down // score moves by factor times number of games played var winPercentFactor = (player.WinPercent - 50M) / 100; // this a negative factor for win % < 50, positive for > 50 var adjustedScore = averageScore + winPercentFactor * player.GamesPlayed; player.AdjustedScore = Math.Max(player.GameScore, adjustedScore); } _presentPlayers = Sort(playerProvider.PresentPlayers().ToList()); Log("Games played so far: " + rounds.Count() + "\n"); foreach (var player in _presentPlayers) { Log(String.Format("{0}\t{4}\tPlayed:{6}\tPoints/Adj:{5}/{1}\tWin:{2}%\tXP:{3}" , player.Name.PadRight(15) , player.AdjustedScore.ToString("0.00").PadLeft(5) , player.WinPercent.ToString("0").PadLeft(3) , player.SkillLevel.Value.ToString().PadLeft(3) , player.Gender , player.GameScore.ToString().PadLeft(2) , player.GamesPlayed.ToString().PadLeft(2) )); } }