Skip to content
This repository
Browse code

Adds new Bracket::Base and subclasses

BracketTree::Bracket is no longer a class. Instead, BracketTree::Bracket::Base holds
the basic data, and new bracket types are subclasses of that. This allows flexibility
of template selection, as well as extending functionality in the future for tournament
types (rounds, losers/winners brackets, rematch, etc).

Because of this, BracketTree::Template::Base#generate_blank_bracket has been removed.
The corresponding BracketTree::Bracket::Base#generate_from_template method has been
added.
  • Loading branch information...
commit 99cc8ec818fdc843f0cfade4292b4f892774c2bf 1 parent b54067e
Andrew Nordman cadwallion authored
174 lib/bracket_tree/bracket.rb
... ... @@ -1,176 +1,12 @@
1 1 require 'bracket_tree/node'
2   -
  2 +require 'bracket_tree/bracket/base'
3 3 module BracketTree
4   - class Bracket
  4 + module Bracket
5 5 class NoSeedOrderError < Exception ; end
6 6 class SeedLimitExceededError < Exception ; end
7 7
8   - include Enumerable
9   - attr_accessor :root, :seed_order, :matches, :insertion_order
10   -
11   - def initialize options = {}
12   - @insertion_order = []
13   - @matches = []
14   -
15   - if options[:matches]
16   - options[:matches].each do |m|
17   - @matches << Match.new(m)
18   - end
19   - end
20   - end
21   -
22   - # Adds a Node at the given position, setting the data as the payload. Maps to
23   - # binary tree under the hood. The `data` can be any serializable object.
24   - #
25   - # @param Fixnum position - Seat position to add
26   - # @param Object data - the player object to store in the Seat position
27   - def add position, data
28   - node = Node.new position, data
29   - @insertion_order << position
30   -
31   - if @root.nil?
32   - @root = node
33   - else
34   - current = @root
35   - loop do
36   - if node.position < current.position
37   - if current.left.nil?
38   - current.left = node
39   - break
40   - else
41   - current = current.left
42   - end
43   - elsif node.position > current.position
44   - if current.right.nil?
45   - current.right = node
46   - break
47   - else
48   - current = current.right
49   - end
50   - else
51   - break
52   - end
53   - end
54   - end
55   - end
56   -
57   - # Replaces the data at a given node position with new payload. This is useful
58   - # for updating bracket data, replacing placeholders with actual data, seeding,
59   - # etc..
60   - #
61   - # @param [Fixnum] position - the node position to replace
62   - # @param payload - the new payload object to replace
63   - def replace position, payload
64   - node = at position
65   - if node
66   - node.payload = payload
67   - true
68   - else
69   - nil
70   - end
71   - end
72   -
73   - # Seeds bracket based on `seed_order` value of bracket. Provide an iterator
74   - # with players that will be inserted in the appropriate location. Will raise a
75   - # SeedLimitExceededError if too many players are sent, and a NoSeedOrderError if
76   - # the `seed_order` attribute is nil
77   - #
78   - # @param [Enumerable] players - players to be seeded
79   - def seed players
80   - if @seed_order.nil?
81   - raise NoSeedOrderError, 'Bracket does not have a seed order.'
82   - elsif players.size > @seed_order.size
83   - raise SeedLimitExceededError, 'cannot seed more players than seed order list.'
84   - else
85   - @seed_order.each do |position|
86   - replace position, players.shift
87   - end
88   - end
89   - end
90   -
91   - def winner
92   - @root.payload
93   - end
94   -
95   - def each(&block)
96   - in_order(@root, block)
97   - end
98   -
99   - def to_h
100   - @root.to_h
101   - end
102   -
103   - # Array of Seats mapping to the individual positions of the bracket tree. The
104   - # order of the nodes is important, as insertion in this order maintains the
105   - # binary tree
106   - #
107   - # @return Array seats
108   - def seats
109   - entries.sort_by { |node| @insertion_order.index(node.position) }
110   - end
111   -
112   - alias_method :to_a, :seats
113   -
114   - def at position
115   - find { |n| n.position == position }
116   - end
117   -
118   - alias_method :size, :count
119   -
120   - # Progresses the bracket by using the stored `matches` to copy data to the winning
121   - # and losing seats. This facilitates match progression without manually
122   - # manipulating bracket positions
123   - #
124   - # @param Fixnum seat - winning seat position
125   - # @return Boolean result - result of progression
126   - def match_winner seat
127   - match = @matches.find { |m| m.include? seat }
128   -
129   - if match
130   - losing_seat = match.seats.find { |s| s != seat }
131   -
132   - if match.winner_to
133   - replace match.winner_to, at(seat).payload
134   - end
135   -
136   - if match.loser_to
137   - replace match.loser_to, at(losing_seat).payload
138   - end
139   -
140   - return true
141   - else
142   - return false
143   - end
144   - end
145   -
146   - # Inverse of `match_winner`, progresses the bracket based on seat. See `match_winner`
147   - # for more details
148   - #
149   - # @param Fixnum seat - losing seat position
150   - # @return Boolean result - result of progression
151   - def match_loser seat
152   - match = @matches.find { |m| m.include? seat }
153   -
154   - if match
155   - winning_seat = match.seats.find { |s| s != seat }
156   - match_winner winning_seat
157   - else
158   - return false
159   - end
160   - end
161   -
162   - def in_order(node, block)
163   - if node
164   - unless node.left.nil?
165   - in_order(node.left, block)
166   - end
167   -
168   - block.call(node)
169   -
170   - unless node.right.nil?
171   - in_order(node.right, block)
172   - end
173   - end
174   - end
  8 + autoload :Base, 'bracket_tree/bracket/base'
  9 + autoload :SingleElimination, 'bracket_tree/bracket/single_elimination'
  10 + autoload :DoubleElimination, 'bracket_tree/bracket/double_elimination'
175 11 end
176 12 end
228 lib/bracket_tree/bracket/base.rb
... ... @@ -0,0 +1,228 @@
  1 +module BracketTree
  2 + module Bracket
  3 + # Basic bracketing functionality. If you wish to create a custom bracket type,
  4 + # inherit from this class to provide easy access to bracketing.
  5 + #
  6 + # Example:
  7 + # class MLGDouble < BracketTree::Bracket::Base
  8 + # template BracketTree::Template::DoubleElimination
  9 + # end
  10 + #
  11 + # bracket = MLGDouble.by_size 8
  12 + #
  13 + # This creates a bracket based on the standard double elimination template class.
  14 + # The template parameter can be any class that inherits from BracketTree::Template::Base,
  15 + # though.
  16 + #
  17 + # class MLGDoubleTemplate < BracketTree::Template::Base
  18 + # def self.location
  19 + # File.join File.dirname(__FILE__), 'templates', 'mlg_double'
  20 + # end
  21 + # end
  22 + #
  23 + # class MLGDouble < BracketTree::Bracket::Base
  24 + # template MLGDoubleTemplate
  25 + # end
  26 + class Base
  27 + class NoTemplateError < Exception ; end
  28 +
  29 + class << self
  30 + def by_size size, options = {}
  31 + generate_from_template @template, size
  32 + end
  33 +
  34 + def template class_name
  35 + @template = class_name
  36 + end
  37 +
  38 + # Generates a blank bracket object from the passed Template class for the
  39 + # passed size
  40 + #
  41 + # @param BracketTree::Template::Base template - the Template the bracket is
  42 + # based on
  43 + # @param Fixnum size - bracket size
  44 + # @return BracketTree::Bracket::Base bracket - a blank bracket with hash placeholders
  45 + def generate_from_template template, size
  46 + template = template.by_size size
  47 + bracket = new(matches: template.matches, seed_order: template.seed_order)
  48 +
  49 + template.seats.each do |position|
  50 + bracket.add position, {}
  51 + end
  52 +
  53 + bracket
  54 + end
  55 + end
  56 +
  57 + include Enumerable
  58 + attr_accessor :root, :seed_order, :matches, :insertion_order
  59 +
  60 + def initialize options = {}
  61 + @insertion_order = []
  62 + @matches = []
  63 +
  64 + if options[:matches]
  65 + options[:matches].each do |m|
  66 + @matches << Match.new(m)
  67 + end
  68 + end
  69 +
  70 + @seed_order = options[:seed_order] if options[:seed_order]
  71 + end
  72 +
  73 + # Adds a Node at the given position, setting the data as the payload. Maps to
  74 + # binary tree under the hood. The `data` can be any serializable object.
  75 + #
  76 + # @param Fixnum position - Seat position to add
  77 + # @param Object data - the player object to store in the Seat position
  78 + def add position, data
  79 + node = Node.new position, data
  80 + @insertion_order << position
  81 +
  82 + if @root.nil?
  83 + @root = node
  84 + else
  85 + current = @root
  86 + loop do
  87 + if node.position < current.position
  88 + if current.left.nil?
  89 + current.left = node
  90 + break
  91 + else
  92 + current = current.left
  93 + end
  94 + elsif node.position > current.position
  95 + if current.right.nil?
  96 + current.right = node
  97 + break
  98 + else
  99 + current = current.right
  100 + end
  101 + else
  102 + break
  103 + end
  104 + end
  105 + end
  106 + end
  107 +
  108 + # Replaces the data at a given node position with new payload. This is useful
  109 + # for updating bracket data, replacing placeholders with actual data, seeding,
  110 + # etc..
  111 + #
  112 + # @param [Fixnum] position - the node position to replace
  113 + # @param payload - the new payload object to replace
  114 + def replace position, payload
  115 + node = at position
  116 + if node
  117 + node.payload = payload
  118 + true
  119 + else
  120 + nil
  121 + end
  122 + end
  123 +
  124 + # Seeds bracket based on `seed_order` value of bracket. Provide an iterator
  125 + # with players that will be inserted in the appropriate location. Will raise a
  126 + # SeedLimitExceededError if too many players are sent, and a NoSeedOrderError if
  127 + # the `seed_order` attribute is nil
  128 + #
  129 + # @param [Enumerable] players - players to be seeded
  130 + def seed players
  131 + if @seed_order.nil?
  132 + raise NoSeedOrderError, 'Bracket does not have a seed order.'
  133 + elsif players.size > @seed_order.size
  134 + raise SeedLimitExceededError, 'cannot seed more players than seed order list.'
  135 + else
  136 + @seed_order.each do |position|
  137 + replace position, players.shift
  138 + end
  139 + end
  140 + end
  141 +
  142 + def winner
  143 + @root.payload
  144 + end
  145 +
  146 + def each(&block)
  147 + in_order(@root, block)
  148 + end
  149 +
  150 + def to_h
  151 + @root.to_h
  152 + end
  153 +
  154 + # Array of Seats mapping to the individual positions of the bracket tree. The
  155 + # order of the nodes is important, as insertion in this order maintains the
  156 + # binary tree
  157 + #
  158 + # @return Array seats
  159 + def seats
  160 + entries.sort_by { |node| @insertion_order.index(node.position) }
  161 + end
  162 +
  163 + alias_method :to_a, :seats
  164 +
  165 + def at position
  166 + find { |n| n.position == position }
  167 + end
  168 +
  169 + alias_method :size, :count
  170 +
  171 + # Progresses the bracket by using the stored `matches` to copy data to the winning
  172 + # and losing seats. This facilitates match progression without manually
  173 + # manipulating bracket positions
  174 + #
  175 + # @param Fixnum seat - winning seat position
  176 + # @return Boolean result - result of progression
  177 + def match_winner seat
  178 + match = @matches.find { |m| m.include? seat }
  179 +
  180 + if match
  181 + losing_seat = match.seats.find { |s| s != seat }
  182 +
  183 + if match.winner_to
  184 + replace match.winner_to, at(seat).payload
  185 + end
  186 +
  187 + if match.loser_to
  188 + replace match.loser_to, at(losing_seat).payload
  189 + end
  190 +
  191 + return true
  192 + else
  193 + return false
  194 + end
  195 + end
  196 +
  197 + # Inverse of `match_winner`, progresses the bracket based on seat. See `match_winner`
  198 + # for more details
  199 + #
  200 + # @param Fixnum seat - losing seat position
  201 + # @return Boolean result - result of progression
  202 + def match_loser seat
  203 + match = @matches.find { |m| m.include? seat }
  204 +
  205 + if match
  206 + winning_seat = match.seats.find { |s| s != seat }
  207 + match_winner winning_seat
  208 + else
  209 + return false
  210 + end
  211 + end
  212 +
  213 + def in_order(node, block)
  214 + if node
  215 + unless node.left.nil?
  216 + in_order(node.left, block)
  217 + end
  218 +
  219 + block.call(node)
  220 +
  221 + unless node.right.nil?
  222 + in_order(node.right, block)
  223 + end
  224 + end
  225 + end
  226 + end
  227 + end
  228 +end
7 lib/bracket_tree/bracket/double_elimination.rb
... ... @@ -0,0 +1,7 @@
  1 +module BracketTree
  2 + module Bracket
  3 + class DoubleElimination < Base
  4 + template BracketTree::Template::DoubleElimination
  5 + end
  6 + end
  7 +end
7 lib/bracket_tree/bracket/single_elimination.rb
... ... @@ -0,0 +1,7 @@
  1 +module BracketTree
  2 + module Bracket
  3 + class SingleElimination < Base
  4 + template BracketTree::Template::SingleElimination
  5 + end
  6 + end
  7 +end
18 lib/bracket_tree/template.rb
@@ -53,22 +53,6 @@ def initialize
53 53 @matches = []
54 54 end
55 55
56   - # Generates a Bracket object with placeholder empty hashes for each Seat in the
57   - # Template
58   - #
59   - # @return [BracketTree::Bracket] bracket
60   - def generate_blank_bracket
61   - bracket = Bracket.new matches: @matches
62   -
63   - @seats.each do |position|
64   - bracket.add position, {}
65   - end
66   -
67   - bracket.seed_order = @starting_seats
68   -
69   - bracket
70   - end
71   -
72 56 # Returns hash representation of the Template
73 57 #
74 58 # @return [Hash] template
@@ -79,6 +63,8 @@ def to_h
79 63 matches: @matches
80 64 }
81 65 end
  66 +
  67 + alias_method :seed_order, :starting_seats
82 68 end
83 69 end
84 70 end
2  lib/bracket_tree/templates/double_elimination.rb
... ... @@ -1,7 +1,5 @@
1 1 module BracketTree
2 2 module Template
3   - # Template for double-elimination-based tournament formats. Uses the 'right' half
4   - # of the binary tree for the loser's bracket and the 'left' half for the winner's
5 3 class DoubleElimination < Base
6 4 class << self
7 5 def location
19 spec/unit/bracket_tree/bracket_spec.rb → spec/bracket_spec.rb
... ... @@ -1,7 +1,8 @@
1 1 require 'spec_helper'
2 2
3   -describe BracketTree::Bracket do
4   - let(:bracket) { BracketTree::Bracket.new }
  3 +describe BracketTree::Bracket::Base do
  4 + let(:bracket) { BracketTree::Bracket::Base.new }
  5 +
5 6 describe '#initialize' do
6 7 it 'should create an array of Match objects if matches are passed' do
7 8 matches = [
@@ -9,7 +10,7 @@
9 10 { seats: [5,7], winner_to: 4, loser_to: nil },
10 11 { seats: [2,4], winner_to: 6, loser_to: nil }
11 12 ]
12   - bracket = BracketTree::Bracket.new matches: matches
  13 + bracket = BracketTree::Bracket::Base.new matches: matches
13 14
14 15 bracket.matches.should be_a Array
15 16 bracket.matches.map(&:class).should == [BracketTree::Match, BracketTree::Match, BracketTree::Match]
@@ -19,6 +20,12 @@
19 20 bracket.matches.should be_a Array
20 21 bracket.matches.should == []
21 22 end
  23 +
  24 + it 'should assign the seed order if seed_order is passed' do
  25 + expected = [1,3,5,7]
  26 + bracket = BracketTree::Bracket::Base.new seed_order: expected
  27 + bracket.seed_order.should == expected
  28 + end
22 29 end
23 30
24 31 describe '#add' do
@@ -88,7 +95,7 @@
88 95 end
89 96
90 97 describe '#seats' do
91   - let(:bracket) { BracketTree::Template::SingleElimination.by_size(4).generate_blank_bracket }
  98 + let(:bracket) { BracketTree::Bracket::SingleElimination.by_size 4 }
92 99
93 100 it 'should return 7 seats' do
94 101 bracket.seats.should have(7).seats
@@ -138,7 +145,7 @@
138 145 end
139 146
140 147 describe '#seed' do
141   - let(:bracket) { BracketTree::Template::SingleElimination.by_size(4).generate_blank_bracket }
  148 + let(:bracket) { BracketTree::Bracket::SingleElimination.by_size 4 }
142 149 let(:players) do
143 150 [
144 151 { name: 'player4' },
@@ -170,7 +177,7 @@
170 177 end
171 178
172 179 describe '#match_winner' do
173   - let(:bracket) { BracketTree::Template::DoubleElimination.by_size(4).generate_blank_bracket }
  180 + let(:bracket) { BracketTree::Bracket::DoubleElimination.by_size 4 }
174 181
175 182 it 'copies the seat data to the seat specified in the match winner_to' do
176 183 bracket.at(1).payload[:seed_value] = 1
20 spec/template_spec.rb
@@ -55,24 +55,4 @@ def template_json
55 55 template.to_h.should == template_json
56 56 end
57 57 end
58   -
59   - describe '#generate_blank_bracket' do
60   - it 'should return a Bracket object with empty hashes in the seats' do
61   - template = TestTemplate.by_size 4
62   - bracket = template.generate_blank_bracket
63   -
64   - bracket.should be_a BracketTree::Bracket
65   - bracket.size.should == 7
66   - bracket.to_a.map { |n| n.payload }.should == [{}, {}, {}, {}, {}, {}, {}]
67   - end
68   -
69   - it 'should populate the matches from the template' do
70   - template = TestTemplate.by_size 4
71   - bracket = template.generate_blank_bracket
72   -
73   - bracket.matches.should be_a Array
74   - bracket.matches[0].should be_a BracketTree::Match
75   - bracket.matches[0].to_h.should == template.matches[0]
76   - end
77   - end
78 58 end

0 comments on commit 99cc8ec

Please sign in to comment.
Something went wrong with that request. Please try again.