llimllib / ncaa-bracket-randomizer
- Source
- Commits
- Network (0)
- Issues (0)
- Downloads (0)
- Wiki (1)
- Graphs
-
Tree:
1db753c
ncaa-bracket-randomizer / bracket.py
| 1db753ce » | llimllib | 2009-03-18 | 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"> <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"> <span class="round-%s game-%s"></span>''' % ( | ||||
| 145 | elt.round, elt.gameno)) | ||||
| 146 | else: | ||||
| 147 | o.append('<td class="middle"> </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() | ||||
