]> git.xonotic.org Git - xonotic/xonstat.git/commitdiff
Various bugfixes from testing.
authorAnt Zucaro <azucaro@gmail.com>
Sun, 24 Dec 2017 20:29:28 +0000 (15:29 -0500)
committerAnt Zucaro <azucaro@gmail.com>
Sun, 24 Dec 2017 20:29:28 +0000 (15:29 -0500)
xonstat/glicko.py
xonstat/views/submission.py

index 4aed505654259e13d6e58a0b328523436148d3eb..f9f9020d0869f63a13bac398ddfd0f1e77dff545 100644 (file)
@@ -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.
     """
     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 = []
     p_g2 = player.to_glicko2()
 
     gs = []
@@ -242,11 +245,20 @@ class GlickoProcessor(object):
                 .filter(PlayerGameStat.player_id > 2)\
                 .all()
 
                 .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
 
         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
         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
             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)
 
             else:
                 pgstats.append(pgstat)
 
@@ -264,7 +278,7 @@ class GlickoProcessor(object):
 
     def _load_glicko_wip(self, player_id, game_type_cd, category):
         """
 
     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
 
         :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
 
 
         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.
         """
         """
         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
 
         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={}"
             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)
 
             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))
 
             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():
         """
         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():
 
 
 def main():
index 8b42dbc53ad031c9fb762674275a040fb6311ab4..135a08f799afc4c73f6d6b32ede55792d8632e32 100644 (file)
@@ -8,6 +8,7 @@ import pyramid.httpexceptions
 from sqlalchemy import Sequence
 from sqlalchemy.orm.exc import NoResultFound
 from xonstat.elo import EloProcessor
 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
 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)
 
 
             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",
     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'}
 
 
     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'}
 
 
     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,
 
 
 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:
 
     """
     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
     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)
     """
     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.duration = duration
 
+    game.category = category
+
     try:
         session.query(Game).filter(Game.server_id == server_id)\
             .filter(Game.match_id == match_id).one()
     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(),
             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 = []
         )
 
         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():
         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)
 
 
             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':
             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))
                           .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)
 
             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)
 
         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
             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 = {}
 
         else:
             elos = {}