-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgroupelo.py
More file actions
executable file
·214 lines (182 loc) · 6.55 KB
/
groupelo.py
File metadata and controls
executable file
·214 lines (182 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python
"""Calculate Elo ratings for a series of game results, where games can
include variable numbers of players and teams.
See http://en.wikipedia.org/wiki/Elo_rating_system
New players start at 1500
Rn = Ro + K(W-We)
Rn is new rating
Ro is previous rating
K is the same for every 2-player match
W is 1 for win, 0 for loss, 0.5 for draw
We is win expectancy
For 2 players, We = 1 / (10 ** (-delta/400) + 1)
where delta is the difference in ratings
For N players, assume each player played a game against
each of his opponents. But reduce K so that multiplayer
matches count the same as 2-player matches.
We add 1 point per match, for "anti-deflation"
"""
__copyright__ = "Copyright 2010 David Ripton"
__license__ = "MIT"
import sys
import itertools
from collections import defaultdict
STARTING_RATING = 1500
ANTI_DEFLATION = 1
CATEGORIES = [
"overall",
"group", "solo",
"armed", "unarmed",
"tournament", "exhibition",
]
def constant_factory(value):
return itertools.repeat(value).next
def explode(li):
"""Convert a list of strings into a list of lists of strings.
Each inner list is one team.
"""
result = []
for el in li:
li2 = el.split("&")
inner = [el2.strip() for el2 in li2]
result.append(inner)
return result
def win_expectancy(r1, r2):
"""Return the win expectancy for the player with rating r1 against
the player with rating r2."""
return 1.0 / (10 ** ((r2 - r1) / 400.0) + 1)
def rating_delta(r1, r2, w):
"""Return the rating delta (relative to player with rating r1) for a
match between players with ratings r1 and r2, and result w.
w is 1 for a win for r1, 0 for a loss for r1, and 0.5 for a draw.
"""
k = 50
we = win_expectancy(r1, r2)
return k * (w - we)
def bare_name(name):
"""Return name without any trailing '*' or '!' characters."""
while name and (name[-1] == "*" or name[-1] == "!"):
name = name[:-1]
return name
class Elo(object):
def __init__(self, category, lines):
assert category in CATEGORIES
self.category = category
self.lines = lines
# name: rating
self.ratings = defaultdict(constant_factory(STARTING_RATING))
# name: number of wins
self.wins = defaultdict(int)
# name: number of losses
self.losses = defaultdict(int)
def process(self, line):
"""Process a line denoting one match, and update the ratings."""
line = line.strip()
if not line or line.startswith("#"):
return
parts = line.split(",")
assert len(parts) >= 4
game_id = parts[0].strip()
armed_unarmed = parts[1].strip()
tournament_exhibition = parts[2].strip()
winners = [parts[3]]
losers = parts[4:]
winner_lists = explode(winners)
loser_lists = explode(losers)
# Filter out results for the wrong category of fight
if self.category == "overall":
pass
elif self.category == "group":
if len(winner_lists[0]) == 1 and len(loser_lists) == 1:
return
elif self.category == "solo":
if len(winner_lists[0]) > 1 or len(loser_lists) > 1:
return
elif self.category == "armed":
if armed_unarmed != self.category:
return
elif self.category == "unarmed":
if armed_unarmed != self.category:
return
elif self.category == "tournament":
if tournament_exhibition != self.category:
return
elif self.category == "exhibition":
if tournament_exhibition != self.category:
return
# name: change in rating
deltas = defaultdict(int)
for winner_list in winner_lists:
for winner in winner_list:
name = bare_name(winner)
self.wins[name] += 1
for loser_list in loser_lists:
for loser in loser_list:
name = bare_name(loser)
self.losses[name] += 1
opponent_count = 0
for loser_list in loser_lists:
for loser in loser_list:
opponent_count += 1
# Assumes all teams have the same number of players.
ally_count = -1
for winner_list in winner_lists:
for winner in winner_list:
ally_count += 1
for ii, loser_list in enumerate(loser_lists):
for loser in loser_list:
loser = bare_name(loser)
for winner_list in winner_lists:
for winner in winner_list:
winner = bare_name(winner)
wr = self.ratings[winner]
lr = self.ratings[loser]
delta = rating_delta(wr, lr, 1)
deltas[winner] += delta
deltas[loser] -= delta
# Only the losers after this one, to avoid double-counting.
for jj in xrange(ii + 1, len(loser_lists)):
loser_list2 = loser_lists[jj]
for loser2 in loser_list2:
loser2 = bare_name(loser2)
r1 = self.ratings[loser]
r2 = self.ratings[loser2]
delta = rating_delta(r1, r2, 0.5)
deltas[loser] += delta
deltas[loser2] -= delta
adjusted_deltas = {}
for key, value in deltas.iteritems():
if ally_count == 0:
adjusted_deltas[key] = value / opponent_count ** 0.5
else:
adjusted_deltas[key] = value / opponent_count
for name, delta in adjusted_deltas.iteritems():
self.ratings[name] += delta + ANTI_DEFLATION
def process_all(self):
"""Process all lines."""
for line in self.lines:
self.process(line)
def output(self):
sorted_ratings = sorted((
(rating, name) for name, rating in self.ratings.iteritems()),
reverse=True)
print self.category
for rating, name in sorted_ratings:
print "%.3f %s (%d-%d)" % (rating, name,
self.wins[name], self.losses[name])
print
def main():
if len(sys.argv) > 1:
fn = sys.argv[1]
fil = open(fn)
else:
fil = sys.stdin
bytes = fil.read()
fil.close()
lines = bytes.split("\n")
for category in CATEGORIES:
elo = Elo(category, lines)
elo.process_all()
elo.output()
if __name__ == "__main__":
main()