rank.py (6389B)
1 #!/usr/bin/env python3 2 3 import os 4 import sys 5 import time 6 from math import log, erf, sqrt 7 from random import shuffle 8 import pickle 9 from trueskill import Rating, quality, rate 10 11 os.environ['DJANGO_SETTINGS_MODULE'] = 'prologin.concours.settings' 12 sys.path.insert(0, '/root/sadm/python-lib') 13 from django.contrib.auth.models import User 14 from prologin.concours.stechec.models import * 15 16 # Number of players per game 17 party_size = 4 18 19 # Quality of sampling new members for a party 20 sample_size = 1 21 22 # The conservative score (aka "at least this good") is : mean - conservativeness * stddev 23 conservativeness = 3 24 25 # Number of matches running at the same time 26 max_concurrent_matches = 250 27 28 # Maximum number of simultaneous matches per player 29 concurrent_match_per_player = 50 30 31 # Maximum difference in number of concurrent matches between the player who is playing the most and the one who is playing the least 32 min_ppp_margin = 2 33 34 # Frequency (in number of launched games) at which game results are gathered 35 update_score_frequency = 100 36 37 tournament_name='Tournoi final' 38 39 filepath=sys.argv[1] 40 with open(filepath, 'rb') as f: 41 ratings = pickle.load(f) 42 43 current_matches = [] 44 nb_match_per_player = {} 45 champions = {} 46 users = {} 47 for user in ratings.keys(): 48 # The last non deleted champion of a user 49 champion = Champion.objects.filter(author__username=user).order_by('-ts').exclude(deleted=True)[0].pk 50 nb_match_per_player[user] = 0 51 champions[user] = champion 52 users[champion] = user 53 54 def launch_match(cs): 55 match = Match() 56 match.author = User.objects.get(username='epsilon012') 57 match.tournament = Tournament.objects.get(name=tournament_name) 58 match.save() 59 60 for c in cs: 61 mc = MatchPlayer() 62 mc.champion = Champion.objects.get(pk=c) 63 mc.match = match 64 mc.save() 65 66 match.status = 'new' 67 match.save() 68 69 return match.pk 70 71 def is_match_complete(match): 72 m = Match.objects.filter(pk=match) 73 return m.status == 'done' 74 75 def get_match_results(match): 76 players = MatchPlayer.objects.filter(match__pk=match) 77 scores = dict((p.champion.pk, p.score) for p in players) 78 return scores 79 80 def do_match(party): 81 for member in party: 82 nb_match_per_player[member] += 1 83 party_champ = [ champions[member] for member in party ] 84 pk=launch_match(party_champ) 85 current_matches.append(pk) 86 return pk 87 88 def conservative_estimate(key): 89 return ratings[key].mu - conservativeness * ratings[key].sigma 90 91 def can_enter_match(user): 92 return nb_match_per_player[user] < concurrent_match_per_player 93 94 def match_making(): 95 # Number of game of the player who is playing the least 96 min_ppp = min(nb_match_per_player.values()) 97 pick_from_me = [ user for user in ratings.keys() if nb_match_per_player[user] <= min_ppp + min_ppp_margin] 98 99 ready = list(filter(can_enter_match, ratings.keys())) 100 101 # The player with the most uncertain rating, he'll be in the party no matter what 102 fuzziest = max(pick_from_me, key=lambda x: ratings[x].sigma) 103 ready.remove(fuzziest) 104 105 # Fill sample_me with available ("ready") players, the remaining (party_size-1) players to be picked will be consecutive players in this array 106 # The //x*x is done so that sample_me is extend with arrays of size multiple of (party_size-1), this mean we can't sample a party from two different shuffled ready arrays, since doing so might result in a match with the same player appearing twice 107 sample_me = [] 108 for _ in range(sample_size): 109 shuffle(ready) 110 sample_me += ready[:len(ready)//(party_size-1)*(party_size-1)] 111 112 # We pick the remaining (party_size-1) players from sample_me to maximize the match quality 113 max_qual = -1 114 max_party = None 115 for i in range(len(sample_me)//(party_size-1)): 116 party_minus_1 = sample_me[(party_size-1)*i:(party_size-1)*(i+1)] 117 party = party_minus_1 + [ fuzziest ] 118 gr = [[ratings[member]] for member in party] 119 qual = quality(gr) 120 121 if qual > max_qual: 122 max_qual = qual 123 max_party = party 124 125 return max_party 126 127 # update ratings given the result of a match in a dictionary {user: score} 128 def update(scores): 129 for key in scores: 130 nb_match_per_player[key] -= 1 131 scores[key] = -scores[key] 132 133 if not any(scores.values()): 134 return 135 136 new_rating = rate([(ratings[key],) for key in scores], scores.values()) 137 for key, value in zip(scores.keys(), new_rating): 138 ratings[key] = value[0] 139 140 with open(filepath, 'wb') as f: 141 pickle.dump(ratings, f) 142 143 # Check if any matches finished and update ratings accordingly 144 def update_scores(): 145 global current_matches 146 remove_me = [] 147 for match_pk in current_matches: 148 match = Match.objects.filter(pk=match_pk)[0] 149 if match.status == 'done': 150 print("Match {0:<5} done".format(match_pk)) 151 remove_me.append(match_pk) 152 scores_champ = get_match_results(match_pk) 153 update(dict((users[key], scores_champ[key]) for key in scores_champ)) 154 current_matches = [ item for item in current_matches if item not in remove_me ] 155 156 if(len(sys.argv)>2): 157 N = int(sys.argv[2]) 158 else: 159 n = len(ratings) 160 N = int(round(n*log(n, 2))) 161 162 for epoch in range(N): 163 party = match_making() 164 if party is None: 165 print("Epoch {0:<5} No match".format(epoch)) 166 time.sleep(3) 167 else: 168 id_match = do_match(party) 169 print("Epoch {0:<5} id {1:<5} party {2}".format(epoch, id_match, party)) 170 171 while len(current_matches) >= max_concurrent_matches: 172 update_scores() 173 time.sleep(3) 174 175 if (epoch+1) % update_score_frequency == 0: 176 update_scores() 177 178 while len(current_matches) > 0: 179 time.sleep(5) 180 update_scores() 181 182 leaderboard = list(ratings.keys()) 183 #leaderboard.sort(key=conservative_estimate, reverse=True) 184 leaderboard.sort(key=lambda x: ratings[x].mu, reverse=True) 185 for (position, champion) in enumerate(leaderboard): 186 if position!=len(leaderboard)-1: 187 std = (ratings[leaderboard[position]].mu - ratings[leaderboard[position+1]].mu) / (ratings[leaderboard[position]].sigma) 188 std = erf(std/sqrt(2)) 189 else: 190 std = '-' 191 print("{0:<2} champion {1:<15} estimate {2:<20} mu {3:<20} sigma {4:<20} prob {5:<20}".format(position+1, champion, conservative_estimate(champion), ratings[champion].mu, ratings[champion].sigma, std))