public static Rating GetPartialUpdate(Rating prior, Rating fullPosterior, double updatePercentage) { var priorGaussian = new GaussianDistribution(prior.Mean, prior.StandardDeviation); var posteriorGaussian = new GaussianDistribution(fullPosterior.Mean, fullPosterior.StandardDeviation); // From a clarification email from Ralf Herbrich: // "the idea is to compute a linear interpolation between the prior and posterior skills of each player // ... in the canonical space of parameters" double precisionDifference = posteriorGaussian.Precision - priorGaussian.Precision; double partialPrecisionDifference = updatePercentage*precisionDifference; double precisionMeanDifference = posteriorGaussian.PrecisionMean - priorGaussian.PrecisionMean; double partialPrecisionMeanDifference = updatePercentage*precisionMeanDifference; GaussianDistribution partialPosteriorGaussion = GaussianDistribution.FromPrecisionMean( priorGaussian.PrecisionMean + partialPrecisionMeanDifference, priorGaussian.Precision + partialPrecisionDifference); return new Rating(partialPosteriorGaussion.Mean, partialPosteriorGaussion.StandardDeviation, prior._ConservativeStandardDeviationMultiplier); }
private void refreshScoreBoxes() { Person p1 = new Person(); Person p2 = new Person(); foreach (Person person in playerList) { if (person.Name == player1Selector.Text) { p1 = person; } if (person.Name == player2Selector.Text) { p2 = person; } } p1MuDisplay.Text = p1.Mu.ToString(); p1SigmaDisplay.Text = p1.Sigma.ToString(); p1ScoreDisplay.Text = p1.Score.ToString(); p2MuDisplay.Text = p2.Mu.ToString(); p2SigmaDisplay.Text = p2.Sigma.ToString(); p2ScoreDisplay.Text = p2.Score.ToString(); p1WLDBox.Text = p1.Wins + " -- " + p1.Losses + " -- " + p1.Draws + " (" + p1.WinPercent + "%)"; p2WLDBox.Text = p2.Wins + " -- " + p2.Losses + " -- " + p2.Draws + " (" + p2.WinPercent + "%)"; Player p1s = new Player(1); Player p2s = new Player(2); Rating p1r = new Rating(p1.Mu, p1.Sigma); Rating p2r = new Rating(p2.Mu, p2.Sigma); Team t1 = new Team(p1s, p1r); Team t2 = new Team(p2s, p2r); matchQualBox.Text = (TrueSkillCalculator.CalculateMatchQuality(GameInfo.DefaultGameInfo, Teams.Concat(t1, t2)) * 100).ToString(); }
private void updatePlayerMatch(UInt16 winner) { Person p1 = new Person(); Person p2 = new Person(); foreach (Person person in playerList) { if (person.Name == player1Selector.Text) { p1 = person; } if (person.Name == player2Selector.Text) { p2 = person; } } Player p1s = new Player(1); Player p2s = new Player(2); Rating p1r = new Rating(p1.Mu, p1.Sigma); Rating p2r = new Rating(p2.Mu, p2.Sigma); Team t1 = new Team(p1s, p1r); Team t2 = new Team(p2s, p2r); IDictionary<Player, Rating> newRatings = null; if(winner == 0) newRatings = TrueSkillCalculator.CalculateNewRatings(GameInfo.DefaultGameInfo, Teams.Concat(t1, t2), 1, 1); else if(winner == 1) newRatings = TrueSkillCalculator.CalculateNewRatings(GameInfo.DefaultGameInfo, Teams.Concat(t1, t2), 1, 2); else if(winner == 2) newRatings = TrueSkillCalculator.CalculateNewRatings(GameInfo.DefaultGameInfo, Teams.Concat(t1, t2), 2, 1); p1.Mu = newRatings[p1s].Mean; p1.Sigma = newRatings[p1s].StandardDeviation; p2.Mu = newRatings[p2s].Mean; p2.Sigma = newRatings[p2s].StandardDeviation; foreach (Person person in playerList) { if (person.Name == player1Selector.Text) { person.Mu = p1.Mu; person.Sigma = p1.Sigma; if (winner == 0) person.Draws++; else if (winner == 1) person.Wins++; else if (winner == 2) person.Losses++; } if (person.Name == player2Selector.Text) { person.Mu = p2.Mu; person.Sigma = p2.Sigma; if (winner == 0) person.Draws++; else if (winner == 1) person.Losses++; else if (winner == 2) person.Wins++; } } requireSave = true; refreshScoreBoxes(); }
private static void AssertRating(double expected, Rating actual) { Assert.AreEqual(expected, actual.Mean, ErrorTolerance); }
/// <summary> /// Gets players that are most suited for competition (would provide as close as possible to an even match). Both @candidates and @target need to have the TSCharacterComponent attached. /// </summary> /// <param name="candidates">the possible candidates to match against</param> /// <param name="target">the player for whom we wish to find matches</param> /// <returns>a list of character IDs in order of </returns> public static List<SkillMatchInfo> GetTopQualityMatches(IEnumerable<ICharacterInfo> candidates, ICharacterInfo target, int maxResults) { DateTime start = DateTime.UtcNow; List<SkillMatchInfo> matches = new List<SkillMatchInfo>(); try { GameInfo gi = GameInfo.DefaultGameInfo; Player targetPlayer = new Player(target.ID); double targetMu = target.Properties.GetDoubleProperty((int)TSPropertyID.RatingMean).GetValueOrDefault(); double targetSigma = target.Properties.GetDoubleProperty((int)TSPropertyID.RatingStandardDeviation).GetValueOrDefault(); Rating targetRating = new Rating(targetMu, targetSigma); Team targetTeam = new Team(targetPlayer, targetRating); int numCandidates = 0; IEnumerator<ICharacterInfo> enu = candidates.GetEnumerator(); while (enu.MoveNext()) { numCandidates++; Player player = new Player(enu.Current.ID); double mu = enu.Current.Properties.GetDoubleProperty((int)TSPropertyID.RatingMean).GetValueOrDefault(); double sigma = enu.Current.Properties.GetDoubleProperty((int)TSPropertyID.RatingStandardDeviation).GetValueOrDefault(); Rating rating = new Rating(mu, sigma); Team team = new Team(player, rating); double quality = TrueSkillCalculator.CalculateMatchQuality(gi, Teams.Concat(targetTeam, team)); matches.Add(new SkillMatchInfo(enu.Current.ID, quality)); } // Sort it matches.OrderBy(i => i.MatchQuality); // trim it, if necessary if (maxResults > 0) { if (maxResults > matches.Count) { maxResults = matches.Count; } matches = matches.GetRange(0, maxResults - 1); } DateTime end = DateTime.UtcNow; TimeSpan exeTime = end - start; int highestQuality = 0; if (matches.Count > 0) { highestQuality = (int)Math.Floor(matches[0].MatchQuality * 100); } Log.LogMsg("TrueSkill match maker tested [" + numCandidates + "] candidates for character [" + target.CharacterName + " | " + target.ID + "]. Returned [" + matches.Count + "] possible matches in [" + exeTime.TotalMilliseconds + " ms]. Best match found had a [" + highestQuality + "%] quality rating."); } catch(Exception e) { Log.LogMsg("TrueSkill match maker encountered an error when searching for match candidates. " + e.Message); } return matches; }
public static void recalcMatches(List<Person> playerList, List<Match> matchList, Double startMu, Double startSigma, Double multiplier, UInt16 decay, UInt32 decayValue, DateTime lastDate, ProgressBar progress) { lastDate += new TimeSpan(23, 59, 59); Dictionary<String, Person> playerMap = new Dictionary<string, Person>(); DateTime latestMatch = DateTime.MinValue; int matchTotal = matchList.Count; int counted = 0; if (progress != null) { progress.Value = 0; progress.Refresh(); } foreach (Person person in playerList) { person.Mu = startMu; person.Sigma = startSigma; person.Wins = 0; person.Losses = 0; person.Draws = 0; person.Multiplier = multiplier; person.DecayDays = 0; person.DecayMonths = 0; playerMap.Add(person.Name, person); } foreach (Match match in matchList) { if (progress != null) { counted++; progress.Value = (counted * 100) / matchTotal; progress.PerformStep(); } if (match.Timestamp <= lastDate) { Person p1 = playerMap[match.Player1]; Person p2 = playerMap[match.Player2]; if (decay > 0) { uint i = 0; if (decay < 3) { while (p1.LastMatch.AddDays(i).CompareTo(match.Timestamp) < 0) { i++; } p1.DecayDays += i; i = 0; while (p2.LastMatch.AddDays(i).CompareTo(match.Timestamp) < 0) { i++; } p2.DecayDays += i; } else { i = 0; while (p1.LastMatch.AddMonths((int)i).CompareTo(match.Timestamp) < 0) { i++; } p1.DecayMonths += i; i = 0; while (p2.LastMatch.AddMonths((int)i).CompareTo(match.Timestamp) < 0) { i++; } p2.DecayMonths += i; } switch (decay) { case 1: while (p1.DecayDays > decayValue - 1) { p1.decayScore(startSigma); p1.DecayDays -= decayValue; } while (p2.DecayDays > decayValue - 1) { p2.decayScore(startSigma); p2.DecayDays -= decayValue; } break; case 2: while (p1.DecayDays > (7 * decayValue) - 1) { p1.decayScore(startSigma); p1.DecayDays -= 7 * decayValue; } while (p2.DecayDays > (7 * decayValue) - 1) { p2.decayScore(startSigma); p2.DecayDays -= 7 * decayValue; } break; case 3: while (p1.DecayMonths > decayValue - 1) { p1.decayScore(startSigma); p1.DecayMonths -= decayValue; } while (p2.DecayMonths > decayValue - 1) { p2.decayScore(startSigma); p2.DecayMonths -= decayValue; } break; case 4: while (p1.DecayMonths > (12 * decayValue) - 1) { p1.decayScore(startSigma); p1.DecayMonths -= 12 * decayValue; } while (p2.DecayMonths > (12 * decayValue) - 1) { p2.decayScore(startSigma); p2.DecayMonths -= 12 * decayValue; } break; } } match.P1Score = p1.Score; match.P2Score = p2.Score; Player p1s = new Player(1); Player p2s = new Player(2); Rating p1r = new Rating(p1.Mu, p1.Sigma); Rating p2r = new Rating(p2.Mu, p2.Sigma); Team t1 = new Team(p1s, p1r); Team t2 = new Team(p2s, p2r); IDictionary<Player, Rating> newRatings = null; if (match.Winner == 0) newRatings = TrueSkillCalculator.CalculateNewRatings(GameInfo.DefaultGameInfo, Teams.Concat(t1, t2), 1, 1); else if (match.Winner == 1) newRatings = TrueSkillCalculator.CalculateNewRatings(GameInfo.DefaultGameInfo, Teams.Concat(t1, t2), 1, 2); else if (match.Winner == 2) newRatings = TrueSkillCalculator.CalculateNewRatings(GameInfo.DefaultGameInfo, Teams.Concat(t1, t2), 2, 1); p1.Mu = newRatings[p1s].Mean; p1.Sigma = newRatings[p1s].StandardDeviation; p2.Mu = newRatings[p2s].Mean; p2.Sigma = newRatings[p2s].StandardDeviation; match.P1Score2 = p1.Score; match.P2Score2 = p2.Score; p1.LastMatch = match.Timestamp; p2.LastMatch = match.Timestamp; if (latestMatch < match.Timestamp) latestMatch = match.Timestamp; if (match.Winner == 0) { p1.Draws++; p2.Draws++; } else if (match.Winner == 1) { p1.Wins++; p2.Losses++; } else if (match.Winner == 2) { p1.Losses++; p2.Wins++; } } else break; } foreach (Person p in playerList) { if (decay > 0) { uint i = 0; while (p.LastMatch.AddDays(i).CompareTo(latestMatch) < 0) { i++; } p.DecayDays += i; i = 0; while (p.LastMatch.AddMonths((int)i).CompareTo(latestMatch) < 0) { i++; } p.DecayMonths += i; switch (decay) { case 1: while (p.DecayDays > 0) { p.decayScore(startSigma); p.DecayDays--; } break; case 2: while (p.DecayDays > 6) { p.decayScore(startSigma); p.DecayDays -= 7; } break; case 3: while (p.DecayMonths > 0) { p.decayScore(startSigma); p.DecayMonths--; } break; case 4: while (p.DecayMonths > 11) { p.decayScore(startSigma); p.DecayMonths -= 12; } break; } } } }
/// <summary> /// Adds the <paramref name="player"/> to the team. /// </summary> /// <param name="player">The player to add.</param> /// <param name="rating">The rating of the <paramref name="player"/>.</param> /// <returns>The instance of the team (for chaining convenience).</returns> public Team <TPlayer> AddPlayer(TPlayer player, Rating rating) { _PlayerRatings[player] = rating; return(this); }
/// <summary> /// Constructs a <see cref="Team"/> and populates it with the specified <paramref name="player"/>. /// </summary> /// <param name="player">The player to add.</param> /// <param name="rating">The rating of the <paramref name="player"/>.</param> public Team(TPlayer player, Rating rating) { AddPlayer(player, rating); }
//------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ private static void AssertRating(double expectedMean, double expectedStandardDeviation, Rating actual) { Assert.AreEqual(expectedMean, actual.Mean, ErrorTolerance); Assert.AreEqual(expectedStandardDeviation, actual.StandardDeviation, ErrorTolerance); }