def test_deprecated_individual_rating_groups(): r1, r2, r3 = Rating(50, 1), Rating(10, 5), Rating(15, 5) with raises(TypeError): deprecated_call(rate, [r1, r2, r3]) with raises(TypeError): deprecated_call(quality, [r1, r2, r3]) assert t.transform_ratings([r1, r2, r3]) == rate([(r1,), (r2,), (r3,)]) assert t.match_quality([r1, r2, r3]) == quality([(r1,), (r2,), (r3,)]) deprecated_call(t.transform_ratings, [r1, r2, r3]) deprecated_call(t.match_quality, [r1, r2, r3])
def test_deprecated_individual_rating_groups(): r1, r2, r3 = Rating(50, 1), Rating(10, 5), Rating(15, 5) with raises(TypeError): deprecated_call(rate, [r1, r2, r3]) with raises(TypeError): deprecated_call(quality, [r1, r2, r3]) assert t.transform_ratings([r1, r2, r3]) == rate([(r1, ), (r2, ), (r3, )]) assert t.match_quality([r1, r2, r3]) == quality([(r1, ), (r2, ), (r3, )]) deprecated_call(t.transform_ratings, [r1, r2, r3]) deprecated_call(t.match_quality, [r1, r2, r3])
def Update(self,ladder_id,match,db): #print 'match id %d'%match.id scores, result_dict = calculateWinnerOrder(match,db) session = db.session() teams = defaultdict(tuple) player_id_map = defaultdict(list) def constant_factory(value): import itertools return itertools.repeat(value).next minteam_score = defaultdict(constant_factory(100000)) for name,result in result_dict.iteritems(): score = scores[name] * -1 player_id = session.query( Player ).filter( Player.nick == name ).first().id rank = session.query( TrueskillRanks ).filter( TrueskillRanks.ladder_id == ladder_id ).filter( TrueskillRanks.player_id == player_id ).first() if not rank: rank = TrueskillRanks() rank.ladder_id = ladder_id rank.player_id = player_id session.add(rank) rank.rank = score l = list(teams[result.team]) l.append(rank.rating) teams[result.team] = tuple(l) player_id_map[result.team].append(player_id) minteam_score[result.team] = min(minteam_score[result.team],score) session.commit() ordered_teams = [] ordered_minteam_score = [] team_ids = teams.keys() team_ids.sort() for i in range(len(teams)): ordered_teams.append(teams[team_ids[i]]) ordered_minteam_score.append(minteam_score[team_ids[i]]) i = 0 if len(ordered_teams) < 2: Log.error('less than 2 teams, cannot update ranking') raise Exception('not enough teams') for team_ratings in trueskill.transform_ratings(ordered_teams,ordered_minteam_score): j = 0 current_team = team_ids[i] q = session.query( TrueskillRanks ).filter( TrueskillRanks.ladder_id == ladder_id ) for rating in team_ratings: pids = player_id_map[current_team] pid = pids[j] rank = q.filter( TrueskillRanks.player_id == pid ).one() rank.rating = rating session.add( rank ) j += 1 i += 1 session.commit() session.close()
def create_match(session, user1, user2, timestamp, games): """Creates and adds a match to the session given two users a unix timestamp and a list of score pairs.""" match = Match(timestamp, int(time.time())) p1 = Participation() user1.participations.append(p1) match.participations.append(p1) p2 = Participation() user2.participations.append(p2) match.participations.append(p2) p1_wins = len(filter(lambda g: g[0] > g[1], games)) p2_wins = len(filter(lambda g: g[0] < g[1], games)) u1ratings = get_most_recent_ratings(user1.id) u2ratings = get_most_recent_ratings(user2.id) u1ratings.pop('exposure') u2ratings.pop('exposure') p1rating = trueskill.Rating(**u1ratings) p2rating = trueskill.Rating(**u2ratings) if p1_wins > p2_wins: ranks = [0, 1] else: ranks = [1, 0] new_ratings = trueskill.transform_ratings([(p1rating,), (p2rating,)], ranks=ranks) (p1rating,), (p2rating,) = new_ratings true_skill_rating1 = TrueSkillCache(p1rating.sigma, p1rating.mu, p1rating.exposure) match.true_skill_ratings.append(true_skill_rating1) user1.true_skill_ratings.append(true_skill_rating1) true_skill_rating2 = TrueSkillCache(p2rating.sigma, p2rating.mu, p2rating.exposure) match.true_skill_ratings.append(true_skill_rating2) user2.true_skill_ratings.append(true_skill_rating2) for game_scores in games: game = Game(match) s1 = Score(game_scores[0]) game.scores.append(s1) user1.scores.append(s1) s2 = Score(game_scores[1]) game.scores.append(s2) user2.scores.append(s2) match.games.append(game) session.add(match)
def test_6_players_no_draws(): r1 = trueskill.Rating() r2 = trueskill.Rating() r3 = trueskill.Rating() r4 = trueskill.Rating() r5 = trueskill.Rating() r6 = trueskill.Rating() sigma0 = r1.sigma [(r1, ), (r2, ), (r3, ), (r4, ), (r5, ), (r6, )] = \ trueskill.transform_ratings([(r1, ), (r2, ), (r3, ), (r4, ), (r5, ), (r6, )]) assert r1.mu > r2.mu > r3.mu > r4.mu > r5.mu > r6.mu for rating in [r1, r2, r3, r4, r5, r6]: assert rating.sigma < sigma0
def test_3_player_draw(): r1 = trueskill.Rating() r2 = trueskill.Rating() r3 = trueskill.Rating() sigma0 = r1.sigma [(r1, ), (r2, ), (r3, )] = trueskill.transform_ratings([(r1, ), (r2, ), (r3, )], ranks=(1, 1, 3)) # We would hope it would be zero, but trueskill isn't that accurate. assert abs(r1.mu - r2.mu) < 0.01 assert r1.mu > r3.mu assert r2.mu > r3.mu for rating in [r1, r2, r3]: assert rating.sigma < sigma0
def process(self, line): """Process a line denoting one match, and update the ratings.""" line = line.strip() if not line or line.startswith("#"): return parts = line.split(",") assert len(parts) >= 2 winners = [parts[0]] losers = parts[1:] winner_lists = explode(winners) loser_lists = explode(losers) for winner_list in winner_lists: for winner in winner_list: self.wins[winner] += 1 for loser_list in loser_lists: for loser in loser_list: self.losses[loser] += 1 # Fetch old ratings for combatants all_ratings = [] for team in winner_lists + loser_lists: team_ratings = [] for name in team: team_ratings.append(self.ratings[name]) team_ratings = tuple(team_ratings) all_ratings.append(team_ratings) # Calculate new ratings for combatants new_ratings = trueskill.transform_ratings(all_ratings) # Store new ratings for combatants for ii, team in enumerate(winner_lists + loser_lists): new_team_ratings = new_ratings[ii] for jj, name in enumerate(team): self.ratings[name] = new_team_ratings[jj]
def save_game(self, game): """Save a finished Game to the results database. XXX This involves a non-trivial amount of computation and I/O, so maybe it should be run from a thread to avoid blocking the reactor. But sqlite is not thread-safe so we can't reuse connections. """ logging.info("") with self.connection: cursor = self.connection.cursor() for player in game.players: logging.info("%s %s", player.player_class, player.player_info) # See if that player is already in the database query = """SELECT player_id FROM player where name = ? AND class = ?""" cursor.execute(query, (player.name, player.player_class)) row = cursor.fetchone() # If not, insert it. if row is None: query = """INSERT INTO player (name, class, info, mu, sigma) VALUES (?, ?, ?, ?, ?)""" cursor.execute( query, (player.name, player.player_class, player.player_info, DEFAULT_MU, DEFAULT_SIGMA) ) # And fetch the player_id. query = """SELECT player_id FROM player where class = ? AND info = ?""" cursor.execute(query, (player.player_class, player.player_info)) row = cursor.fetchone() else: player_id = row["player_id"] # We may need to update info, if new fields were added. query = """UPDATE player SET info = ? where player_id = ?""" cursor.execute(query, (player.player_info, player_id)) player_id = row["player_id"] # Add the game. query = """INSERT INTO game (name, start_time, finish_time) VALUES (?, ?, ?)""" cursor.execute(query, (game.name, int(game.start_time), int(game.finish_time))) # Find the game_id for the just-inserted game. query = """SELECT game_id FROM game WHERE name = ? AND start_time = ? AND finish_time = ?""" cursor.execute(query, (game.name, int(game.start_time), int(game.finish_time))) row = cursor.fetchone() game_id = row["game_id"] rank = 1 for tup in game.finish_order: for player in tup: # Find the player_id query = """SELECT player_id FROM player WHERE name = ? AND class = ? AND info = ?""" cursor.execute(query, (player.name, player.player_class, player.player_info)) row = cursor.fetchone() player_id = row["player_id"] # Add to rank. query = """INSERT INTO rank(player_id, game_id, rank) VALUES (?, ?, ?)""" cursor.execute(query, (player_id, game_id, rank)) rank += len(tup) # Update trueskill values # There is a slight bias when there are ties, so we process tied # players in random order. query = """SELECT p.player_id, p.mu, p.sigma, r.rank FROM player p, rank r WHERE p.player_id = r.player_id AND r.game_id = ? ORDER BY r.rank, RANDOM()""" cursor.execute(query, (game_id,)) rows = cursor.fetchall() player_ids = [] rating_tuples = [] ranks = [] for row in rows: player_id = row["player_id"] player_ids.append(player_id) rank = row["rank"] ranks.append(rank) mu = row["mu"] sigma = row["sigma"] rating = trueskill.Rating(mu=mu, sigma=sigma) rating_tuples.append((rating,)) rating_tuples2 = trueskill.transform_ratings(rating_tuples, ranks) while player_ids: player_id = player_ids.pop() rating = rating_tuples2.pop()[0] mu = rating.mu sigma = rating.sigma query = """UPDATE player set mu = ?, sigma = ? WHERE player_id = ?""" cursor.execute(query, (mu, sigma, player_id)) logging.info("")
def save_game(self, game): """Save a finished Game to the results database. XXX This involves a non-trivial amount of computation and I/O, so maybe it should be run from a thread to avoid blocking the reactor. But sqlite is not thread-safe so we can't reuse connections. """ logging.info("") with self.connection: cursor = self.connection.cursor() for player in game.players: logging.info("%s %s", player.player_class, player.player_info) # See if that player is already in the database query = """SELECT player_id FROM player where name = ? AND class = ?""" cursor.execute(query, (player.name, player.player_class)) row = cursor.fetchone() # If not, insert it. if row is None: query = """INSERT INTO player (name, class, info, mu, sigma) VALUES (?, ?, ?, ?, ?)""" cursor.execute( query, (player.name, player.player_class, player.player_info, DEFAULT_MU, DEFAULT_SIGMA)) # And fetch the player_id. query = """SELECT player_id FROM player where class = ? AND info = ?""" cursor.execute(query, (player.player_class, player.player_info)) row = cursor.fetchone() else: player_id = row["player_id"] # We may need to update info, if new fields were added. query = """UPDATE player SET info = ? where player_id = ?""" cursor.execute(query, (player.player_info, player_id)) player_id = row["player_id"] # Add the game. query = """INSERT INTO game (name, start_time, finish_time) VALUES (?, ?, ?)""" cursor.execute( query, (game.name, int(game.start_time), int(game.finish_time))) # Find the game_id for the just-inserted game. query = """SELECT game_id FROM game WHERE name = ? AND start_time = ? AND finish_time = ?""" cursor.execute( query, (game.name, int(game.start_time), int(game.finish_time))) row = cursor.fetchone() game_id = row["game_id"] rank = 1 for tup in game.finish_order: for player in tup: # Find the player_id query = """SELECT player_id FROM player WHERE name = ? AND class = ? AND info = ?""" cursor.execute( query, (player.name, player.player_class, player.player_info)) row = cursor.fetchone() player_id = row["player_id"] # Add to rank. query = """INSERT INTO rank(player_id, game_id, rank) VALUES (?, ?, ?)""" cursor.execute(query, (player_id, game_id, rank)) rank += len(tup) # Update trueskill values # There is a slight bias when there are ties, so we process tied # players in random order. query = """SELECT p.player_id, p.mu, p.sigma, r.rank FROM player p, rank r WHERE p.player_id = r.player_id AND r.game_id = ? ORDER BY r.rank, RANDOM()""" cursor.execute(query, (game_id, )) rows = cursor.fetchall() player_ids = [] rating_tuples = [] ranks = [] for row in rows: player_id = row["player_id"] player_ids.append(player_id) rank = row["rank"] ranks.append(rank) mu = row["mu"] sigma = row["sigma"] rating = trueskill.Rating(mu=mu, sigma=sigma) rating_tuples.append((rating, )) rating_tuples2 = trueskill.transform_ratings(rating_tuples, ranks) while player_ids: player_id = player_ids.pop() rating = rating_tuples2.pop()[0] mu = rating.mu sigma = rating.sigma query = """UPDATE player set mu = ?, sigma = ? WHERE player_id = ?""" cursor.execute(query, (mu, sigma, player_id)) logging.info("")