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);
        }
Beispiel #3
0
        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)
                                  ));
            }
        }