]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/elo.py
Initial debug version of elo ranking.
[xonotic/xonstat.git] / xonstat / elo.py
1 import sys\r
2 import math\r
3 import random\r
4 \r
5 class EloParms:\r
6     def __init__(self, global_K = 15, initial = 100, floor = 100, logdistancefactor = math.log(10)/float(400), maxlogdistance = math.log(10)):\r
7         self.global_K = global_K\r
8         self.initial = initial\r
9         self.floor = floor\r
10         self.logdistancefactor = logdistancefactor\r
11         self.maxlogdistance = maxlogdistance\r
12 \r
13 \r
14 class KReduction:\r
15     def __init__(self, fulltime, mintime, minratio, games_min, games_max, games_factor):\r
16         self.fulltime = fulltime\r
17         self.mintime = mintime\r
18         self.minratio = minratio\r
19         self.games_min = games_min\r
20         self.games_max = games_max\r
21         self.games_factor = games_factor\r
22 \r
23     def eval(self, mygames, mytime, matchtime):\r
24         if mytime < self.mintime:\r
25             return 0\r
26         if mytime < self.minratio * matchtime:\r
27             return 0\r
28         if mytime < self.fulltime:\r
29             k = mytime / float(self.fulltime)\r
30         else:\r
31             k = 1.0\r
32         if mygames >= self.games_max:\r
33             k *= self.games_factor\r
34         elif mygames > self.games_min:\r
35             k *= 1.0 - (1.0 - self.games_factor) * (mygames - self.games_min) / float(self.games_max - self.games_min)\r
36         return k\r
37 \r
38 \r
39 # For team games where multiple scores and elos are at play, the elos\r
40 # must be adjusted according to their strength relative to the player\r
41 # in the next-lowest scoreboard position.\r
42 def update(elos, ep):\r
43     for x in elos:\r
44         if x.elo == None:\r
45             x.elo = ep.initial\r
46         x.eloadjust = 0\r
47     if len(elos) < 2:\r
48         return elos\r
49     for i in xrange(0, len(elos)):\r
50         ei = elos[i]\r
51         for j in xrange(i+1, len(elos)):\r
52             ej = elos[j]\r
53             si = ei.score\r
54             sj = ej.score\r
55 \r
56             # normalize scores\r
57             ofs = min(0, si, sj)\r
58             si -= ofs\r
59             sj -= ofs\r
60             if si + sj == 0:\r
61                 si, sj = 1, 1 # a draw\r
62 \r
63             # real score factor\r
64             scorefactor_real = si / float(si + sj)\r
65 \r
66             # estimated score factor by elo\r
67             elodiff = min(ep.maxlogdistance, max(-ep.maxlogdistance, (ei.elo - ej.elo) * ep.logdistancefactor))\r
68             scorefactor_elo = 1 / (1 + math.exp(-elodiff))\r
69 \r
70             # how much adjustment is good?\r
71             # scorefactor(elodiff) = 1 / (1 + e^(-elodiff * logdistancefactor))\r
72             # elodiff(scorefactor) = -ln(1/scorefactor - 1) / logdistancefactor\r
73             # elodiff'(scorefactor) = 1 / ((scorefactor) (1 - scorefactor) logdistancefactor)\r
74             # elodiff'(scorefactor) >= 4 / logdistancefactor\r
75 \r
76             # adjust'(scorefactor) = K1 + K2\r
77 \r
78             # so we want:\r
79             # K1 + K2 <= 4 / logdistancefactor <= elodiff'(scorefactor)\r
80             # as we then don't overcompensate\r
81 \r
82             adjustment = scorefactor_real - scorefactor_elo\r
83             ei.eloadjust += adjustment\r
84             ej.eloadjust -= adjustment\r
85     for x in elos:\r
86         x.elo = max(x.elo + x.eloadjust * x.k * ep.global_K / float(len(elos) - 1), ep.floor)\r
87         x.games += 1\r
88     return elos\r
89 \r
90 \r
91 # parameters for K reduction\r
92 # this may be touched even if the DB already exists\r
93 KREDUCTION = KReduction(600, 120, 0.5, 0, 32, 0.2)\r
94 \r
95 # parameters for chess elo\r
96 # only global_K may be touched even if the DB already exists\r
97 # we start at K=200, and fall to K=40 over the first 20 games\r
98 ELOPARMS = EloParms(global_K = 200)\r
99 \r