icu_tournament 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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