-
Notifications
You must be signed in to change notification settings - Fork 0
/
sum_of_wca_ranks.py
193 lines (166 loc) · 8.24 KB
/
sum_of_wca_ranks.py
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
import os, re
from glob import glob
from urllib.request import urlopen, urlretrieve
from collections import Counter
from time import time
from zipfile import ZipFile
from tkinter import *
from tkinter.ttk import *
from tkinter.messagebox import showinfo
def update_tsv_export(reporthook=None):
"""If export is missing or not current, download the current one. Returns True iff the export was updated."""
# Is export file missing or older than 10 minutes?
here = glob('WCA_export*_*.tsv.zip')
if not here or time() - os.stat(max(here)).st_mtime > 10 * 60:
# What's the current export on the WCA site?
base = 'https://www.worldcubeassociation.org/results/misc/'
try:
with urlopen(base + 'export.html') as f:
current = re.search(r'WCA_export\d+_\d+.tsv.zip', str(f.read())).group(0)
except:
print('failed looking for the newest export')
return
# Download if necessary, otherwise mark local as up-to-date
if not os.path.isfile(current):
if not reporthook:
print('downloading export', current, '...')
urlretrieve(base + current, current, reporthook)
for h in here:
if h != current:
os.remove(h)
return True
else:
os.utime(max(here))
def ranking_data():
"""Compute the data which can then be used for display or export."""
prepare_data()
# What's currently checked?
checked = [i for i, v in enumerate(vars) if v.get()]
# Compute everybody's sum of ranks (of the checked events)
sums = [(sum(ranks[i] for i in checked), personId)
for personId, ranks in person_ranks.items()]
# We'll use this to not show people with only default ranks
default_sum = sum(default_ranks[i] for i in checked)
# Build and return the rows to display
rows = []
ctr, prev_sum = 0, None
for sum_, personId in sorted(sums):
ctr += 1
pos = '' if sum_ == prev_sum else ctr
if sum_ == default_sum or type(pos) is int and pos > 100:
break
rows.append([pos, person_name[personId], sum_] + person_ranks[personId])
prev_sum = sum_
return rows
def show():
for x in tree.get_children():
tree.delete(x)
tree['displaycolumns'] = 'pos cuber sum ' + ' '.join(e for e, v in zip(eventIds, vars) if v.get())
global ranking_data_cache
ranking_data_cache = ranking_data()
for i, row in enumerate(ranking_data_cache):
tree.insert('', 'end', values=row, tags=('', 'stripe')[i % 2])
def export():
eIds = [e for e, v in zip(eventIds, vars) if v.get()]
name = 'Sum of Ranks (' + ', '.join(eIds) + ')'
column_names = ['Pos', 'Cubers', 'Sum'] + [e.strip('A') + '\navg' if e.endswith('A') else e for e in eIds]
note = "Using data from [url=https://www.worldcubeassociation.org/results/misc/export.html]" + max(glob('WCA_export*_*.tsv.zip')) + "[/url]" + \
" and Stefan's [url=https://github.com/pochmann/sum-of-wca-ranks/]Sum of WCA Ranks tool[/url]."
out = '[SPOILER="' + name + '"]' + note + '\n\n[TABLE="class:grid,align:left"]\n' \
'[TR][TD][B]' + '[/B][/TD][TD][B]'.join(n.split('[')[0] for n in column_names) + '[/B][/TD][/TR]\n'
for row in ranking_data_cache:
out += '[TR][TD=align:right]{}[/TD][TD]{}[/TD][TD=align:right][B]{}[/B][/TD]'.format(*row[:3])
for var, value, default_rank in zip(vars, row[3:], default_ranks):
if var.get():
color = '00FF00' if value <= 10 else 'FF0000' if value == default_rank else None
value = '[COLOR="#{}"][B]{}[/B][/COLOR]'.format(color, value) if color else str(value)
out += '[TD=align:right]' + value + '[/TD]'
out += '[/TR]\n'
out += '[/TABLE][/SPOILER]'
root.clipboard_clear()
root.clipboard_append(out)
showinfo(message='Copied to clipboard, you can now paste it (ctrl-V) into your forum post.')
def prepare_data():
global eventIds, event_name, person_name, person_ranks, default_ranks, eventIdsA, eventIndex
if 'person_ranks' in globals() and not update_tsv_export():
return
print('preparing data ...')
with ZipFile(max(glob('WCA_export*_*.tsv.zip'))) as zf:
def load(wanted_table, wanted_columns):
with zf.open('WCA_export_' + wanted_table + '.tsv') as tf:
column_names, *rows = [line.split('\t') for line in tf.read().decode().splitlines()]
columns = []
for name in wanted_columns.split():
i = column_names.index(name)
column = [row[i] for row in rows]
try:
column = list(map(int, column))
except:
pass
columns.append(column)
return list(zip(*columns))
event_name = dict(load('Events', 'id cellName'))
event_rank = dict(load('Events', 'id rank'))
person_name = dict((id, name) for id, subid, name in load('Persons', 'id subid name') if subid == 1)
# Get (personId, eventId, rank) triples, appending 'A' to eventIds of averages
ranks = load('RanksSingle', 'personId eventId worldRank') + \
[(p, e + 'A', r) for p, e, r in load('RanksAverage', 'personId eventId worldRank')]
# List of eventIds sorted by rank (and singles before averages)
eventIds = sorted({r[1] for r in ranks}, key=lambda e: (e.endswith('A'), event_rank[e.strip('A')]))
eventIndex = {id: i for i, id in enumerate(eventIds)}
# Compute the default ranks (the "red numbers")
list_sizes = Counter(r[1] for r in ranks)
default_ranks = [list_sizes[id] + 1 for id in eventIds]
# Build person_ranks
person_ranks = {r[0]: default_ranks[:] for r in ranks}
for personId, eventId, rank in ranks:
person_ranks[personId][eventIndex[eventId]] = rank
def make_me_look_good():
showinfo(message='Not implemented yet, sorry.')
update_tsv_export()
prepare_data()
# The window
root = Tk()
root.title('Sum of WCA Ranks')
root.iconbitmap('icon.ico')
# The checkbuttons & co
Label(root, text='Single').grid(row=0, column=1)
Label(root, text='Average').grid(row=0, column=2)
vars = []
for i, eventId in enumerate(eventIds, 1):
a = eventId.endswith('A')
if not a:
Label(root, text=event_name[eventId]).grid(row=i, column=0)
bottom = i + 1
vars.append(IntVar(value=not a))
Checkbutton(root, variable=vars[-1], command=show).grid(row=1 + eventIndex[eventId.strip('A')], column=1 + a)
def check(value, averages=False):
for v, eventId in zip(vars, eventIds):
if eventId.endswith('A') == averages:
v.set(value)
show()
Button(root, text='All', width=5, command=lambda: check(1)).grid(row=bottom, column=1)
Button(root, text='None', width=5, command=lambda: check(0)).grid(row=bottom + 1, column=1)
Button(root, text='All', width=5, command=lambda: check(1, True)).grid(row=bottom, column=2)
Button(root, text='None', width=5, command=lambda: check(0, True)).grid(row=bottom + 1, column=2)
# The ranking display
tree = Treeview(root, columns='pos cuber sum ' + ' '.join(eventIds), show='headings')
tree.tag_configure('stripe', background='#DDD')
tree.column('pos', anchor='e', width=35)
tree.heading('pos', text='Pos', anchor='e')
tree.column('cuber', width=150)
tree.heading('cuber', text='Cuber', anchor='w')
tree.column('sum', width=40, anchor='e')
tree.heading('sum', text='Sum', anchor='e')
for eventId in eventIds:
tree.column(eventId, width=50, anchor='e')
tree.heading(eventId, text=eventId, anchor='e')
tree.grid(row=0, column=3, rowspan=bottom + 2, columnspan=2, sticky='nswe')
vsb = Scrollbar(root, orient="vertical", command=tree.yview)
vsb.grid(row=0, column=5, rowspan=bottom + 2, sticky='ns')
tree.configure(yscrollcommand=vsb.set)
Button(root, text='Export for speedsolving.com', command=export).grid(row=bottom + 3, column=3)
Button(root, text='Make me look good', command=make_me_look_good).grid(row=bottom + 3, column=4)
# Let's do this!
root.after_idle(show)
root.mainloop()