]> git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/elo.py
Use categories in submissions.
[xonotic/xonstat.git] / xonstat / elo.py
index 192817b4ec16bae532134f281b8e97786b28a474..2e2ba761884b18d11e30247d746f5ad8c063d412 100644 (file)
@@ -10,13 +10,13 @@ log = logging.getLogger(__name__)
 class EloParms:
     def __init__(self, global_K=15, initial=100, floor=100,
                  logdistancefactor=math.log(10)/float(400), maxlogdistance=math.log(10),
-                 latencyfactor=0.2):
+                 latency_trend_factor=0.2):
         self.global_K = global_K
         self.initial = initial
         self.floor = floor
         self.logdistancefactor = logdistancefactor
         self.maxlogdistance = maxlogdistance
-        self.latencyfactor = latencyfactor
+        self.latency_trend_factor = latency_trend_factor
 
 
 class KReduction:
@@ -99,11 +99,14 @@ class EloProcessor:
     """EloProcessor is a container for holding all of the intermediary AND
     final values used to calculate Elo deltas for all players in a given
     game."""
-    def __init__(self, session, game, pgstats):
+    def __init__(self, session, game, pgstats, category):
 
         # game which we are processing
         self.game = game
 
+        # which type of Elo category we are processing
+        self.category = category
+
         # work-in-progress values, indexed by player
         self.wip = {}
 
@@ -138,14 +141,16 @@ class EloProcessor:
 
         # Fetch current Elo values for all players. For players that don't yet 
         # have an Elo record, we'll give them a default one.
-        for e in session.query(PlayerElo).\
-                filter(PlayerElo.player_id.in_(self.wip.keys())).\
-                filter(PlayerElo.game_type_cd==game.game_type_cd).all():
+        for e in session.query(PlayerElo)\
+                .filter(PlayerElo.player_id.in_(self.wip.keys()))\
+                .filter(PlayerElo.game_type_cd==game.game_type_cd)\
+                .filter(PlayerElo.category==self.category)\
+                .all():
                     self.wip[e.player_id].elo = e
 
         for pid in self.wip.keys():
             if self.wip[pid].elo is None:
-                self.wip[pid].elo = PlayerElo(pid, game.game_type_cd, ELOPARMS.initial)
+                self.wip[pid].elo = PlayerElo(pid, game.game_type_cd, ELOPARMS.initial, category)
 
             # determine k reduction
             self.wip[pid].k = KREDUCTION.eval(self.wip[pid].elo.games, self.wip[pid].alivetime,
@@ -179,7 +184,8 @@ class EloProcessor:
     def pingfactor(self, pi, pj):
         """ Calculate the ping differences between the two players, but only if both have them. """
         if pi is None or pj is None or pi < 0 or pj < 0:
-            return None
+            # default to a draw
+            return 0.5
 
         else:
             return float(pi)/(pi+pj)
@@ -195,10 +201,12 @@ class EloProcessor:
         pids = self.wip.keys()
         for i in xrange(0, len(pids)):
             ei = self.wip[pids[i]].elo
+            pi = self.wip[pids[i]].pgstat.avg_latency
             for j in xrange(i+1, len(pids)):
                 ej = self.wip[pids[j]].elo
                 si = self.wip[pids[i]].score_per_second
                 sj = self.wip[pids[j]].score_per_second
+                pj = self.wip[pids[j]].pgstat.avg_latency
 
                 # normalize scores
                 ofs = min(0, si, sj)
@@ -215,19 +223,28 @@ class EloProcessor:
                     (float(ei.elo) - float(ej.elo)) * ep.logdistancefactor))
                 scorefactor_elo = 1 / (1 + math.exp(-elodiff))
 
+                # adjust the elo prediction according to ping
+                ping_ratio = self.pingfactor(pi, pj)
+                scorefactor_ping = ep.latency_trend_factor * (0.5 - ping_ratio)
+                scorefactor_elo_adjusted = max(0.0, min(1.0, scorefactor_elo + scorefactor_ping))
+
                 # initial adjustment values, which we may modify with additional rules
-                adjustmenti = scorefactor_real - scorefactor_elo
-                adjustmentj = scorefactor_elo - scorefactor_real
+                adjustmenti = scorefactor_real - scorefactor_elo_adjusted
+                adjustmentj = scorefactor_elo_adjusted - scorefactor_real
 
                 # DEBUG
                 # log.debug("(New) Player i: {0}".format(ei.player_id))
                 # log.debug("(New) Player i's K: {0}".format(self.wip[pids[i]].k))
                 # log.debug("(New) Player j: {0}".format(ej.player_id))
                 # log.debug("(New) Player j's K: {0}".format(self.wip[pids[j]].k))
+                # log.debug("(New) Ping ratio: {0}".format(ping_ratio))
                 # log.debug("(New) Scorefactor real: {0}".format(scorefactor_real))
                 # log.debug("(New) Scorefactor elo: {0}".format(scorefactor_elo))
-                # log.debug("(New) adjustment i: {0}".format(adjustmenti))
-                # log.debug("(New) adjustment j: {0}".format(adjustmentj))
+                # log.debug("(New) Scorefactor ping: {0}".format(scorefactor_ping))
+                # log.debug("(New) adjustment i: {0}".format(scorefactor_real - scorefactor_elo))
+                # log.debug("(New) adjustment j: {0}".format(scorefactor_elo - scorefactor_real))
+                # log.debug("(New) adjustment i with ping: {0}".format(adjustmenti))
+                # log.debug("(New) adjustment j with ping: {0}\n".format(adjustmentj))
 
                 if scorefactor_elo > 0.5:
                     # player i is expected to win
@@ -257,6 +274,9 @@ class EloProcessor:
             new_elo = max(float(w.elo.elo) + w.adjustment * w.k * ep.global_K / float(len(pids) - 1), ep.floor)
             w.elo_delta = new_elo - old_elo
 
+            log.debug("{}'s Old Elo: {} New Elo: {} Delta {}"
+                      .format(pid, old_elo, new_elo, w.elo_delta))
+
             w.elo.elo = new_elo
             w.elo.games += 1
             w.elo.update_dt = datetime.datetime.utcnow()