Skip to content

Instantly share code, notes, and snippets.

@klenwell
Last active January 17, 2022 13:18
Show Gist options
  • Save klenwell/3a15eca6b83ce575d0ca to your computer and use it in GitHub Desktop.
Save klenwell/3a15eca6b83ce575d0ca to your computer and use it in GitHub Desktop.
Trueskill Prediction Simulation
import random
from math import sqrt
from trueskill import TrueSkill, Rating, quality_1vs1, rate_1vs1, BETA
from trueskill.backends import cdf
# From https://github.com/sublee/trueskill/issues/1#issuecomment-10491635
def win_probability(team_rating, opponent_rating):
delta_mu = team_rating.mu - opponent_rating.mu
denom = sqrt(2 * (BETA * BETA) + pow(team_rating.sigma, 2) + pow(opponent_rating.sigma, 2))
win_prob = cdf(delta_mu / denom)
return win_prob
NUM_MATCHES = 1000
total_predictions = 0
correct_predictions = 0
class Player:
def __init__(self, name, talent):
self.name = name
self.talent = talent
self.rating = Rating()
self.wins = 0
self.losses = 0
self.draws = 0
@property
def record(self):
return '%d-%d-%d' % (self.wins, self.losses, self.draws)
@property
def mu_sigma(self):
return '%d~%d' % (round(self.rating.mu * 10), round(self.rating.sigma * 10))
def __repr__(self):
return 'Player(%s) %s [%s]' % (self.name, self.record, self.mu_sigma)
# Create players
players = [
Player('Alice', 50),
Player('Bob', 60),
Player('Carol', 66),
Player('Dave', 67),
Player('Emily', 67),
Player('Felix', 75)
]
for n in range(NUM_MATCHES):
away, home = random.sample(players, 2)
match_quality = quality_1vs1(away.rating, home.rating)
away_win_prob = win_probability(away.rating, home.rating)
if away_win_prob < .33:
prediction = 'LOSS'
elif away_win_prob > .66:
prediction = 'WIN'
else:
prediction = 'TOSS-UP'
print 'Matchup: %s (%s) at %s (%s)' % (
away.name, away.record, home.name, home.record,
)
print 'Rating Before: %s / %s' % (away.mu_sigma, home.mu_sigma)
print 'Match Quality: %.2f' % (match_quality)
print '%s odds winning: %.1f%% [predict: %s]' % (
away.name,
away_win_prob * 100,
prediction
)
away_score = random.randint(0, away.talent)
home_score = random.randint(0, home.talent)
is_draw = away_score == home_score
if is_draw:
away.draws += 1
home.draws += 1
away.rating, home.rating = rate_1vs1(away.rating, home.rating, drawn=True)
print '%s and %s tied at %d' % (away.name, home.name, away_score)
else:
winner = (away_score > home_score) and away or home
loser = (away_score < home_score) and away or home
winner.rating, loser.rating = rate_1vs1(winner.rating, loser.rating)
winner.wins += 1
loser.losses += 1
print '%s beats %s (%d - %d)' % (winner.name, loser.name,
max(away_score, home_score), min(away_score, home_score))
# Update prediction
prediction_result = None
if prediction == 'WIN':
total_predictions += 1
if winner == away:
correct_predictions += 1
prediction_result = 'SUCCESS'
else:
prediction_result = 'FAIL'
elif prediction == 'LOSS':
total_predictions += 1
if loser == away:
correct_predictions += 1
prediction_result = 'SUCCESS'
else:
prediction_result = 'FAIL'
print 'Prediction: %s --> %s' % (prediction, prediction_result)
print 'Rating After: %s / %s' % (away.mu_sigma, home.mu_sigma)
print "\n"
for player in players:
print player
print "Predicted: %d of %d (%.1f%%)" % (
correct_predictions,
total_predictions,
float(correct_predictions) / total_predictions * 100
)
@klenwell
Copy link
Author

Sample output:

Matchup: Felix (83-73-2) at Emily (89-73-3)
Rating Before: 255~8 / 261~8
Match Quality: 0.98
Felix odds winning: 29.1% [predict: LOSS]
Felix beats Emily (44 - 5)
Prediction: LOSS --> FAIL
Rating After: 256~8 / 260~8

...

Player(Alice) 52-117-4 [227~8]
Player(Bob) 87-79-3 [253~8]
Player(Carol) 96-72-1 [249~8]
Player(Dave) 83-76-5 [256~8]
Player(Emily) 89-74-3 [260~8]
Player(Felix) 84-73-2 [256~8]
Predicted: 205 of 362 (56.6%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment