using System; using System.Collections.Generic; using System.Linq; using Moserware.Skills.Numerics; namespace Moserware.Skills.TrueSkill { /// /// Calculates the new ratings for only two players. /// /// /// When you only have two players, a lot of the math simplifies. The main purpose of this class /// is to show the bare minimum of what a TrueSkill implementation should have. /// public class TwoPlayerTrueSkillCalculator : SkillCalculator { public TwoPlayerTrueSkillCalculator() : base(SupportedOptions.None, Range.Exactly(2), Range.Exactly(1)) { } /// public override IDictionary CalculateNewRatings(GameInfo gameInfo, IEnumerable > teams, params int[] teamRanks) { // Basic argument checking Guard.ArgumentNotNull(gameInfo, "gameInfo"); ValidateTeamCountAndPlayersCountPerTeam(teams); // Make sure things are in order RankSorter.Sort(ref teams, ref teamRanks); // Get the teams as a list to make it easier to index List> teamList = teams.ToList(); // Since we verified that each team has one player, we know the player is the first one IDictionary winningTeam = teamList[0]; TPlayer winner = winningTeam.Keys.First(); Rating winnerPreviousRating = winningTeam[winner]; IDictionary losingTeam = teamList[1]; TPlayer loser = losingTeam.Keys.First(); Rating loserPreviousRating = losingTeam[loser]; bool wasDraw = (teamRanks[0] == teamRanks[1]); var results = new Dictionary(); results[winner] = CalculateNewRating(gameInfo, winnerPreviousRating, loserPreviousRating, wasDraw ? PairwiseComparison.Draw : PairwiseComparison.Win); results[loser] = CalculateNewRating(gameInfo, loserPreviousRating, winnerPreviousRating, wasDraw ? PairwiseComparison.Draw : PairwiseComparison.Lose); // And we're done! return results; } private static Rating CalculateNewRating(GameInfo gameInfo, Rating selfRating, Rating opponentRating, PairwiseComparison comparison) { double drawMargin = DrawMargin.GetDrawMarginFromDrawProbability(gameInfo.DrawProbability, gameInfo.Beta); double c = Math.Sqrt( Square(selfRating.StandardDeviation) + Square(opponentRating.StandardDeviation) + 2*Square(gameInfo.Beta)); double winningMean = selfRating.Mean; double losingMean = opponentRating.Mean; switch (comparison) { case PairwiseComparison.Win: case PairwiseComparison.Draw: // NOP break; case PairwiseComparison.Lose: winningMean = opponentRating.Mean; losingMean = selfRating.Mean; break; } double meanDelta = winningMean - losingMean; double v; double w; double rankMultiplier; if (comparison != PairwiseComparison.Draw) { // non-draw case v = TruncatedGaussianCorrectionFunctions.VExceedsMargin(meanDelta, drawMargin, c); w = TruncatedGaussianCorrectionFunctions.WExceedsMargin(meanDelta, drawMargin, c); rankMultiplier = (int) comparison; } else { v = TruncatedGaussianCorrectionFunctions.VWithinMargin(meanDelta, drawMargin, c); w = TruncatedGaussianCorrectionFunctions.WWithinMargin(meanDelta, drawMargin, c); rankMultiplier = 1; } double meanMultiplier = (Square(selfRating.StandardDeviation) + Square(gameInfo.DynamicsFactor))/c; double varianceWithDynamics = Square(selfRating.StandardDeviation) + Square(gameInfo.DynamicsFactor); double stdDevMultiplier = varianceWithDynamics/Square(c); double newMean = selfRating.Mean + (rankMultiplier*meanMultiplier*v); double newStdDev = Math.Sqrt(varianceWithDynamics*(1 - w*stdDevMultiplier)); return new Rating(newMean, newStdDev); } /// public override double CalculateMatchQuality(GameInfo gameInfo, IEnumerable> teams) { Guard.ArgumentNotNull(gameInfo, "gameInfo"); ValidateTeamCountAndPlayersCountPerTeam(teams); Rating player1Rating = teams.First().Values.First(); Rating player2Rating = teams.Last().Values.First(); // We just use equation 4.1 found on page 8 of the TrueSkill 2006 paper: double betaSquared = Square(gameInfo.Beta); double player1SigmaSquared = Square(player1Rating.StandardDeviation); double player2SigmaSquared = Square(player2Rating.StandardDeviation); // This is the square root part of the equation: double sqrtPart = Math.Sqrt( (2*betaSquared) / (2*betaSquared + player1SigmaSquared + player2SigmaSquared)); // This is the exponent part of the equation: double expPart = Math.Exp( (-1*Square(player1Rating.Mean - player2Rating.Mean)) / (2*(2*betaSquared + player1SigmaSquared + player2SigmaSquared))); return sqrtPart*expPart; } } }