2025-11-10 07:25:23 +00:00
# This script is an exploration into processing PUB/PUG match results into player rankings.
# Once I hack together something that seems useful, I may try to tidy this up.
# So far, "single team point whore" glicko ratings are the most useful result. This compares players only to their teammates, so when one team dominates another, the ratings are still fair. However, this still suffers from the problem that certain roles tend to score more than others: for example, a top capper is probably going to have a greater score than a top farmer, even though the farmer is critical to the team's success.
2025-11-10 07:26:02 +00:00
# The next thing I would like to compute is a historical conditional probability of winning a match given that a certain pair or trio of players is on the team.
2025-11-10 07:25:23 +00:00
2025-11-09 21:14:45 +00:00
import yaml
from operator import itemgetter
import glicko2
2025-11-11 03:29:18 +00:00
from more_itertools import pairwise , distinct_combinations
2025-11-09 21:14:45 +00:00
2025-11-11 04:50:04 +00:00
print ( )
2025-11-09 21:14:45 +00:00
# load file
with open ( ' pubresults.yaml ' , ' r ' ) as file :
file_contents = yaml . full_load ( file )
# print(yaml.dump(file_contents))
# print(len([key for key in file_contents]))
# print(len(file_contents))
# print(file_contents[0]['mission']) # zero'th match, missionname
# print(file_contents[0]['date'])
# print(file_contents[0]['results'])
# create player dictionary
# playerdata = dict()
# Point Whore Glickos
pwglickos = dict ( )
# Single Team Whore Glickos
stpwglickos = dict ( )
# Team Player Glickos
tpglickos = dict ( )
players_to_roles = {
' stormcrow ' : [ ' ld ' , ' lof ' ] ,
' jacob ' : [ ' ld ' , ' lo ' , ' cap ' ] ,
' bizzy ' : [ ' ld ' , ' lo ' ] ,
' slush ' : [ ' cap ' ] ,
' astralis ' : [ ' cap ' , ' flex ' ] ,
' domestic ' : [ ' ld ' , ' chase ' ] ,
' danno ' : [ ' ho ' , ' ho ' ] ,
' hybrid ' : [ ' lof ' , ' ho ' ] ,
' vaxity ' : [ ' ho ' , ' shrike ' ] ,
' mistcane ' : [ ' ld ' , ' cap ' ] ,
' nevares ' : [ ' cap ' ] ,
' haggis ' : [ ' ho ' ] ,
' devil ' : [ ' cap ' , ' ho ' ] ,
' efx ' : [ ' ld ' , ' lof ' ] ,
' hexy ' : [ ' ld ' , ' shrike ' ] ,
' halo2 ' : [ ' ho ' ] ,
' blake ' : [ ' lof ' ] ,
' future ' : [ ' flex ' ] ,
' thaen ' : [ ' offense ' ] ,
' strazz ' : [ ' hof ' ] ,
' history ' : [ ' cap ' , ' shrike ' , ' ho ' ] ,
' sliderzero ' : [ ' shrike ' , ' flex ' ] ,
' jerry ' : [ ' ld ' ] ,
' wingedwarrior ' : [ ' ld ' , ' snipe ' ] ,
' sylock ' : [ ' ho ' ] ,
' darrell ' : [ ' ld ' ] ,
' pedro ' : [ ' ld ' ] ,
' coorslightman ' : [ ' ld ' ] ,
' hautsoss ' : [ ' flex ' ] ,
' sajent ' : [ ' ld ' , ' ho ' ] ,
' turtle ' : [ ' ld ' ] ,
' irvin ' : [ ' cap ' ] ,
' redeye ' : [ ' lo ' , ' ho ' , ' flex ' ] ,
' mlgru ' : [ ' shrike ' , ' ho ' , ' cap ' ] ,
' actionswanson ' : [ ' flex ' ] ,
' bendover ' : [ ' ho ' ] ,
' warchilde ' : [ ' ho ' ] ,
' johnwayne ' : [ ' flex ' ] ,
' lsecannon ' : [ ' farm ' ] ,
' hp ' : [ ' ld ' , ' lof ' ] ,
' sake ' : [ ' ld ' ] ,
' anthem ' : [ ' ho ' ] ,
' taco ' : [ ' ho ' ] ,
' exogen ' : [ ' cap ' ] ,
' mp40 ' : [ ' hd ' ] ,
' gunther ' : [ ' ho ' ] ,
' ipkiss ' : [ ' snipe ' ] ,
' alterego ' : [ ' hd ' ] ,
' homer ' : [ ' ho ' ] ,
' spartanonyx ' : [ ' ld ' ] ,
' bish ' : [ ' ho ' ] ,
' flyersfan ' : [ ' ld ' ] ,
' geekofwires ' : [ ' ho ' ] ,
' aromatomato ' : [ ' ho ' ] ,
' heat ' : [ ' ho ' , ' hd ' , ' farm ' ] ,
' daddyroids ' : [ ' ld ' ] ,
' pupecki ' : [ ' ld ' ] ,
' yuanz ' : [ ' farm ' , ' hd ' , ' ho ' ] ,
' m80 ' : [ ' lof ' ] ,
' andycap ' : [ ' hof ' ] ,
' tetchy ' : [ ' cap ' , ' shrike ' ] ,
' systeme ' : [ ' hd ' , ' farm ' , ' ho ' ] ,
' friendo ' : [ ' hof ' , ' farm ' , ' ld ' , ' ho ' ] ,
' coastal ' : [ ' shrike ' , ' ld ' ] ,
' caution ' : [ ' ho ' , ' cap ' ] ,
' jx ' : [ ' ld ' ] ,
' nightwear ' : [ ' flex ' ] ,
' piata ' : [ ' ho ' ] ,
' foxox ' : [ ' snipe ' , ' farm ' ] ,
' elliebackwards ' : [ ' ld ' ] ,
' nutty ' : [ ' ld ' ] ,
' sweetcheeks ' : [ ' farm ' ] ,
' carpenter ' : [ ' hd ' , ' ld ' ] ,
' eeor ' : [ ' ld ' ] ,
' cooter ' : [ ' cap ' ] ,
' flakpyro ' : [ ' flex ' , ' d ' ] ,
' doug ' : [ ' ld ' , ' ho ' , ' snipe ' ] ,
' raynian ' : [ ' ho ' , ' mo ' ] ,
' legelos ' : [ ' ld ' ] ,
' 7thbishop ' : [ ' cap ' , ' hd ' ] ,
' dirkdiggler ' : [ ' ho ' ] ,
' lazer ' : [ ' ld ' ] ,
' iroc ' : [ ' ld ' ] ,
' ember ' : [ ' ld ' ] ,
' 2short ' : [ ' hd ' , ' ho ' , ' cap ' ] ,
' earth ' : [ ' tank ' , ' hd ' , ' hof ' ] ,
' lolcaps ' : [ ' cap ' ] ,
' aftermath ' : [ ' ld ' ] ,
' fnatic ' : [ ' ld ' ] ,
2025-11-15 18:17:51 +00:00
' cooljuke ' : [ ' snipe ' ] ,
' sterio ' : [ ' ld ' ] ,
' jazz ' : [ ' ho ' , ' ld ' , ' cap ' ] ,
2025-11-09 21:14:45 +00:00
}
first_roles_to_players = dict ( )
any_roles_to_players = dict ( )
for player , roles in players_to_roles . items ( ) :
if roles [ 0 ] is None :
# print('')
continue
# first_role_players = first_roles_to_players[roles[0]]
if not roles [ 0 ] in first_roles_to_players :
first_roles_to_players [ roles [ 0 ] ] = list ( )
# print('adding', player,'to role',roles[0])
first_roles_to_players [ roles [ 0 ] ] . append ( player )
for role in roles :
if not role in any_roles_to_players :
any_roles_to_players [ role ] = list ( )
any_roles_to_players [ role ] . append ( player )
2025-11-10 07:25:23 +00:00
2025-11-15 18:17:51 +00:00
# D doesn't include farm and O doesn't include cap
role_relationships = { ' defense ' : [ ' tank ' , ' hd ' , ' lof ' , ' hof ' , ' ld ' , ' flex ' , ' shrike ' , ' snipe ' ] , ' offense ' : [ ' shrike ' , ' ho ' , ' snipe ' , ' flex ' , ' lo ' , ' snipe ' ] }
any_roles_to_players [ ' defense ' ] = list ( )
print ( " any_roles_to_players[ ' defense ' ]: " , any_roles_to_players [ ' defense ' ] )
print ( " any_roles_to_players[ ' offense ' ]: " , any_roles_to_players [ ' offense ' ] )
for ( role , related_roles ) in role_relationships . items ( ) :
if role not in any_roles_to_players :
any_roles_to_players [ role ] = list ( )
for related_role in related_roles :
any_roles_to_players [ role ] . append ( any_roles_to_players [ related_role ] )
print ( " expanded any_roles_to_players[ ' defense ' ]: " , any_roles_to_players [ ' defense ' ] )
print ( " any_roles_to_players[ ' offense ' ]: " , any_roles_to_players [ ' offense ' ] )
2025-11-10 07:25:23 +00:00
player_to_win_count = dict ( )
player_to_match_count = dict ( )
2025-11-11 03:29:18 +00:00
duo_to_win_count = dict ( )
duo_to_match_count = dict ( )
2025-11-11 03:59:55 +00:00
trio_to_win_count = dict ( )
trio_to_match_count = dict ( )
2025-11-10 07:25:23 +00:00
2025-11-09 21:14:45 +00:00
# loop over all matches
for match in file_contents :
2025-11-11 04:50:04 +00:00
# print()
# print(match['date'], match['mission'])
2025-11-09 21:14:45 +00:00
winning_team_score = 0
winning_team_name = None
results = match [ ' results ' ]
# match
merged_match_player_results = list ( )
for team in results :
2025-11-11 04:50:04 +00:00
# print('team:', team)
2025-11-10 07:25:23 +00:00
if results [ team ] [ ' score ' ] > winning_team_score :
winning_team_score = results [ team ] [ ' score ' ]
winning_team_name = team
2025-11-09 21:14:45 +00:00
# print('input unsorted')
# for player in results[team]['players']:
# print('player:', player)
# results[team]['players'].sort(key=itemgetter(2), reverse=True)
# print('input sorted')
# print('appending to list this thing',results[team]['players'],type(results[team]['players']), type(results[team]['players'][0]))
team_player_results = list ( )
# parse the string as a tuple
for player in results [ team ] [ ' players ' ] :
player_split = player . split ( " , " )
# print('player_split:',player_split)
player_tuple = ( player_split [ 0 ] , int ( player_split [ 1 ] ) )
# print('xx:"',player,'"')
# print('xx:',player_tuple)
# todo consider removing the bottom 20% or something, to filter out people who had connection problems
merged_match_player_results . append ( player_tuple )
team_player_results . append ( player_tuple )
# initialize glicko objects for each player, if not already initialized
if player_tuple [ 0 ] not in pwglickos :
pwglickos [ player_tuple [ 0 ] ] = glicko2 . Player ( )
if player_tuple [ 0 ] not in stpwglickos :
stpwglickos [ player_tuple [ 0 ] ] = glicko2 . Player ( )
if player_tuple [ 0 ] not in tpglickos :
tpglickos [ player_tuple [ 0 ] ] = glicko2 . Player ( )
# per team point whore glicko updates
team_player_results . sort ( key = itemgetter ( 1 ) , reverse = True )
for better_player , worse_player in pairwise ( team_player_results ) :
# score ties
lose = 0
win = 1
if better_player [ 1 ] == worse_player [ 1 ] :
lose = 0.5
win = 0.5
# print('bp:', better_player)
# print('wp:', worse_player)
worse_player_glicko = stpwglickos [ worse_player [ 0 ] ]
stpwglickos [ better_player [ 0 ] ] . update_player ( [ worse_player_glicko . rating ] , [ worse_player_glicko . rd ] , [ win ] )
better_player_glicko = stpwglickos [ better_player [ 0 ] ]
stpwglickos [ worse_player [ 0 ] ] . update_player ( [ better_player_glicko . rating ] , [ better_player_glicko . rd ] , [ lose ] )
2025-11-11 03:29:18 +00:00
# WIN RATE STATS GATHERING. SINGLES, DUOS, TRIOS, ETC.
2025-11-10 07:25:23 +00:00
for team in results :
2025-11-11 03:29:18 +00:00
# SINGLES
2025-11-10 07:25:23 +00:00
for player in results [ team ] [ ' players ' ] :
player_split = player . split ( " , " )
playername = player_split [ 0 ]
# player_tuple = (player_split[0], int(player_split[1]))
# 0 is name, 1 is score
if not playername in player_to_match_count :
player_to_match_count [ playername ] = 0
if not playername in player_to_win_count :
player_to_win_count [ playername ] = 0
player_to_match_count [ playername ] + = 1
if team == winning_team_name :
player_to_win_count [ playername ] + = 1
2025-11-11 03:29:18 +00:00
# DUOS
for duo in distinct_combinations ( results [ team ] [ ' players ' ] , 2 ) :
duo0split = duo [ 0 ] . split ( " , " )
duo1split = duo [ 1 ] . split ( " , " )
player_name_duo = ( duo0split [ 0 ] , duo1split [ 0 ] )
# print('Duo ',player_name_duo,' appeared in match ',match)
if not player_name_duo in duo_to_win_count :
duo_to_win_count [ player_name_duo ] = 0
if not player_name_duo in duo_to_match_count :
duo_to_match_count [ player_name_duo ] = 0
duo_to_match_count [ player_name_duo ] + = 1
if team == winning_team_name :
duo_to_win_count [ player_name_duo ] + = 1
2025-11-09 21:14:45 +00:00
2025-11-11 03:59:55 +00:00
# TRIOS
for trio in distinct_combinations ( results [ team ] [ ' players ' ] , 3 ) :
trio0split = trio [ 0 ] . split ( " , " )
trio1split = trio [ 1 ] . split ( " , " )
trio2split = trio [ 2 ] . split ( " , " )
player_name_trio = ( trio0split [ 0 ] , trio1split [ 0 ] , trio2split [ 0 ] )
# print('trio ',player_name_trio,' appeared in match ',match)
if not player_name_trio in trio_to_win_count :
trio_to_win_count [ player_name_trio ] = 0
if not player_name_trio in trio_to_match_count :
trio_to_match_count [ player_name_trio ] = 0
trio_to_match_count [ player_name_trio ] + = 1
if team == winning_team_name :
trio_to_win_count [ player_name_trio ] + = 1
2025-11-09 21:14:45 +00:00
# for player in merged_match_player_results:
# print('inplayer:', player)
# Sort all of the players in the match by their scores
merged_match_player_results . sort ( key = itemgetter ( 1 ) , reverse = True )
for better_player , worse_player in pairwise ( merged_match_player_results ) :
# score ties
lose = 0
win = 1
if better_player [ 1 ] == worse_player [ 1 ] :
lose = 0.5
win = 0.5
# print('bp:', better_player)
# print('wp:', worse_player)
worse_player_glicko = pwglickos [ worse_player [ 0 ] ]
pwglickos [ better_player [ 0 ] ] . update_player ( [ worse_player_glicko . rating ] , [ worse_player_glicko . rd ] , [ win ] )
better_player_glicko = pwglickos [ better_player [ 0 ] ]
pwglickos [ worse_player [ 0 ] ] . update_player ( [ better_player_glicko . rating ] , [ better_player_glicko . rd ] , [ lose ] )
# for player in merged_match_player_results:
# print(player[0], pwglickos[player[0]].rating, pwglickos[player[0]].rd)
# Count a team win as an individual win for each winning team player against all losing team players (and vice versa for losses)
# todo: maybe it should only count as a personal win if your personal score is higher than the other team's player
assert ( len ( results ) == 2 )
winning_team_name = 0
losing_team_name = 0
lose = 0
win = 1
team_names = list ( results . keys ( ) )
if results [ team_names [ 0 ] ] [ ' score ' ] > results [ team_names [ 1 ] ] [ ' score ' ] :
winning_team_name = team_names [ 0 ]
losing_team_name = team_names [ 1 ]
elif results [ team_names [ 0 ] ] [ ' score ' ] < results [ team_names [ 1 ] ] [ ' score ' ] :
winning_team_name = team_names [ 1 ]
losing_team_name = team_names [ 0 ]
else :
lose = 0.5
win = 0.5
for losing_team_player in results [ losing_team_name ] [ ' players ' ] :
losing_player_split = losing_team_player . split ( " , " )
losing_player_tuple = ( losing_player_split [ 0 ] , int ( losing_player_split [ 1 ] ) )
# print('losing_team_player:',losing_player_tuple[0])
for winning_team_player in results [ winning_team_name ] [ ' players ' ] :
winning_player_split = winning_team_player . split ( " , " )
winning_player_tuple = ( winning_player_split [ 0 ] , int ( winning_player_split [ 1 ] ) )
# print('winning_team_player:',winning_player_tuple[0])
# if winning_player_split[1] > losing_player_split[1]:
tpglickos [ losing_player_tuple [ 0 ] ] . update_player ( [ tpglickos [ winning_player_tuple [ 0 ] ] . rating ] , [ tpglickos [ winning_player_tuple [ 0 ] ] . rd ] , [ lose ] )
tpglickos [ winning_player_tuple [ 0 ] ] . update_player ( [ tpglickos [ losing_player_tuple [ 0 ] ] . rating ] , [ tpglickos [ losing_player_tuple [ 0 ] ] . rd ] , [ win ] )
2025-11-11 04:50:04 +00:00
# # Sort by glicko ratings and print them out
# pwglickolist = list(pwglickos.items())
# pwglickolist.sort(key=lambda rating: rating[1].rating, reverse=True)
# print('Point Whore Ratings, sorted:\n', [(x[0], str(round(x[1].rating))) for x in pwglickolist])
# # Sort by glicko ratings and print them out
# stpwglickolist = list(stpwglickos.items())
# stpwglickolist.sort(key=lambda rating: rating[1].rating, reverse=True)
# # print('Single Team Point Whore Ratings, sorted:\n', [(x[0], str(round(x[1].rating))) for x in stpwglickolist])
# print('\nSingle Team Point Whore Ratings',"\n".join([ str((x[0], str(round(x[1].rating)), str(round(x[1].rd)))) for x in stpwglickolist]))
# # Sort by glicko ratings and print them out
# tpglickolist = list(tpglickos.items())
# tpglickolist.sort(key=lambda rating: rating[1].rating, reverse=True)
# print('\nTeam Player Ratings, sorted:')
# print("\n".join([ str((x[0], str(round(x[1].rating)), str(round(x[1].rd)))) for x in tpglickolist]))
# print('\nPer role single team point whore ratings:\n')
# for role, players in first_roles_to_players.items():
# # print('unsorted:',role,players)
# players.sort(key=lambda p: stpwglickos[p].rating if p in stpwglickos else 1400, reverse=True)
# print('sorted:',role,players)
2025-11-10 07:25:23 +00:00
2025-11-15 18:17:51 +00:00
# Warn missing first roles
# for player in player_to_match_count.keys():
# if player not in players_to_roles:
# print("Warning: player '{}' has no first role defined".format(player))
2025-11-10 07:25:23 +00:00
# Print conditional probabilities
player_to_win_rate = dict ( )
for matchkvp in player_to_match_count . items ( ) :
2025-11-12 04:13:40 +00:00
if matchkvp [ 1 ] < 38 :
2025-11-10 07:25:23 +00:00
continue
player_to_win_rate [ matchkvp [ 0 ] ] = player_to_win_count [ matchkvp [ 0 ] ] / matchkvp [ 1 ]
player_to_win_rate_sorted = list ( player_to_win_rate . items ( ) )
player_to_win_rate_sorted . sort ( key = lambda p : p [ 1 ] , reverse = True )
2025-11-11 04:50:04 +00:00
print ( ' Best (and worst) player win rates: \n ' , ' \n ' . join ( [ str ( x ) for x in player_to_win_rate_sorted ] ) )
2025-11-12 04:13:40 +00:00
player_to_win_rate = dict ( )
for matchkvp in player_to_match_count . items ( ) :
if matchkvp [ 1 ] > 38 or matchkvp [ 1 ] < 19 :
continue
player_to_win_rate [ matchkvp [ 0 ] ] = player_to_win_count [ matchkvp [ 0 ] ] / matchkvp [ 1 ]
player_to_win_rate_sorted = list ( player_to_win_rate . items ( ) )
player_to_win_rate_sorted . sort ( key = lambda p : p [ 1 ] , reverse = True )
print ( ' Lower confidence Best (and worst) player win rates: \n ' , ' \n ' . join ( [ str ( x ) for x in player_to_win_rate_sorted ] ) )
2025-11-15 18:17:51 +00:00
print ( ' ' )
# As above but per role
for role , players in first_roles_to_players . items ( ) :
# print([str((p,player_to_match_count[p])) for p in players if p in player_to_match_count])
player_match_counts = [ player_to_match_count [ p ] for p in players if p in player_to_match_count ]
player_match_counts . sort ( )
top_third_match_count = player_match_counts [ len ( player_match_counts ) * 2 / / 3 ]
middle_third_match_count = player_match_counts [ len ( player_match_counts ) / / 3 ]
# print('Role:',role,'player match counts:',player_match_counts,'top third cutoff:',top_third_match_count,'middle third cutoff:',middle_third_match_count)
significant_players = [ ( p , player_to_win_count [ p ] / player_to_match_count [ p ] ) for p in players if p in player_to_match_count and player_to_match_count [ p ] > = top_third_match_count ]
significant_players . sort ( key = lambda p : p [ 1 ] , reverse = True )
print ( ' Higher confidence ' , role , [ p [ 0 ] + ' ' + format ( p [ 1 ] , ' .2f ' ) for p in significant_players ] )
significant_players = [ ( p , player_to_win_count [ p ] / player_to_match_count [ p ] ) for p in players if p in player_to_match_count and player_to_match_count [ p ] > = middle_third_match_count and player_to_match_count [ p ] < top_third_match_count ]
significant_players . sort ( key = lambda p : p [ 1 ] , reverse = True )
print ( ' Middle confidence ' , role , [ p [ 0 ] + ' ' + format ( p [ 1 ] , ' .2f ' ) for p in significant_players ] )
significant_players = [ ( p , player_to_win_count [ p ] / player_to_match_count [ p ] ) for p in players if p in player_to_match_count and player_to_match_count [ p ] < middle_third_match_count and player_to_match_count [ p ] > 1 ]
significant_players . sort ( key = lambda p : p [ 1 ] , reverse = True )
print ( ' Lower confidence ' , role , [ p [ 0 ] + ' ' + format ( p [ 1 ] , ' .2f ' ) for p in significant_players ] )
print ( )
2025-11-11 04:50:04 +00:00
print ( )
2025-11-11 03:29:18 +00:00
duo_to_win_rate = dict ( )
for matchkvp in duo_to_match_count . items ( ) :
2025-11-12 04:13:40 +00:00
if matchkvp [ 1 ] < 18 :
2025-11-11 03:29:18 +00:00
continue
duo_to_win_rate [ matchkvp [ 0 ] ] = duo_to_win_count [ matchkvp [ 0 ] ] / matchkvp [ 1 ]
duo_to_win_rate_sorted = list ( duo_to_win_rate . items ( ) )
duo_to_win_rate_sorted . sort ( key = lambda p : p [ 1 ] , reverse = True )
2025-11-15 18:17:51 +00:00
print ( ' Duo win rates (n >= 18): \n ' , ' \n ' . join ( [ str ( x ) for x in duo_to_win_rate_sorted ] ) )
2025-11-11 03:59:55 +00:00
2025-11-11 04:50:04 +00:00
print ( )
2025-11-11 03:59:55 +00:00
trio_to_win_rate = dict ( )
for matchkvp in trio_to_match_count . items ( ) :
2025-11-12 04:13:40 +00:00
if matchkvp [ 1 ] < 10 :
2025-11-11 03:59:55 +00:00
continue
trio_to_win_rate [ matchkvp [ 0 ] ] = trio_to_win_count [ matchkvp [ 0 ] ] / matchkvp [ 1 ]
trio_to_win_rate_sorted = list ( trio_to_win_rate . items ( ) )
trio_to_win_rate_sorted . sort ( key = lambda p : p [ 1 ] , reverse = True )
2025-11-15 18:17:51 +00:00
print ( ' Trio win rates (n >= 10): \n ' , ' \n ' . join ( [ str ( x ) for x in trio_to_win_rate_sorted ] ) )
2025-11-11 04:50:04 +00:00
print ( )