icu_tournament 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,91 +1,84 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module ICU
4
-
5
- =begin rdoc
6
-
7
- == Result
8
-
9
- A result is the outcome of a game from the perspective of one of the players.
10
- If the game was not a bye or a walkover and involved a second player, then
11
- that second player will also have a result for the same game, and the two
12
- results will be mirror images of each other.
13
-
14
- A result always involves a round number, a player number and a score, so these
15
- three attributes must be supplied in the constructor.
16
-
17
- result = ICU::Result.new(2, 10, 'W')
18
-
19
- The above example represents player 10 winning in round 2. As it stands, it represends
20
- a bye or walkover since there is no opponent. Without an opponent, it is unrateable.
21
-
22
- result.rateable # => false
23
-
24
- The player's colour and the number of their opponent can be set as follows:
25
-
26
- result.colour = 'B'
27
- result.opponent = 13
28
-
29
- Specifying an opponent always makes a result rateable.
30
-
31
- result.rateable # => true
32
-
33
- This example now represents a win by player 10 with the black pieces over player number 13 in round 2.
34
- Alternatively, all this can been specified in the constructor.
35
-
36
- result = ICU::Result.new(2, 10, 'W', :opponent => 13, :colour => 'B')
37
-
38
- To make a game unratable, even if it involves an opponent, set the _rateable_ atribute explicity:
39
-
40
- result.rateable = false
41
-
42
- or include it in the constructor:
43
-
44
- result = ICU::Result.new(2, 10, 'W', :opponent => 13, :colour => 'B', :rateable => false)
45
-
46
- The result of the same game from the perspective of the opponent is:
47
-
48
- tluser = result.reverse
49
-
50
- which, with the above example, would be:
51
-
52
- tluser.player # => 13
53
- tluser.opponent # => 10
54
- tluser.score # => 'L'
55
- tluser.colour # => 'B'
56
- tluser.round # => 2
57
-
58
- The reversed result copies the _rateable_ attribute of the original unless an
59
- explicit override is supplied.
60
-
61
- result.rateable # => true
62
- result.reverse.rateable # => true (copied from original)
63
- result.reverse(false).rateable # => false (overriden)
64
-
65
- A result which has no opponent is not reversible (the _reverse_ method returns _nil_).
66
-
67
- The return value from the _score_ method is always one of _W_, _L_ or _D_. However,
68
- when setting the score, a certain amount of variation is permitted as long as it is
69
- clear what is meant. For eample, the following would all be converted to _D_:
70
-
71
- result.score = ' D '
72
- result.score = 'd'
73
- result.score = '='
74
- result.score = '0.5'
75
- result.score = '½'
76
-
77
- The _points_ read-only accessor always returns a floating point number: either 0.0, 0.5 or 1.0.
78
-
79
- =end
80
-
4
+ #
5
+ # A result is the outcome of a game from the perspective of one of the players.
6
+ # If the game was not a bye or a walkover and involved a second player, then
7
+ # that second player will also have a result for the same game, and the two
8
+ # results will be mirror images of each other.
9
+ #
10
+ # A result always involves a round number, a player number and a score, so these
11
+ # three attributes must be supplied in the constructor.
12
+ #
13
+ # result = ICU::Result.new(2, 10, 'W')
14
+ #
15
+ # The above example represents player 10 winning in round 2. As it stands, it represends
16
+ # a bye or walkover since there is no opponent. Without an opponent, it is unrateable.
17
+ #
18
+ # result.rateable # => false
19
+ #
20
+ # The player's colour and the number of their opponent can be set as follows:
21
+ #
22
+ # result.colour = 'B'
23
+ # result.opponent = 13
24
+ #
25
+ # Specifying an opponent always makes a result rateable.
26
+ #
27
+ # result.rateable # => true
28
+ #
29
+ # This example now represents a win by player 10 with the black pieces over player number 13 in round 2.
30
+ # Alternatively, all this can been specified in the constructor.
31
+ #
32
+ # result = ICU::Result.new(2, 10, 'W', :opponent => 13, :colour => 'B')
33
+ #
34
+ # To make a game unratable, even if it involves an opponent, set the _rateable_ atribute explicity:
35
+ #
36
+ # result.rateable = false
37
+ #
38
+ # or include it in the constructor:
39
+ #
40
+ # result = ICU::Result.new(2, 10, 'W', :opponent => 13, :colour => 'B', :rateable => false)
41
+ #
42
+ # The result of the same game from the perspective of the opponent is:
43
+ #
44
+ # tluser = result.reverse
45
+ #
46
+ # which, with the above example, would be:
47
+ #
48
+ # tluser.player # => 13
49
+ # tluser.opponent # => 10
50
+ # tluser.score # => 'L'
51
+ # tluser.colour # => 'B'
52
+ # tluser.round # => 2
53
+ #
54
+ # The reversed result copies the _rateable_ attribute of the original unless an
55
+ # explicit override is supplied.
56
+ #
57
+ # result.rateable # => true
58
+ # result.reverse.rateable # => true (copied from original)
59
+ # result.reverse(false).rateable # => false (overriden)
60
+ #
61
+ # A result which has no opponent is not reversible (the _reverse_ method returns _nil_).
62
+ #
63
+ # The return value from the _score_ method is always one of _W_, _L_ or _D_. However,
64
+ # when setting the score, a certain amount of variation is permitted as long as it is
65
+ # clear what is meant. For eample, the following would all be converted to _D_:
66
+ #
67
+ # result.score = ' D '
68
+ # result.score = 'd'
69
+ # result.score = '='
70
+ # result.score = '0.5'
71
+ # result.score = '½'
72
+ #
73
+ # The _points_ read-only accessor always returns a floating point number: either 0.0, 0.5 or 1.0.
74
+ #
81
75
  class Result
82
-
83
76
  extend ICU::Accessor
84
77
  attr_positive :round
85
78
  attr_integer :player
86
-
79
+
87
80
  attr_reader :score, :colour, :opponent, :rateable
88
-
81
+
89
82
  # Constructor. Round number, player number and score must be supplied.
90
83
  # Optional hash attribute are _opponent_, _colour_ and _rateable_.
91
84
  def initialize(round, player, score, opt={})
@@ -95,7 +88,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
95
88
  [:colour, :opponent].each { |a| self.send("#{a}=", opt[a]) unless opt[a].nil? }
96
89
  self.rateable = opt[:rateable] # always attempt to set this, and do it last, to get the right default
97
90
  end
98
-
91
+
99
92
  # Score for the game, even if a default. One of 'W', 'L' or 'D'. Reasonable inputs like 1, 0, =, ½, etc will be converted.
100
93
  def score=(score)
101
94
  @score = case score.to_s.strip
@@ -105,7 +98,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
105
98
  else raise "invalid score (#{score})"
106
99
  end
107
100
  end
108
-
101
+
109
102
  # Return the score as a floating point number.
110
103
  def points
111
104
  case @score
@@ -114,7 +107,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
114
107
  else 0.5
115
108
  end
116
109
  end
117
-
110
+
118
111
  # Colour. Either 'W' (white) or 'B' (black).
119
112
  def colour=(colour)
120
113
  @colour = case colour.to_s
@@ -124,7 +117,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
124
117
  else raise "invalid colour (#{colour})"
125
118
  end
126
119
  end
127
-
120
+
128
121
  # Opponent player number. Either absent (_nil_) or any integer except the player number.
129
122
  def opponent=(opponent)
130
123
  @opponent = case opponent
@@ -137,7 +130,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
137
130
  raise "opponent number and player number (#{@opponent}) must be different" if @opponent == player
138
131
  self.rateable = true if @opponent
139
132
  end
140
-
133
+
141
134
  # Rateable flag. If false, result is not rateable. Can only be true if there is an opponent.
142
135
  def rateable=(rateable)
143
136
  if opponent.nil?
@@ -150,7 +143,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
150
143
  else true
151
144
  end
152
145
  end
153
-
146
+
154
147
  # Return a reversed version (from the opponent's perspective) of a result.
155
148
  def reverse(rateable=nil)
156
149
  return unless @opponent
@@ -161,7 +154,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
161
154
  r.rateable = rateable || @rateable
162
155
  r
163
156
  end
164
-
157
+
165
158
  # Renumber the player and opponent (if there is one) according to the supplied hash. Return self.
166
159
  def renumber(map)
167
160
  raise "result player number #{@player} not found in renumbering hash" unless map[@player]
@@ -174,7 +167,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
174
167
  end
175
168
  self
176
169
  end
177
-
170
+
178
171
  # Loose equality. True if the round, player and opponent numbers, colour and score all match.
179
172
  def ==(other)
180
173
  return unless other.is_a? Result
@@ -183,7 +176,7 @@ The _points_ read-only accessor always returns a floating point number: either 0
183
176
  end
184
177
  true
185
178
  end
186
-
179
+
187
180
  # Strict equality. True if the there's loose equality and also the rateablity is the same.
188
181
  def eql?(other)
189
182
  return true if equal?(other)
@@ -1,65 +1,58 @@
1
1
  module ICU
2
-
3
- =begin rdoc
4
-
5
- == Team
6
-
7
- A team consists of a name and one or more players referenced by numbers.
8
- Typically the team will be attached to a tournament (ICU::Tournament)
9
- and the numbers will the unique numbers by which the players in that
10
- tournament are referenced. To instantiate a team, you must supply a
11
- name.
12
-
13
- team = ICU::Team.new('Wandering Dragons')
14
-
15
- Then you simply add player's (numbers) to it.
16
-
17
- team.add_player(1)
18
- team.add_payeer(3)
19
- team.add_player(7)
20
-
21
- To get the current members of a team
22
-
23
- team.members # => [1, 3, 7]
24
-
25
- You can enquire whether a team contains a given player number.
26
-
27
- team.contains?(3) # => true
28
- team.contains?(4) # => false
29
-
30
- Or whether it matches a given name (which ignoring case and removing spurious whitespace)
31
-
32
- team.matches(' wandering dragons ') # => true
33
- team.matches('Blundering Bishops') # => false
34
-
35
- Whenever you reset the name of a tournament spurious whitespace is removed but case is not altered.
36
-
37
- team.name = ' blundering bishops '
38
- team.name # => "blundering bishops"
39
-
40
- Attempting to add non-numbers or duplicate numbers as new team members results in an exception.
41
-
42
- team.add(nil) # exception - not a number
43
- team.add(3) # exception - already a member
44
-
45
- =end
46
-
2
+ #
3
+ # A team consists of a name and one or more players referenced by numbers.
4
+ # Typically the team will be attached to a tournament (ICU::Tournament)
5
+ # and the numbers will the unique numbers by which the players in that
6
+ # tournament are referenced. To instantiate a team, you must supply a
7
+ # name.
8
+ #
9
+ # team = ICU::Team.new('Wandering Dragons')
10
+ #
11
+ # Then you simply add player's (numbers) to it.
12
+ #
13
+ # team.add_player(1)
14
+ # team.add_payeer(3)
15
+ # team.add_player(7)
16
+ #
17
+ # To get the current members of a team
18
+ #
19
+ # team.members # => [1, 3, 7]
20
+ #
21
+ # You can enquire whether a team contains a given player number.
22
+ #
23
+ # team.contains?(3) # => true
24
+ # team.contains?(4) # => false
25
+ #
26
+ # Or whether it matches a given name (which ignoring case and removing spurious whitespace)
27
+ #
28
+ # team.matches(' wandering dragons ') # => true
29
+ # team.matches('Blundering Bishops') # => false
30
+ #
31
+ # Whenever you reset the name of a tournament spurious whitespace is removed but case is not altered.
32
+ #
33
+ # team.name = ' blundering bishops '
34
+ # team.name # => "blundering bishops"
35
+ #
36
+ # Attempting to add non-numbers or duplicate numbers as new team members results in an exception.
37
+ #
38
+ # team.add(nil) # exception - not a number
39
+ # team.add(3) # exception - already a member
40
+ #
47
41
  class Team
48
-
49
42
  attr_reader :name, :members
50
-
43
+
51
44
  # Constructor. Name must be supplied.
52
45
  def initialize(name)
53
46
  self.name = name
54
47
  @members = Array.new
55
48
  end
56
-
49
+
57
50
  # Set name. Must not be blank.
58
51
  def name=(name)
59
52
  @name = name.strip.squeeze(' ')
60
53
  raise "team can't be blank" if @name.length == 0
61
54
  end
62
-
55
+
63
56
  # Add a team member referenced by any integer.
64
57
  def add_member(number)
65
58
  pnum = number.to_i
@@ -67,7 +60,7 @@ Attempting to add non-numbers or duplicate numbers as new team members results i
67
60
  raise "can't add duplicate player number #{pnum} to team '#{@name}'" if @members.include?(pnum)
68
61
  @members.push(pnum)
69
62
  end
70
-
63
+
71
64
  # Renumber the players according to the supplied hash. Return self.
72
65
  def renumber(map)
73
66
  @members.each_with_index do |pnum, index|
@@ -76,12 +69,12 @@ Attempting to add non-numbers or duplicate numbers as new team members results i
76
69
  end
77
70
  self
78
71
  end
79
-
72
+
80
73
  # Detect if a member exists in a team.
81
74
  def include?(number)
82
75
  @members.include?(number)
83
76
  end
84
-
77
+
85
78
  # Does the team name match the given string (ignoring case and spurious whitespace).
86
79
  def matches(name)
87
80
  self.name.downcase == name.strip.squeeze(' ').downcase
@@ -1,167 +1,157 @@
1
1
  module ICU
2
-
3
- =begin rdoc
4
-
5
- == Building a Tournament
6
-
7
- One way to create a tournament object is by parsing one of the supported file types (e.g. ICU::Tournament::Krause).
8
- It is also possible to build one programmatically by:
9
-
10
- 1. creating a bare tournament instance,
11
- 2. adding all the players,
12
- 3. adding all the results.
13
-
14
- For example:
15
-
16
- require 'rubygems'
17
- require 'icu_tournament'
18
-
19
- t = ICU::Tournament.new('Bangor Masters', '2009-11-09')
20
-
21
- t.add_player(ICU::Player.new('Bobby', 'Fischer', 10))
22
- t.add_player(ICU::Player.new('Garry', 'Kasparov', 20))
23
- t.add_player(ICU::Player.new('Mark', 'Orr', 30))
24
-
25
- t.add_result(ICU::Result.new(1, 10, 'D', :opponent => 30, :colour => 'W'))
26
- t.add_result(ICU::Result.new(2, 20, 'W', :opponent => 30, :colour => 'B'))
27
- t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))
28
-
29
- t.validate!(:rerank => true)
30
-
31
- and then:
32
-
33
- serializer = ICU::Tournament::Krause.new
34
- puts serializer.serialize(@t)
35
-
36
- or equivalntly, just:
37
-
38
- puts t.serialize('Krause')
39
-
40
- would result in the following output:
41
-
42
- 012 Bangor Masters
43
- 042 2009-11-09
44
- 001 10 Fischer,Bobby 1.5 1 30 w = 20 b 1
45
- 001 20 Kasparov,Garry 1.0 2 30 b 1 10 w 0
46
- 001 30 Orr,Mark 0.5 3 10 b = 20 w 0
47
-
48
- Note that the players should be added first because the _add_result_ method will
49
- raise an exception if the players it references through their tournament numbers
50
- (10, 20 and 30 in this example) have not already been added to the tournament.
51
-
52
- See ICU::Player and ICU::Result for more details about players and results.
53
-
54
-
55
- == Validation
56
-
57
- A tournament can be validated with either the <em>validate!</em> or _invalid_ methods.
58
- On success, the first returns true while the second returns false.
59
- On error, the first throws an exception while the second returns a string
60
- describing the error.
61
-
62
- Validations checks that:
63
-
64
- * there are at least two players
65
- * every player has a least one result
66
- * the result round numbers are consistent (no more than one game per player per round)
67
- * the tournament dates (start, finish, round dates), if there are any, are consistent
68
- * the player ranks are consistent with their scores
69
-
70
- Side effects of calling <em>validate!</em> or _invalid_ include:
71
-
72
- * the number of rounds will be set if not set already
73
- * the finish date will be set if not set already and if there are round dates
74
-
75
- Optionally, additional validation checks can be performed given a tournament
76
- parser/serializer. For example:
77
-
78
- t.validate!(:type => ICU::Tournament.ForeignCSV.new)
79
-
80
- Or equivalently:
81
-
82
- t.validate!(:type => 'ForeignCSV')
83
-
84
- Such additional validation is always performed before a tournament is serialized.
85
- For example, the following are equivalent and will throw an exception if
86
- the tournament is invalid according to either the general rules or the rules
87
- specific for the type used:
88
-
89
- t.serialize('ForeignCSV')
90
- ICU::Tournament::ForeignCSV.new.serialize(t)
91
-
92
- == Ranking
93
-
94
- The players in a tournament can be ranked by calling the _rerank_ method directly.
95
-
96
- t.rerank
97
-
98
- Alternatively they can be ranked as a side effect of validation if the _rerank_ option is set,
99
- but this only applies if the tournament is not yet ranked or it's ranking is inconsistent.
100
-
101
- t.validate(:rerank => true)
102
-
103
- Ranking is inconsistent if some but not all players have a rank or if all players
104
- have a rank but some are ranked higher than others on lower scores.
105
-
106
- To rank the players requires a tie break method to be specified to order players on the same score.
107
- The default is alphabetical (by last name then first name). Other methods can be specified by supplying
108
- an array of methods (strings or symbols) in order of precedence to the _tie_breaks_ setter. Examples:
109
-
110
- t.tie_breaks = ['Sonneborn-Berger']
111
- t.tie_breaks = [:buchholz, :neustadtl, :blacks, :wins]
112
- t.tie_breaks = [] # reset to the default
113
-
114
- The full list of supported methods is:
115
-
116
- * _Buchholz_: sum of opponents' scores
117
- * _Harkness_ (or _median_): like Buchholz except the highest and lowest opponents' scores are discarded (or two highest and lowest if 9 rounds or more)
118
- * _modified_median_: same as Harkness except only lowest (or highest) score(s) are discarded for players with more (or less) than 50%
119
- * _Neustadtl_ (or _Sonneborn-Berger_): sum of scores of players defeated plus half sum of scores of players drawn against
120
- * _progressive_ (or _cumulative_): sum of running score for each round
121
- * _ratings_: sum of opponents ratings
122
- * _blacks_: number of blacks
123
- * _wins_: number of wins
124
- * _name_: alphabetical by name (if _tie_breaks_ is set to an empty array, as it is initially, then this will be used as the back-up tie breaker)
125
-
126
- The return value from _rerank_ is the tournament object itself, to allow chaining, for example:
127
-
128
- t.rerank.renumber
129
-
130
-
131
- == Renumbering
132
-
133
- The numbers used to uniquely identify each player in a tournament can be any set of unique integers
134
- (including zero and negative numbers). To renumber the players so that these numbers start at 1 and
135
- end with the total number of players, use the _renumber_ method. This method takes one optional
136
- argument to specify how the renumbering is done.
137
-
138
- t.renumber(:rank) # renumber by rank (if there are consistent rankings), otherwise by name alphabetically
139
- t.renumber # the same, as renumbering by rank is the default
140
- t.renumber(:name) # renumber by name alphabetically
141
- t.renumber(:order) # renumber maintaining the order of the original numbers
142
-
143
- The return value from _renumber_ is the tournament object itself.
144
-
145
-
146
- == Parsing Files
147
-
148
- As an alternative to processing files by first instantiating a parser of the appropropriate class
149
- (such as ICU::Tournament::SwissPerfect, ICU::Tournament::Krause and ICU::Tournament::ForeignCSV)
150
- and then calling the parser's <em>parse_file</em> or <em>parse_file!</em> instance method,
151
- a convenience class method, <em>parse_file!</em>, is available when a parser instance is not required.
152
- For example:
153
-
154
- t = ICU::Tournament.parse_file!('champs.zip', 'SwissPerfect', :start => '2010-07-03')
155
-
156
- The method takes a filename, format and an options hash as arguments. It either returns
157
- an instance of ICU::Tournament or throws an exception. See the documentation for the
158
- different formats for what options are available. For some, no options are available,
159
- in which case any options supplied to this method will be silently ignored.
160
-
161
- =end
162
-
2
+ #
3
+ # One way to create a tournament object is by parsing one of the supported file types (e.g. ICU::Tournament::Krause).
4
+ # It is also possible to build one programmatically by:
5
+ #
6
+ # * creating a bare tournament instance,
7
+ # * adding all the players,
8
+ # * adding all the results.
9
+ #
10
+ # For example:
11
+ #
12
+ # require 'rubygems'
13
+ # require 'icu_tournament'
14
+ #
15
+ # t = ICU::Tournament.new('Bangor Masters', '2009-11-09')
16
+ #
17
+ # t.add_player(ICU::Player.new('Bobby', 'Fischer', 10))
18
+ # t.add_player(ICU::Player.new('Garry', 'Kasparov', 20))
19
+ # t.add_player(ICU::Player.new('Mark', 'Orr', 30))
20
+ #
21
+ # t.add_result(ICU::Result.new(1, 10, 'D', :opponent => 30, :colour => 'W'))
22
+ # t.add_result(ICU::Result.new(2, 20, 'W', :opponent => 30, :colour => 'B'))
23
+ # t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))
24
+ #
25
+ # t.validate!(:rerank => true)
26
+ #
27
+ # and then:
28
+ #
29
+ # serializer = ICU::Tournament::Krause.new
30
+ # puts serializer.serialize(@t)
31
+ #
32
+ # or equivalntly, just:
33
+ #
34
+ # puts t.serialize('Krause')
35
+ #
36
+ # would result in the following output:
37
+ #
38
+ # 012 Bangor Masters
39
+ # 042 2009-11-09
40
+ # 001 10 Fischer,Bobby 1.5 1 30 w = 20 b 1
41
+ # 001 20 Kasparov,Garry 1.0 2 30 b 1 10 w 0
42
+ # 001 30 Orr,Mark 0.5 3 10 b = 20 w 0
43
+ #
44
+ # Note that the players should be added first because the _add_result_ method will
45
+ # raise an exception if the players it references through their tournament numbers
46
+ # (10, 20 and 30 in this example) have not already been added to the tournament.
47
+ #
48
+ # See ICU::Player and ICU::Result for more details about players and results.
49
+ #
50
+ # == Validation
51
+ #
52
+ # A tournament can be validated with either the <em>validate!</em> or _invalid_ methods.
53
+ # On success, the first returns true while the second returns false.
54
+ # On error, the first throws an exception while the second returns a string
55
+ # describing the error.
56
+ #
57
+ # Validations checks that:
58
+ #
59
+ # * there are at least two players
60
+ # * every player has a least one result
61
+ # * the result round numbers are consistent (no more than one game per player per round)
62
+ # * the tournament dates (start, finish, round dates), if there are any, are consistent
63
+ # * the player ranks are consistent with their scores
64
+ #
65
+ # Side effects of calling <em>validate!</em> or _invalid_ include:
66
+ #
67
+ # * the number of rounds will be set if not set already
68
+ # * the finish date will be set if not set already and if there are round dates
69
+ #
70
+ # Optionally, additional validation checks can be performed given a tournament
71
+ # parser/serializer. For example:
72
+ #
73
+ # t.validate!(:type => ICU::Tournament.ForeignCSV.new)
74
+ #
75
+ # Or equivalently:
76
+ #
77
+ # t.validate!(:type => 'ForeignCSV')
78
+ #
79
+ # Such additional validation is always performed before a tournament is serialized.
80
+ # For example, the following are equivalent and will throw an exception if
81
+ # the tournament is invalid according to either the general rules or the rules
82
+ # specific for the type used:
83
+ #
84
+ # t.serialize('ForeignCSV')
85
+ # ICU::Tournament::ForeignCSV.new.serialize(t)
86
+ #
87
+ # == Ranking
88
+ #
89
+ # The players in a tournament can be ranked by calling the _rerank_ method directly.
90
+ #
91
+ # t.rerank
92
+ #
93
+ # Alternatively they can be ranked as a side effect of validation if the _rerank_ option is set,
94
+ # but this only applies if the tournament is not yet ranked or it's ranking is inconsistent.
95
+ #
96
+ # t.validate(:rerank => true)
97
+ #
98
+ # Ranking is inconsistent if some but not all players have a rank or if all players
99
+ # have a rank but some are ranked higher than others on lower scores.
100
+ #
101
+ # To rank the players requires a tie break method to be specified to order players on the same score.
102
+ # The default is alphabetical (by last name then first name). Other methods can be specified by supplying
103
+ # an array of methods (strings or symbols) in order of precedence to the _tie_breaks_ setter. Examples:
104
+ #
105
+ # t.tie_breaks = ['Sonneborn-Berger']
106
+ # t.tie_breaks = [:buchholz, :neustadtl, :blacks, :wins]
107
+ # t.tie_breaks = [] # reset to the default
108
+ #
109
+ # The full list of supported methods is:
110
+ #
111
+ # * _Buchholz_: sum of opponents' scores
112
+ # * _Harkness_ (or _median_): like Buchholz except the highest and lowest opponents' scores are discarded (or two highest and lowest if 9 rounds or more)
113
+ # * _modified_median_: same as Harkness except only lowest (or highest) score(s) are discarded for players with more (or less) than 50%
114
+ # * _Neustadtl_ (or _Sonneborn-Berger_): sum of scores of players defeated plus half sum of scores of players drawn against
115
+ # * _progressive_ (or _cumulative_): sum of running score for each round
116
+ # * _ratings_: sum of opponents ratings
117
+ # * _blacks_: number of blacks
118
+ # * _wins_: number of wins
119
+ # * _name_: alphabetical by name (if _tie_breaks_ is set to an empty array, as it is initially, then this will be used as the back-up tie breaker)
120
+ #
121
+ # The return value from _rerank_ is the tournament object itself, to allow chaining, for example:
122
+ #
123
+ # t.rerank.renumber
124
+ #
125
+ # == Renumbering
126
+ #
127
+ # The numbers used to uniquely identify each player in a tournament can be any set of unique integers
128
+ # (including zero and negative numbers). To renumber the players so that these numbers start at 1 and
129
+ # end with the total number of players, use the _renumber_ method. This method takes one optional
130
+ # argument to specify how the renumbering is done.
131
+ #
132
+ # t.renumber(:rank) # renumber by rank (if there are consistent rankings), otherwise by name alphabetically
133
+ # t.renumber # the same, as renumbering by rank is the default
134
+ # t.renumber(:name) # renumber by name alphabetically
135
+ # t.renumber(:order) # renumber maintaining the order of the original numbers
136
+ #
137
+ # The return value from _renumber_ is the tournament object itself.
138
+ #
139
+ # == Parsing Files
140
+ #
141
+ # As an alternative to processing files by first instantiating a parser of the appropropriate class
142
+ # (such as ICU::Tournament::SwissPerfect, ICU::Tournament::Krause and ICU::Tournament::ForeignCSV)
143
+ # and then calling the parser's <em>parse_file</em> or <em>parse_file!</em> instance method,
144
+ # a convenience class method, <em>parse_file!</em>, is available when a parser instance is not required.
145
+ # For example:
146
+ #
147
+ # t = ICU::Tournament.parse_file!('champs.zip', 'SwissPerfect', :start => '2010-07-03')
148
+ #
149
+ # The method takes a filename, format and an options hash as arguments. It either returns
150
+ # an instance of ICU::Tournament or throws an exception. See the documentation for the
151
+ # different formats for what options are available. For some, no options are available,
152
+ # in which case any options supplied to this method will be silently ignored.
153
+ #
163
154
  class Tournament
164
-
165
155
  extend ICU::Accessor
166
156
  attr_date :start
167
157
  attr_date_or_nil :finish
@@ -389,7 +379,7 @@ in which case any options supplied to this method will be silently ignored.
389
379
  check_type(options[:type]) if options[:type]
390
380
  true
391
381
  end
392
-
382
+
393
383
  # Convenience method to parse a file.
394
384
  def self.parse_file!(file, format, opts={})
395
385
  type = format.to_s