llimllib / ncaa-bracket-randomizer

Uses Ken Pomeroy's College Basketball rankings plus randomness to generate a bracket for you

This URL has Read+Write access

1db753ce » llimllib 2009-03-18 initial commit; previous de... 1 import re
2 from math import ceil, floor
3
4 bracket = eval(file("teams.dat").read())
5
6 teamre = '\s*(\d+) <a href="[^"]+">([^<]+)</a>\s*<a href=.*?</a></a>\s*(\d+-\d+)\s*(\.\d+)\s*([\d.]*)\/(\d+)\s*([\d.]*)\/(\d+)'
7 kenpom = [x.groups() for x in [re.search(teamre, line) for line in file("kenpom.html")] if x]
8 #kenpom is a list of tuples:
9 #(rank, name, record, pythagorean score, adjusted O, adj O rank, adjusted D, adj D rank)
10 kenpom = dict((x[1], x) for x in kenpom)
11
12 #make sure we agree on all our team names
13 for r in bracket:
14 for t in bracket[r].values():
15 if type(t) == type([]):
16 for t2 in t:
17 assert(t2 in kenpom)
18 else:
19 assert(t in kenpom)
20
21 #let's build a dictionary {"region" -> {seed -> [name, pyth, adjo, adjd]}}
22 merged = {}
23 for region in ("Midwest", "West", "East", "South"):
24 merged[region] = {}
25 for seed, teamname in bracket[region].items():
26 if type(teamname) == type([]):
27 t1, t2 = teamname
28 rank, name, record, pyth, adjo, adjorank, adjd, adjdrank = kenpom[t1]
29 merged[region][16] = [name, pyth, adjo, adjd]
30 rank, name, record, pyth, adjo, adjorank, adjd, adjdrank = kenpom[t2]
31 merged[region][17] = [name, pyth, adjo, adjd]
32 else:
33 rank, name, record, pyth, adjo, adjorank, adjd, adjdrank = kenpom[teamname]
34 merged[region][seed] = [name, pyth, adjo, adjd]
35
36 class Game(object):
37 def __init__(self, region, round, gameno, seed1=None, team1=None, seed2=None, team2=None):
38 self.region = region
39 self.round = round
40 self.gameno = gameno
41 self.seed1 = seed1 or ""
42 self.team1 = team1 or [""]
43 self.seed2 = seed2 or ""
44 self.team2 = team2 or [""]
45 self.child = None
46 self.rows = [None, None]
47
48 def __repr__(self):
49 return "%s vs %s round %s game %s region %s rows %s" % (self.team1[0],
50 self.team2[0], self.round, self.gameno, self.region, self.rows)
51
52 games = []
53 game = 1
54 for region in merged:
55 for seed in (1,8,5,4,6,3,7,2):
56 oppseed = 17-seed
57 t1 = merged[region][seed]
58 t2 = merged[region][oppseed]
59 games.append(Game(region, 1, game, seed, t1, oppseed, t2))
60 game += 1
61
62 for round in (2,3,4):
63 i = iter(g for g in games if g.round==round-1)
64 roundg = []
65 for g in i:
66 g2 = i.next()
67 gg = Game(g.region, round, game)
68 roundg.append(gg)
69 g.child = g2.child = gg
70 game += 1
71 games.extend(roundg)
72
73 rf1, rf2 = [g for g in games if g.round==4 and g.region in ("Midwest", "West")]
74 rf3, rf4 = [g for g in games if g.round==4 and g.region in ("East", "South")]
75 ff1 = Game("final four", 5, game)
76 ff2 = Game("final four", 5, game+1)
77 games.append(ff1)
78 games.append(ff2)
79 rf1.child = rf2.child = ff1
80 rf3.child = rf4.child = ff2
81
82 c = Game("final four", 6, game+2)
83 games.append(c)
84 ff1.child = ff2.child = c
85
86 winner = Game("final four", 7, game+3)
87 games.append(winner)
88 c.child = winner
89
90 class Table(object):
91 def __init__(self):
92 self.data = [[]]
93
94 def __getitem__(self, (x,y)):
95 return self.data[y][x]
96
97 def __setitem__(self, (x,y), val):
98 try:
99 self.data[y][x] = val
100 except IndexError:
101 self.resize(x, y)
102 self.data[y][x] = val
103 except:
104 print y, x
105 raise
106
107 #we're fine with *horrible* efficiency in this particular app
108 def resize(self, x, y):
109 for i in range(y - len(self.data) + 1):
110 self.data.append([])
111 for row in self.data:
112 for i in range(x - len(row) + 1):
113 row.append(None)
114
115 def __repr__(self):
116 return self.data.__repr__()
117
118 def tableize(self):
119 o = []
120 for y, row in enumerate(self.data):
121 o.append("<tr>")
122 for x, elt in enumerate(row):
123 if x == 0:
124 #TODO: 16v17
125 pass
126 elif not elt: o.append('<td class="none"></td>')
127 elif elt.rows[0] == y:
128 if x == 1:
129 o.append('''<td class="top" width="200">%s. <span class="round-%s game-%s"
130 >%s (%s, %s, %s)</span></td>''' % (
131 elt.seed1, elt.round, elt.gameno, elt.team1[0], elt.team1[1],
132 elt.team1[2], elt.team1[3]))
133 else:
134 o.append('''<td class="top" width="180">&nbsp;<span
135 class="round-%s game-%s"></span>''' % (
136 elt.round, elt.gameno))
137 elif elt.rows[1] == y:
138 if x in (0,1):
139 o.append('''<td class="bottom">%s. <span class="round-%s game-%s"
140 >%s (%s, %s, %s)</span></td>''' % (
141 elt.seed2, elt.round, elt.gameno, elt.team2[0], elt.team2[1],
142 elt.team2[2], elt.team2[3]))
143 else:
144 o.append('''<td class="bottom">&nbsp;<span class="round-%s game-%s"></span>''' % (
145 elt.round, elt.gameno))
146 else:
147 o.append('<td class="middle">&nbsp;</td>')
148 return '\n'.join(o)
149
150 t = Table()
151
152 #insert the games into the table in the proper spot
153 row = 0
154 for region in ("Midwest", "West", "East", "South"):
155 for g in [x for x in games if x.round==1 and x.region==region]:
156 g.rows = [row, row+2]
157 t[1, row] = g
158 t[1, row+1] = g
159 t[1, row+2] = g
160 g.child.rows[1 if g.child.rows[0] else 0] = row+1
161 row += 3
162
163 for round in (2,3,4):
164 for g in [x for x in games if x.round==round and x.region==region]:
165 miny, maxy = g.rows
166 for i in range(0, maxy - miny + 1):
167 t[round, miny + i] = g
168 g.child.rows[1 if g.child.rows[0] else 0] = miny + int(ceil(float(maxy-miny)/2))
169
170 ff1.rows = [12, 36]
171 for i in range(12,37): t[5, i] = ff1
172 ff2.rows = [60, 84]
173 for i in range(60,85): t[5, i] = ff2
174 c.rows = [25, 72]
175 for i in range(25,73): t[6, i] = c
176 winner.rows = [50, 51]
177 t[7, 50] = winner
178
179 out = file("out.html", "w")
180 #TODO: if you change a team in an early game, update later games
181 #TODO: color teams based on pythagorean diff
182 #TODO: better randomizer
183 out.write("""
184 <html><head>
185 <script src="jquery-1.3.2.min.js" type="text/javascript"></script>
186 <script>
187 rounds = {0:0, 1: 32, 2: 48, 3:56, 4:60, 5:62, 6:63};
188
189 function handleClick(that) {
190 var atts = that.attr("class");
191 var game = parseFloat(atts.match(/game-(\d+)/)[1]);
192 var round = parseFloat(atts.match(/round-(\d+)/)[1]);
193 var game_parity = (game - rounds[round-1]) % 2 == 0 ? 1 : 0;
194 if (round < 4) {
195 var nextgame = (Math.ceil((game-rounds[round-1])/2) + rounds[round]).toString();
196 var firstorlast = game_parity ? ":last" : ":first";
197 }
198 else {
199 if (game == 59 || game==57) {
200 var nextgame = 61;
201 var firstorlast = game == 59 ? ":first" : ":last";
202 }
203 else if (game==58 || game==60) {
204 var nextgame = 62;
205 var firstorlast = game == 58 ? ":first" : ":last";
206 }
207 else if (game == 61 || game == 62) {
208 var nextgame = 63;
209 var firstorlast = game == 61 ? ":first" : ":last";
210 }
211 else if (game == 63) {
212 var nextgame = 64;
213 var firstorlast = ":first";
214 }
215 }
216 that.click(function() {
217 //console.log("game, round, nextgame, (g-r[r-1]) ", game, round, nextgame, game - rounds[round-1], firstorlast);
218 $(".game-" + nextgame + firstorlast).html(that.html());
219 });
220 }
221
222 function randomize() {
223 for (i=0; i < 7; i++) {
224 $("td.top > span.round-" + i).each(function(_) {
225 var that = $(this);
226 var atts = that.attr("class");
227 var game = atts.match(/game-\d+/)[0];
228 var opp = $("." + game + ":last");
229 function parsepoints(obj) {
230 p = obj.html().match(/(.*?) \((.\d+), (\d+\.\d+), (\d+\.\d+)/);
231 return [p[1], parseFloat(p[2]), parseFloat(p[3]), parseFloat(p[4])]
232 }
233 var topp = parsepoints(that);
234 var oppp = parsepoints(opp);
235 var a = topp[1];
236 var b = oppp[1];
237
238 //use the log5 formula
239 var log5 = (a - a * b) / (a + b - 2 * a * b);
240
241 if (log5 > .5) {
242 that.css("color", "#00" + parseInt(255 * log5).toString(16) + "00");
243 opp.css("color", "#" + parseInt(255 * log5).toString(16) + "0000");
244 } else {
245 opp.css("color", "#00" + parseInt(255 * log5).toString(16) + "00");
246 that.css("color", "#" + parseInt(255 * log5).toString(16) + "0000");
247 }
248
249 console.log(topp[0] + " vs " + oppp[0] + " %: ", log5);
250
251 log5 > Math.random() ? that.click() : opp.click();
252 });
253 }
254 }
255
256 $(document).ready(function() {
257 for (i=0; i < 8; i++) {
258 $(".round-" + i).each(function(i) { handleClick($(this)); });
259 }
260 $("td.top > span.round-1").each(function(i) { $(this).parent().css("height", "30px").css("vertical-align", "bottom"); })
261 $("#randomize").click(function() { randomize(); });
262 });
263 </script>
264 <style>
265 .top { border-bottom: 1px solid #aaaaaa; padding: 0px 5px 0px 5px; }
266 .bottom { border-bottom: 1px solid #aaaaaa; border-right: 1px solid #aaaaaa; padding: 0px 5px 0px 5px; }
267 .middle { border-right: 1px solid #aaaaaa; padding: 0px 5px 0px 5px; }
268 tr { padding-bottom: 10px; font-size: 12px; }
269 span { cursor: pointer; }
270 </style>
271 </head>
272 <body><input type="submit" id="randomize" value="randomize"><table cellspacing=0 width=1200 style='table-layout:fixed'>
273 """)
274 out.write(t.tableize())
275 out.write("</table></body></html>")
276 out.close()