Last active
January 17, 2022 13:18
-
-
Save klenwell/3a15eca6b83ce575d0ca to your computer and use it in GitHub Desktop.
Trueskill Prediction Simulation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample output: