From 69137be08939796dde60eda6848bef36bda40865 Mon Sep 17 00:00:00 2001 From: Ant Zucaro Date: Sun, 24 Dec 2017 15:29:28 -0500 Subject: [PATCH] Various bugfixes from testing. --- xonstat/glicko.py | 36 ++++++++++++++++++++++++++-------- xonstat/views/submission.py | 39 ++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/xonstat/glicko.py b/xonstat/glicko.py index 4aed505..f9f9020 100644 --- a/xonstat/glicko.py +++ b/xonstat/glicko.py @@ -92,6 +92,9 @@ def rate(player, opponents, results): Calculate the ratings improvement for a given player, provided their opponents and corresponding results versus them. """ + if len(opponents) == 0 or len(results) == 0: + return player + p_g2 = player.to_glicko2() gs = [] @@ -242,11 +245,20 @@ class GlickoProcessor(object): .filter(PlayerGameStat.player_id > 2)\ .all() + return pgstats_raw + except Exception as e: log.error("Error fetching player_game_stat records for game {}".format(game.game_id)) log.error(e) raise e + def _filter_pgstats(self, game, pgstats_raw): + """ + Filter the raw game stats so that all of them are Glicko-eligible. + + :param pgstats_raw: the list of raw PlayerGameStat + :return: list of PlayerGameStat + """ pgstats = [] for pgstat in pgstats_raw: # ensure warmup isn't included in the pgstat records @@ -257,6 +269,8 @@ class GlickoProcessor(object): k = KREDUCTION.eval(pgstat.alivetime.total_seconds(), game.duration.total_seconds()) if k <= 0.0: continue + elif pgstat.player_id <= 2: + continue else: pgstats.append(pgstat) @@ -264,7 +278,7 @@ class GlickoProcessor(object): def _load_glicko_wip(self, player_id, game_type_cd, category): """ - Retrieve a PlayerGlicko record from the database. + Retrieve a PlayerGlicko record from the database or local cache. :param player_id: the player ID to fetch :param game_type_cd: the game type code @@ -290,12 +304,18 @@ class GlickoProcessor(object): return wip - def load(self, game_id): + def load(self, game_id, game=None, pgstats=None): """ Load all of the needed information from the database. Compute results for each player pair. """ - game = self._load_game(game_id) - pgstats = self._load_pgstats(game) + if game is None: + game = self._load_game(game_id) + + if pgstats is None: + pgstats = self._load_pgstats(game) + + pgstats = self._filter_pgstats(game, pgstats) + game_type_cd = game.game_type_cd category = game.category @@ -348,7 +368,7 @@ class GlickoProcessor(object): new_pg = rate(wip.pg, wip.opponents, wip.results) log.debug("New rating for player {} before factors: mu={} phi={} sigma={}" - .format(pg.player_id, new_pg.mu, new_pg.phi, new_pg.sigma)) + .format(new_pg.player_id, new_pg.mu, new_pg.phi, new_pg.sigma)) avg_k_factor = sum(wip.k_factors)/len(wip.k_factors) avg_ping_factor = LATENCY_TREND_FACTOR * sum(wip.ping_factors)/len(wip.ping_factors) @@ -362,15 +382,15 @@ class GlickoProcessor(object): log.debug("New rating for player {} after factors: mu={} phi={} sigma={}" .format(wip.pg.player_id, wip.pg.mu, wip.pg.phi, wip.pg.sigma)) - def save(self, session): + def save(self): """ Put all changed PlayerElo and PlayerGameStat instances into the session to be updated or inserted upon commit. """ for wip in self.wips.values(): - session.add(wip.pg) + self.session.add(wip.pg) - session.commit() + self.session.commit() def main(): diff --git a/xonstat/views/submission.py b/xonstat/views/submission.py index 8b42dbc..135a08f 100644 --- a/xonstat/views/submission.py +++ b/xonstat/views/submission.py @@ -8,6 +8,7 @@ import pyramid.httpexceptions from sqlalchemy import Sequence from sqlalchemy.orm.exc import NoResultFound from xonstat.elo import EloProcessor +from xonstat.glicko import GlickoProcessor from xonstat.models import DBSession, Server, Map, Game, PlayerGameStat, PlayerWeaponStat from xonstat.models import PlayerRank, PlayerCaptime, PlayerGameFragMatrix from xonstat.models import TeamGameStat, PlayerGameAnticheat, Player, Hashkey, PlayerNick @@ -244,8 +245,8 @@ class Submission(object): self.weapons) -def elo_submission_category(submission): - """Determines the Elo category purely by what is in the submission data.""" +def game_category(submission): + """Determines the game's category purely by what is in the submission data.""" mod = submission.mod vanilla_allowed_weapons = {"shotgun", "devastator", "blaster", "mortar", "vortex", "electro", @@ -405,8 +406,8 @@ def should_do_weapon_stats(game_type_cd): return game_type_cd not in {'cts'} -def gametype_elo_eligible(game_type_cd): - """True of the game type should process Elos. False otherwise.""" +def gametype_rating_eligible(game_type_cd): + """True of the game type should process ratings (Elo/Glicko). False otherwise.""" return game_type_cd in {'duel', 'dm', 'ca', 'ctf', 'tdm', 'ka', 'ft'} @@ -590,7 +591,7 @@ def get_or_create_map(session, name): def create_game(session, game_type_cd, server_id, map_id, match_id, start_dt, duration, mod, - winner=None): + winner=None, category=None): """ Creates a game. Parameters: @@ -603,6 +604,7 @@ def create_game(session, game_type_cd, server_id, map_id, match_id, start_dt, du start_dt - when the game started (datetime object) duration - how long the game lasted winner - the team id of the team that won + category - the category of the game """ seq = Sequence('games_game_id_seq') game_id = session.execute(seq) @@ -618,6 +620,8 @@ def create_game(session, game_type_cd, server_id, map_id, match_id, start_dt, du game.duration = duration + game.category = category + try: session.query(Game).filter(Game.server_id == server_id)\ .filter(Game.match_id == match_id).one() @@ -1129,14 +1133,15 @@ def submit_stats(request): map_id=gmap.map_id, match_id=submission.match_id, start_dt=datetime.datetime.utcnow(), - duration=submission.duration + duration=submission.duration, + category=game_category(submission) ) events_by_hashkey = {elem["P"]: elem for elem in submission.humans + submission.bots} players_by_hashkey = get_or_create_players(session, events_by_hashkey) pgstats = [] - elo_pgstats = [] + rating_pgstats = [] player_ids = [] hashkeys_by_player_id = {} for hashkey, player in players_by_hashkey.items(): @@ -1147,12 +1152,12 @@ def submit_stats(request): frag_matrix = create_frag_matrix(session, submission.player_indexes, pgstat, events) - # player ranking opt-out + # player rating opt-out if 'r' in events and events['r'] == '0': - log.debug("Excluding player {} from ranking calculations (opt-out)" + log.debug("Excluding player {} from rating calculations (opt-out)" .format(pgstat.player_id)) - else: - elo_pgstats.append(pgstat) + elif pgstat.player_id > 2: + rating_pgstats.append(pgstat) if player.player_id > 1: create_anticheats(session, pgstat, game, player, events) @@ -1170,10 +1175,18 @@ def submit_stats(request): for events in submission.teams: create_team_stat(session, game, events) - if server.elo_ind and gametype_elo_eligible(submission.game_type_cd): - ep = EloProcessor(session, game, elo_pgstats) + rating_eligible = gametype_rating_eligible(submission.game_type_cd) + if rating_eligible and server.elo_ind and len(rating_pgstats) > 1: + # calculate Elo ratings + ep = EloProcessor(session, game, rating_pgstats) ep.save(session) elos = ep.wip + + # calculate Glicko ratings + gp = GlickoProcessor(session) + gp.load(game.game_id, game, rating_pgstats) + gp.process() + gp.save() else: elos = {} -- 2.39.2