forked from dmillard/pingpong
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pingpong.py
256 lines (213 loc) · 7.62 KB
/
pingpong.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import sqlite3
import trueskill as ts
from random import shuffle
from datetime import datetime
from ranking import Ranking
from flask import *
app = Flask(__name__)
DATABASE = 'pingpong.sqlite'
SCHEMA = '''
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS player (
id INTEGER PRIMARY KEY,
exposure REAL, -- result of trueskill.exposure()
alias TEXT UNIQUE, -- msft alias
nick TEXT UNIQUE, -- nickname
mu REAL,
sigma REAL,
won INTEGER,
lost INTEGER
);
CREATE INDEX IF NOT EXISTS exposure ON player (exposure);
CREATE TABLE IF NOT EXISTS match (
id INTEGER PRIMARY KEY,
winner REFERENCES player,
loser REFERENCES player,
winscore INTEGER,
losescore INTEGER,
date DATETIME,
scheduled BOOLEAN
);
CREATE TABLE IF NOT EXISTS schedule (
id INTEGER PRIMARY KEY,
p1 REFERENCES player,
p2 REFERENCES player
);
CREATE INDEX IF NOT EXISTS scheduleplayers ON schedule (p1, p2);
CREATE TABLE IF NOT EXISTS week (
week INTEGER
);
'''
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.executescript(SCHEMA);
db.row_factory = sqlite3.Row
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/', methods=['GET'])
def index():
db = get_db()
players = db.execute('SELECT * FROM player ORDER BY exposure DESC;')
players = Ranking(players.fetchall(), start=1,
key=lambda x: x['exposure'])
aliases = db.execute('SELECT alias FROM player ORDER BY alias;')
recents = db.execute('''
SELECT w.alias, l.alias, winscore, losescore, scheduled
FROM match
JOIN player w ON winner = w.id
JOIN player l ON loser = l.id
ORDER BY date DESC LIMIT 10;''')
games = db.execute('SELECT Count(*) AS TotalMatches FROM match;').fetchone()
# get or regenerate the schedule
weekrow = db.execute('SELECT * FROM week;').fetchone();
week = datetime.now().isocalendar()[1]
if weekrow is None or weekrow['week'] != week:
db.execute('DELETE FROM schedule;')
db.execute('DELETE FROM week;')
db.execute('INSERT INTO week VALUES (?);', (week,))
players2 = db.execute(
'SELECT * FROM player ORDER BY exposure DESC;').fetchall()
if week % 2 == 0: # random week
shuffle(players2)
players2 = players2[:len(players2)//2*2] # even the number
matches = []
for i in range(0, len(players2), 2):
p1 = players2[i]
p2 = players2[i+1]
matches.append((p1['id'], p2['id']))
db.executemany('''
INSERT INTO schedule (p1, p2)
VALUES (?, ?);''',
matches)
db.commit()
schedule = db.execute('''
SELECT p1.alias, p2.alias, p1.mu, p1.sigma, p2.mu, p2.sigma
FROM schedule
JOIN player p1 ON p1 = p1.id
JOIN player p2 ON p2 = p2.id;''').fetchall()
qualities = []
for match in schedule:
r1 = ts.Rating(match[2], match[3])
r2 = ts.Rating(match[4], match[5])
qualities.append(ts.quality_1vs1(r1, r2) * 100)
return render_template('index.html',
players=players, aliases=aliases, recents=recents, games = games[0],
schedule=zip(schedule, qualities), rankedweek=(week%2==1))
@app.route('/signup', methods=['POST'])
def signup():
db = get_db()
try:
rating = ts.Rating()
db.execute('''
INSERT INTO
player (alias, nick, mu, sigma, exposure, won, lost)
VALUES (?, ?, ?, ?, ?, 0, 0);''',
(request.form['alias'],
request.form['nick'],
rating.mu,
rating.sigma,
ts.expose(rating)))
db.commit()
except sqlite3.IntegrityError as e:
flash(str(e))
return redirect(url_for('index'))
@app.route('/record', methods=['POST'])
def record():
db = get_db()
p1 = request.form['p1']
s1 = int(request.form['s1'])
p2 = request.form['p2']
s2 = int(request.form['s2'])
if (not (0 <= s1 < 2 or 0 <= s2 < 2) or
not (s1 == 2 or s2 == 2) or
not 2 <= s1+s2 <= 3):
flash('Ladder is based on 3 game matches only')
return redirect(url_for('index'))
if p1 == p2:
flash('Match players must be different.')
return redirect(url_for('index'))
# check if this was a scheduled match
scheduledrow = db.execute('''
SELECT (s.id)
FROM schedule s
JOIN player p1 ON s.p1 = p1.id
JOIN player p2 ON s.p2 = p2.id
WHERE p1.alias=? AND p2.alias=?
OR p1.alias=? AND p2.alias=?;''',
(p1, p2, p2, p1)).fetchone()
scheduled = scheduledrow is not None
if scheduled:
db.execute('DELETE FROM schedule WHERE id=?;', (scheduledrow[0],))
if s1 > s2:
win_alias, win_score, lose_alias, lose_score = p1, s1, p2, s2
else:
win_alias, win_score, lose_alias, lose_score = p2, s2, p1, s1
date_string = request.form['date'] + ' '
date_string += request.form['time'] + ' '
date_string += 'PM' if 'ampm' in request.form else 'AM'
date = datetime.strptime(date_string, "%m/%d/%Y %I:%M %p")
db.execute('''
INSERT INTO
match (winner, loser, winscore, losescore, date, scheduled)
SELECT w.id, l.id, ?, ?, ?, ?
FROM player w JOIN player l
WHERE w.alias = ? AND l.alias = ?;''',
(win_score, lose_score, date, scheduled, win_alias, lose_alias))
def get_rating(alias):
sql = 'SELECT mu, sigma FROM player WHERE alias=?;'
row = db.execute(sql, (alias,)).fetchone()
if row is None:
flash('Alias ' + alias + ' does not exist.')
return None
return ts.Rating(mu=row['mu'], sigma=row['sigma'])
win_rating = get_rating(win_alias)
lose_rating = get_rating(lose_alias)
if win_rating is None or lose_rating is None:
return redirect(url_for('index'))
win_rating, lose_rating = ts.rate_1vs1(win_rating, lose_rating)
win_exposure = ts.expose(win_rating);
lose_exposure = ts.expose(lose_rating);
db.execute('''
UPDATE player
SET exposure=?, mu=?, sigma=?, won=won+1
WHERE alias=?;''',
(win_exposure, win_rating.mu, win_rating.sigma, win_alias));
db.execute('''
UPDATE player
SET exposure=?, mu=?, sigma=?, lost=lost+1
WHERE alias=?;''',
(lose_exposure, lose_rating.mu, lose_rating.sigma, lose_alias));
db.commit()
return redirect(url_for('index'))
@app.route('/matches', methods=['GET'])
def matches():
db = get_db()
recents = db.execute('''
SELECT w.alias, l.alias, winscore, losescore, scheduled, date
FROM match
JOIN player w ON winner = w.id
JOIN player l ON loser = l.id
ORDER BY date DESC;''')
return render_template('matches.html', recents=recents)
if __name__ == '__main__':
# set secret key for sessions
import string
import os.path
from random import SystemRandom
r = SystemRandom()
key_chars = string.ascii_letters + string.digits + string.punctuation
cwd = os.path.abspath(os.path.dirname(__file__))
try:
with open(os.path.join(cwd, 'key.json'), 'r') as f:
app.secret_key = json.load(f)['key']
except:
app.secret_key = ''.join(r.choice(key_chars) for i in range(64))
with open(os.path.join(cwd, 'key.json'), 'w') as f:
json.dump({'key' : app.secret_key}, f)
app.run(debug=False, host='0.0.0.0')