]> git.rm.cloudns.org Git - xonotic/xonstat.git/commitdiff
Outline of a processor class for batch and per-game calculations.
authorAnt Zucaro <azucaro@gmail.com>
Sat, 23 Dec 2017 13:34:56 +0000 (08:34 -0500)
committerAnt Zucaro <azucaro@gmail.com>
Sat, 23 Dec 2017 13:34:56 +0000 (08:34 -0500)
xonstat/glicko.py

index ccda6d5c942d6800dec14c1ac1520d85e6fa8d96..f8ca65303998141e67360cab48020153c58d5cef 100644 (file)
@@ -2,7 +2,7 @@ import logging
 import math
 import sys
 
-from xonstat.models import PlayerGlicko
+from xonstat.models import PlayerGlicko, Game, PlayerGameStat
 
 log = logging.getLogger(__name__)
 
@@ -166,6 +166,119 @@ class KReduction:
 KREDUCTION = KReduction()
 
 
+class GlickoWIP(object):
+    """ A work-in-progress Glicko value. """
+    def __init__(self, pg):
+        """
+        Initialize a GlickoWIP instance.
+        :param pg: the player's PlayerGlicko record.
+        """
+        # the player's current (or base) PlayerGlicko record
+        self.pg = pg
+
+        # the list of k factors for each game in the ranking period
+        self.ks = []
+
+        # the list of opponents (PlayerGlicko or PlayerGlickoBase) in the ranking period
+        self.opponents = []
+
+        # the list of results for those games in the ranking period
+        self.results = []
+
+
+class GlickoProcessor(object):
+    """
+    Processes the given list games using the Glicko2 algorithm.
+    """
+    def __init__(self, session):
+        """
+        Create a GlickoProcessor instance.
+
+        :param session: the SQLAlchemy session to use for fetching/saving records.
+        :param game_ids: the list of game_ids that need to be processed.
+        """
+        self.session = session
+        self.wips = {}
+
+    def scorefactor(self, si, sj, game_type_cd):
+        """
+        Calculate the real scorefactor of the game. This is how players
+        actually performed, which is compared to their expected performance.
+
+        :param si: the score per second of player I
+        :param sj: the score per second of player J
+        :param game_type_cd: the game type of the game in question
+        :return: float
+        """
+        scorefactor_real = si / float(si + sj)
+
+        # duels are done traditionally - a win nets
+        # full points, not the score factor
+        if game_type_cd == 'duel':
+            # player i won
+            if scorefactor_real > 0.5:
+                scorefactor_real = 1.0
+            # player j won
+            elif scorefactor_real < 0.5:
+                scorefactor_real = 0.0
+            # nothing to do here for draws
+
+        return scorefactor_real
+
+    def pingfactor(self, pi, pj):
+        """
+        Calculate the ping differences between the two players, but only if both have them.
+
+        :param pi: the latency of player I
+        :param pj: the latency of player J
+        :return: float
+        """
+        if pi is None or pj is None or pi < 0 or pj < 0:
+            # default to a draw
+            return 0.5
+
+        else:
+            return float(pi)/(pi+pj)
+
+    def load(self, game_id):
+        """
+        Load all of the needed information from the database.
+        """
+        try:
+            game = self.session.query(Game).filter(Game.game_id==game_id).one()
+        except:
+            log.error("Game ID {} not found.".format(game_id))
+            return
+
+        try:
+            pgstats_raw = self.session.query(PlayerGameStat)\
+                .filter(PlayerGameStat.game_id==game_id)\
+                .filter(PlayerGameStat.player_id > 2)\
+                .all()
+
+            # ensure warmup isn't included in the pgstat records
+            for pgstat in pgstats_raw:
+                if pgstat.alivetime > game.duration:
+                    pgstat.alivetime = game.duration
+        except:
+            log.error("Error fetching player_game_stat records for game {}".format(self.game_id))
+            return
+
+    def process(self):
+        """
+        Calculate the Glicko2 ratings, deviations, and volatility updates for the records loaded.
+        :return: bool
+        """
+        pass
+
+    def save(self, session):
+        """
+        Put all changed PlayerElo and PlayerGameStat instances into the
+        session to be updated or inserted upon commit.
+        """
+        pass
+
+
 def main():
     # the example in the actual Glicko2 paper, for verification purposes
     pA = PlayerGlicko(1, "duel", mu=1500, phi=200)