Represents a NCAA tournament pool. Contains 4 regions of 16 teams each. Champions of Region 1 and Region 2 and champions of Region 3 and Region 4 play each other in the final four.

Methods
Attributes
[R] entries
[RW] entry_fee
[R] payouts
[R] regions
Public Class methods
ncaa_2008()

Creates a Pool object for the 2008 NCAA tournament

     # File lib/tournament/pool.rb, line 83
 83:   def self.ncaa_2008
 84:     pool = Tournament::Pool.new
 85:     pool.add_region("East",
 86:       [
 87:         Tournament::Team.new('North Carolina', 'UNC', 1),
 88:         Tournament::Team.new('Mt. St. Mary\'s', 'MSM', 16),
 89:         Tournament::Team.new('Indiana', 'Ind', 8),
 90:         Tournament::Team.new('Arkansas', 'Ark', 9),
 91:         Tournament::Team.new('Notre Dame', 'ND', 5),
 92:         Tournament::Team.new('George Mason', 'GM', 12),
 93:         Tournament::Team.new('Washington St.', 'WSt', 4),
 94:         Tournament::Team.new('Winthrop', 'Win', 13),
 95:         Tournament::Team.new('Oklahoma', 'Okl', 6),
 96:         Tournament::Team.new('St. Joseph\'s', 'StJ', 11),
 97:         Tournament::Team.new('Louisville', 'Lou', 3),
 98:         Tournament::Team.new('Boise St.', 'BSt', 14),
 99:         Tournament::Team.new('Butler', 'But', 7),
100:         Tournament::Team.new('South Alabama', 'SAl', 10),
101:         Tournament::Team.new('Tennessee', 'Ten', 2),
102:         Tournament::Team.new('American', 'Am', 15)
103:       ],
104:       0
105:     )
106:     pool.add_region("Midwest",
107:       [
108:         Tournament::Team.new('Kansas', 'Kan', 1),
109:         Tournament::Team.new('Portland St.', 'PSt', 16),
110:         Tournament::Team.new('UNLV', 'ULV', 8),
111:         Tournament::Team.new('Kent St.', 'KSt', 9),
112:         Tournament::Team.new('Clemson', 'Clm', 5),
113:         Tournament::Team.new('Villanova', 'Vil', 12),
114:         Tournament::Team.new('Vanderbilt', 'Van', 4),
115:         Tournament::Team.new('Siena', 'Sie', 13),
116:         Tournament::Team.new('USC', 'USC', 6),
117:         Tournament::Team.new('Kansas St.', 'KSU', 11),
118:         Tournament::Team.new('Wisconsin', 'Wis', 3),
119:         Tournament::Team.new('CSU Fullerton', 'CSF', 14),
120:         Tournament::Team.new('Gonzaga', 'Gon', 7),
121:         Tournament::Team.new('Davidson', 'Dav', 10),
122:         Tournament::Team.new('Georgetown', 'GT', 2),
123:         Tournament::Team.new('UMBC', 'UBC', 15)
124:       ],
125:       1
126:     )
127:     pool.add_region("South",
128:       [
129:         Tournament::Team.new('Memphis', 'Mem', 1),
130:         Tournament::Team.new('TX Arlington', 'TxA', 16),
131:         Tournament::Team.new('Mississippi St.', 'MiS', 8),
132:         Tournament::Team.new('Oregon', 'Ore', 9),
133:         Tournament::Team.new('Michigan St.', 'MSU', 5),
134:         Tournament::Team.new('Temple', 'Tem', 12),
135:         Tournament::Team.new('Pittsburgh', 'Pit', 4),
136:         Tournament::Team.new('Oral Roberts', 'ORo', 13),
137:         Tournament::Team.new('Marquette', 'Mar', 6),
138:         Tournament::Team.new('Kentucky', 'Ken', 11),
139:         Tournament::Team.new('Stanford', 'Sta', 3),
140:         Tournament::Team.new('Cornell', 'Cor', 14),
141:         Tournament::Team.new('Miami (FL)', 'Mia', 7),
142:         Tournament::Team.new('St. Mary\'s', 'StM', 10),
143:         Tournament::Team.new('Texas', 'Tex', 2),
144:         Tournament::Team.new('Austin Peay', 'APe', 15)
145:       ],
146:       2
147:     )
148:     pool.add_region("West",
149:       [
150:         Tournament::Team.new('UCLA', 'ULA', 1),
151:         Tournament::Team.new('Mis. Valley St', 'MVS', 16),
152:         Tournament::Team.new('BYU', 'BYU', 8),
153:         Tournament::Team.new('Texas A&M', 'A&M', 9),
154:         Tournament::Team.new('Drake', 'Dra', 5),
155:         Tournament::Team.new('W. Kentucky', 'WKy', 12),
156:         Tournament::Team.new('Connecticut', 'Con', 4),
157:         Tournament::Team.new('San Diego', 'SD', 13),
158:         Tournament::Team.new('Purdue', 'Pur', 6),
159:         Tournament::Team.new('Baylor', 'Bay', 11),
160:         Tournament::Team.new('Xavier', 'Xav', 3),
161:         Tournament::Team.new('Georgia', 'UG', 14),
162:         Tournament::Team.new('West Virginia', 'WVa', 7),
163:         Tournament::Team.new('Arizona', 'UA', 10),
164:         Tournament::Team.new('Duke', 'Duk', 2),
165:         Tournament::Team.new('Belmont', 'Bel', 15)
166:       ],
167:       3
168:     )
169:     return pool
170:   end
new()

Create a new empty pool with no Regions or Entries

    # File lib/tournament/pool.rb, line 12
12:   def initialize
13:     @regions = Array.new(4)
14:     @entries = []
15:     @payouts = {}
16:   end
split_line(str, len)

Splits str on space chars in chunks of around len size

     # File lib/tournament/pool.rb, line 268
268:   def self.split_line(str, len)
269:     new_str = []
270:     beg_idx = 0
271:     end_idx = len - 1
272:     while end_idx < str.length
273:       end_idx += 1 while end_idx < (str.length - 1) && str[end_idx].chr != ' '
274:       new_str << str[beg_idx,(end_idx-beg_idx+1)].strip
275:       beg_idx = end_idx + 1
276:       end_idx += len
277:     end
278:     new_str << str[beg_idx,str.length-1]
279:     return new_str.reject {|s| s.nil? || s.length == 0}  
280:   end
test(num_picks = 20)

Run a test pool with random entries and a random outcome.

     # File lib/tournament/pool.rb, line 173
173:   def self.test(num_picks = 20)
174:     pool = ncaa_2008
175:     pool.entry_fee = 10
176:     pool.set_payout(1, 70)
177:     pool.set_payout(2, 20)
178:     pool.set_payout(3, 10)
179:     pool.set_payout(:last, -10)
180:     b = pool.bracket
181:     b.scoring_strategy = Tournament::Bracket::UpsetScoringStrategy.new
182:     picks = (1..num_picks).map {|n| Tournament::Bracket.random_bracket(b.teams)}
183:     # Play out the bracket
184:     32.times { |n| b.set_winner(1,n+1, b.matchup(1, n+1)[rand(2)])}
185:     16.times { |n| b.set_winner(2,n+1, b.matchup(2, n+1)[rand(2)])}
186:     8.times { |n| b.set_winner(3,n+1, b.matchup(3, n+1)[rand(2)])}
187:     4.times { |n| b.set_winner(4,n+1, b.matchup(4, n+1)[rand(2)])}
188:     #2.times { |n| b.set_winner(5,n+1, b.matchup(5, n+1)[rand(2)])}
189:     #1.times { |n| b.set_winner(6,n+1, b.matchup(6, n+1)[rand(2)])}
190:     picks.each_with_index {|p, idx| pool.add_entry Tournament::Entry.new("picker_#{idx}", p) }
191:     picks.each_with_index do |p, idx|
192:       puts "Score #{idx+1}: #{p.score_against(b)}"
193:     end
194:     pool.region_report
195:     pool.leader_report
196:     pool.final_four_report
197:     pool.possibility_report
198:     pool.entry_report
199:     pool.score_report
200:   end
Public Instance methods
add_entry(entry)

Add an Tournament::Entry object to the pool

    # File lib/tournament/pool.rb, line 28
28:   def add_entry(entry)
29:     @entries << entry
30:   end
add_entry_yaml(yaml)

Add an Tournament::Entry object to the pool after reading the Tournament::Entry from the provided YAML file.

    # File lib/tournament/pool.rb, line 34
34:   def add_entry_yaml(yaml)
35:     @entries << YAML::load_file(yaml)
36:   end
add_region(name, teams, index)

add regions to the pool. Champ of region with index = 0 plays region with index = 1 and index == 2 plays index == 3.

    # File lib/tournament/pool.rb, line 20
20:   def add_region(name, teams, index)
21:     @regions[index] = {
22:       :name => name,
23:       :teams => teams
24:     }
25:   end
bracket()

Creates a bracket for the pool by combining all the regions into one bracket of 64 teams. By default the bracket uses the basic scoring strategy.

    # File lib/tournament/pool.rb, line 62
62:   def bracket
63:     unless @bracket
64:       if @regions.compact.size != 4
65:         raise "Not all regions have been set."
66:       end
67:       all_teams = @regions.map do |region|
68:         region[:teams]
69:       end
70:       all_teams = all_teams.flatten
71:       @bracket = Tournament::Bracket.new(all_teams)
72:     end
73:     return @bracket
74:   end
bracket=(new_bracket)

Replaces the pool‘s bracket (as in after updating the bracket for game results)

    # File lib/tournament/pool.rb, line 78
78:   def bracket=(new_bracket)
79:     @bracket = new_bracket
80:   end
entry_report()

Shows a report of each entry‘s picks by round.

     # File lib/tournament/pool.rb, line 283
283:   def entry_report
284:     puts "There are #{@entries.size} entries."
285:     if @entries.size > 0
286:       puts "".center(15) + "|" + "First Round".center(128)
287:       puts "Name".center(15) + "|" + "Sweet 16".center(64) + "|" + "Elite 8".center(32) +
288:         "|" + "Final 4".center(16) + "|" + "Final 2".center(8) + "|" + "Champion".center(15) +
289:         "|" + "Tie Break"
290:       puts ("-" * 15) + "+" + ("-" * 64) + "+" + ("-" * 32) +
291:         "+" + ("-" * 16) + "+" + ("-" * 8) + "+" + ("-" * 15) +
292:         "+" + ("-" * 10)
293:       output = Proc.new do |name, bracket, tie_breaker|
294:         first_round = bracket.winners[1].map {|t| "%s" % (t.short_name rescue 'Unk')}.join('-')
295:         sweet_16 = bracket.winners[2].map {|t| "%s" % (t.short_name rescue 'Unk')}.join('-')
296:         elite_8 = bracket.winners[3].map {|t| "%s" % (t.short_name rescue 'Unk')}.join('-')
297:         final_4 = bracket.winners[4].map {|t| "%s" % (t.short_name rescue 'Unk')}.join('-')
298:         final_2 = bracket.winners[5].map {|t| "%s" % (t.short_name rescue 'Unk')}.join('-')
299:         champ = bracket.champion.name rescue 'Unk'
300:         puts "               |%128s" % first_round
301:         puts "%15s|%64s|%32s|%16s|%8s|%15s|%s" %
302:           [name, sweet_16, elite_8, final_4, final_2, champ, tie_breaker.to_s] 
303:         puts ("-" * 15) + "+" + ("-" * 64) + "+" + ("-" * 32) +
304:           "+" + ("-" * 16) + "+" + ("-" * 8) + "+" + ("-" * 15) +
305:           "+" + ("-" * 10)
306:       end
307: 
308:       output.call('Tournament', bracket, '-')
309: 
310:       @entries.sort_by{|e| e.name}.each do |entry|
311:         output.call(entry.name, entry.picks, entry.tie_breaker)
312:       end
313:     end
314:   end
final_four_report()

When there are four teams left, for each of the 16 possible outcomes shows who will win according to the configured payouts.

     # File lib/tournament/pool.rb, line 335
335:   def final_four_report
336:     if @entries.size == 0
337:       puts "There are no entries in the pool."
338:       return
339:     end
340:     if self.bracket.teams_left != 4
341:       puts "The final four report should only be run when there"
342:       puts "are exactly four teams left in the tournament."
343:       return
344:     end
345:     total_payout = @entries.size * @entry_fee
346:     # Subtract out constant payments
347:     total_payout = @payouts.values.inject(total_payout) {|t, amount| t += amount if amount < 0; t}
348: 
349:     payout_keys = @payouts.keys.sort do |a,b|
350:       if Symbol === a
351:         1
352:       elsif Symbol === b
353:         -1
354:       else
355:         a <=> b
356:       end
357:     end
358: 
359:     print "Final Four: #{self.bracket.winners[4][0,2].map{|t| "(#{t.seed}) #{t.name}"}.join(" vs. ")}"
360:     puts "    #{self.bracket.winners[4][2,2].map{|t| "(#{t.seed}) #{t.name}"}.join(" vs. ")}"
361:     puts "Payouts"
362:     payout_keys.each do |key|
363:       amount = if @payouts[key] > 0
364:         @payouts[key].to_f / 100.0 * total_payout
365:       else
366:         -@payouts[key]
367:       end
368:       puts "%4s: $%5.2f" % [key, amount]
369:     end
370:     sep= "--------------+----------------+-----------------------------------------"
371:     puts "              |                | Winners      Tie    "
372:     puts " Championship |    Champion    | Rank Score Break Name"
373:     puts sep
374:     self.bracket.each_possible_bracket do |poss|
375:       rankings = @entries.map{|p| [p, p.picks.score_against(poss)] }.sort_by {|arr| -arr[1] }
376:       finishers = {}
377:       @payouts.each do |rank, payout|
378:         finishers[rank] = {}
379:         finishers[rank][:payout] = payout
380:         finishers[rank][:entries] = []
381:         finishers[rank][:score] = 0
382:       end
383:       #puts "Got finishers: #{finishers.inspect}"
384:       index = 0
385:       rank = 1
386:       while index < @entries.size
387:         rank_score = rankings[index][1]
388:         finishers_key = index < (@entries.size - 1) ? rank : :last
389:         finish_hash = finishers[finishers_key]
390:         #puts "For rank_score = #{rank_score} finishers key = #{finishers_key.inspect}, hash = #{finish_hash}, index = #{index}"
391:         if finish_hash
392:           while index < @entries.size && rankings[index][1] == rank_score
393:             finish_hash[:entries] << rankings[index][0]
394:             finish_hash[:score] = rank_score
395:             index += 1
396:           end
397:           rank += 1
398:           next
399:         end
400:         index += 1
401:         rank += 1
402:       end
403: 
404:       num_payouts = payout_keys.size
405: 
406:       first_line = true
407:       showed_last = false
408:       payout_count = 0
409:       while payout_count < num_payouts
410:         rank = payout_keys[payout_count]
411:         finish_hash = finishers[rank]
412:         label = finish_hash[:entries].size == 1 ? "#{rank}".upcase : "TIE"
413:         finish_hash[:entries].each do |winner| 
414:           line = if first_line
415:             "%14s|%16s| %4s %5d %5d %s" % [
416:               poss.winners[5].map{|t| t.short_name}.join("-"),
417:               poss.champion.name,
418:               label,
419:               finish_hash[:score],
420:               winner.tie_breaker,
421:               winner.name
422:             ]
423:           else
424:             "%14s|%16s| %4s %5d %5d %s" % [
425:               '',
426:               '',
427:               label,
428:               finish_hash[:score],
429:               winner.tie_breaker,
430:               winner.name
431:             ]
432:           end
433:           puts line
434:           first_line = false
435:         end
436:         payout_count += finish_hash[:entries].size
437:         showed_last = (rank == :last)
438:         if payout_count >= num_payouts && !showed_last
439:           if payout_keys[num_payouts-1] == :last
440:             payout_count -= 1
441:             showed_last = true
442:           end
443:         end
444:       end
445:       puts sep
446:     end
447:     nil
448:   end
leader_report()

Generate the leader board report. Shows each entry sorted by current score and gives a breakdown of score by round.

     # File lib/tournament/pool.rb, line 204
204:   def leader_report
205:     puts "Total games played: #{@bracket.games_played}"
206:     puts "Number of entries: #{@entries.size}"
207:     if @entries.size > 0
208:       puts " Curr| Max |               |Champ| Round Scores"
209:       puts "Score|Score|      Name     |Live?|" + (1..bracket.rounds).to_a.map{|r| "%3d" % r}.join(" ")
210:       sep ="-----+-----+---------------+-----+" + ("-" * 4 * bracket.rounds)
211:       puts sep
212:       @entries.sort_by {|e| -e.picks.score_against(bracket)}.each do |entry|
213:         total = entry.picks.score_against(bracket)
214:         max = entry.picks.maximum_score(bracket)
215:         champ = entry.picks.champion
216:         round_scores = []
217:         1.upto(bracket.rounds) do |round|
218:           scores = entry.picks.scores_for_round(round, bracket)
219:           round_scores << scores.inject(0) {|sum, arr| sum += (arr[0] ? arr[0] : 0)}
220:         end
221:         puts "%5d|%5d|%15s|%3s %1s|%s" % [total, max, entry.name,
222:           champ.short_name,(bracket.still_alive?(champ) ? 'Y' : 'N'), round_scores.map {|s| "%3d" % s}.join(" ")]
223:       end
224:       puts sep
225:     end
226:   end
possibility_report()

Runs through every possible outcome of the tournament and calculates each entry‘s chance to win as a percentage of the possible outcomes the entry would win if the tournament came out that way.

     # File lib/tournament/pool.rb, line 453
453:   def possibility_report
454:     $stdout.sync = true
455:     if @entries.size == 0
456:       puts "There are no entries in the pool."
457:       return
458:     end
459:     max_possible_score = @entries.map{|p| 0}
460:     min_ranking = @entries.map{|p| @entries.size + 1}
461:     times_winner = @entries.map{|p| 0 }
462:     player_champions = @entries.map{|p| Hash.new {|h,k| h[k] = 0} }
463:     count = 0
464:     old_percentage = -1 
465:     old_remaining = 1_000_000_000_000
466:     puts "Checking #{self.bracket.number_of_outcomes} possible outcomes"
467:     start = Time.now.to_f
468:     self.bracket.each_possible_bracket do |poss|
469:       poss_scores = @entries.map{|p| p.picks.score_against(poss)}
470:       sort_scores = poss_scores.sort.reverse
471:       @entries.each_with_index do |entry, i|
472:         score = poss_scores[i]
473:         max_possible_score[i] = score if score > max_possible_score[i]
474:         rank = sort_scores.index(score) + 1
475:         min_ranking[i] = rank if rank < min_ranking[i]
476:         times_winner[i] += 1 if rank == 1
477:         if rank == 1
478:           player_champions[i][poss.champion] += 1
479:         end
480:       end
481:       count += 1
482:       percentage = (count * 100.0 / self.bracket.number_of_outcomes)
483:       elapsed = Time.now.to_f - start
484:       spp = elapsed / count
485:       remaining = ((self.bracket.number_of_outcomes - count) * spp).to_i
486:       if (percentage.to_i != old_percentage) || (remaining < old_remaining)
487:         old_remaining = remaining
488:         old_percentage = percentage.to_i
489:         hashes = '#' * (percentage.to_i/5) + '>'
490:         print "\rCalculating: %3d%% +#{hashes.ljust(20, '-')}+ %5d seconds remaining" % [percentage.to_i, remaining]
491:       end
492:     end
493:     puts "\n"
494:     #puts "\n   Max Scores: #{max_possible_score.inspect}"
495:     #puts "Highest Place: #{min_ranking.inspect}"
496:     #puts " Times Winner: #{times_winner.inspect}"
497:     sort_array = []
498:     times_winner.each_with_index { |n, i| sort_array << [n, i, min_ranking[i], max_possible_score[i], player_champions[i]] }
499:     sort_array = sort_array.sort_by {|arr| arr[0] == 0 ? (arr[2] == 0 ? -arr[3] : arr[2]) : -arr[0]}
500:     #puts "SORT: #{sort_array.inspect}"
501:     puts "    Entry           | Win Chance | Highest Place | Max Score | Tie Break  "
502:     puts "--------------------+------------+---------------+-----------+------------"
503:     sort_array.each do |arr|
504:       chance = arr[0].to_f * 100.0 / self.bracket.number_of_outcomes
505:       puts "%19s | %10.2f | %13d | %9d | %7d " %
506:         [@entries[arr[1]].name, chance, min_ranking[arr[1]], max_possible_score[arr[1]], @entries[arr[1]].tie_breaker]
507:     end
508:     puts "Possible Champions For Win"
509:     puts "    Entry           |    Champion     |  Ocurrences   |  Chance "
510:     puts "--------------------+-----------------+---------------+---------"
511:     sort_array.each do |arr|
512:       next if arr[4].size == 0
513:       arr[4].sort_by{|k,v| -v}.each_with_index do |harr, idx| 
514:         team = harr[0]
515:         occurences = harr[1]
516:         if idx == 0
517:           puts "%19s | %15s | %13d | %8.2f " % [@entries[arr[1]].name, team.name, occurences, occurences.to_f * 100.0 / arr[0]]
518:         else
519:           puts "%19s | %15s | %13d | %8.2f " % ['', team.name, occurences, occurences.to_f * 100.0 / arr[0]]
520:         end
521:       end
522:       puts "--------------------+-----------------+---------------+---------"
523:     end
524:     nil
525:   end
region_report()

Displays the regions and teams in the region.

     # File lib/tournament/pool.rb, line 317
317:   def region_report
318:     puts " Region | Seed | Team               "
319:     current_idx = -1
320:     @regions.each_with_index do |region, idx|
321:       region[:teams].each do |team|
322:         region_name = ''
323:         if idx != current_idx
324:           region_name =  region[:name]
325:           current_idx = idx
326:           puts "--------+------+-------------------------"
327:         end
328:         puts "%8s|%6d|%25s" % [region_name, team.seed, "#{team.name} (#{team.short_name})"]
329:       end
330:     end
331:   end
remove_by_name(name)

Remove an entry by name from the pool

    # File lib/tournament/pool.rb, line 39
39:   def remove_by_name(name)
40:     entry = @entries.find {|e| e.name == name}
41:     if !entry.nil?
42:       @entries.delete(entry)
43:     end
44:   end
score_report()

Shows detailed scores per entry. For each pick in each game, shows either a positive amount if the pick was correct, 0 if the pick was incorrect, or a ’?’ if the game has not yet been played.

     # File lib/tournament/pool.rb, line 231
231:   def score_report
232:     # Compute scores
233:     puts "Total games played: #{@bracket.games_played}"
234:     puts "Number of entries: #{@entries.size}"
235:     sep = "-----+---------------+----------------------------------------------------------------------------------"
236:     if @entries.size > 0
237:       puts "Total|      Name     | Round Scores"
238:       puts sep
239:       fmt1 = "%5d|%15s|%d: %3d %s" 
240:       fmt2 = "     |               |%d: %3d %s" 
241:       fmt3 = "     |               |       %s" 
242:       @entries.sort_by {|e| -e.picks.score_against(bracket)}.each do |entry|
243:         total = entry.picks.score_against(bracket)
244:         1.upto(bracket.rounds) do |round|
245:           scores = entry.picks.scores_for_round(round, bracket)
246:           round_total = scores.inject(0) {|sum, arr| sum += (arr[0] ? arr[0] : 0)}
247:           scores_str = scores.map{|arr| "#{arr[1].short_name}=#{arr[0] ? arr[0] : '?'}"}.join(" ")
248:           if [1,2].include?(round)
249:             scores_str_arr = Tournament::Pool.split_line(scores_str, 70)
250:             if round == 1
251:               puts fmt1 % [total, entry.name, round, round_total, scores_str_arr[0]]
252:             else
253:               puts fmt2 % [round, round_total, scores_str_arr[0]]
254:             end
255:             scores_str_arr[1..-1].each do |scores_str|
256:               puts fmt3 % scores_str
257:             end
258:           else
259:             puts fmt2 % [round, round_total, scores_str]
260:           end
261:         end
262:         puts sep
263:       end
264:     end
265:   end
set_payout(rank, payout)

Set a payout. Takes a rank (or the symbol :last for last place), along with the payout. The payout may be a positive integer, in which case, it represents a percentage of the the total entry fees that particular rank would receive. The payout may also be a negative integer, in which case, it represents a constant payout amount.

    # File lib/tournament/pool.rb, line 53
53:   def set_payout(rank, payout)
54:     # FIXME: Add error checking
55:     @payouts ||= {}
56:     @payouts[rank] = payout
57:   end