]> 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.
     """
+    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():
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 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 = {}