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.
- data/README.rdoc +3 -8
- data/lib/icu_tournament.rb +4 -1
- data/lib/icu_tournament/federation.rb +66 -72
- data/lib/icu_tournament/player.rb +97 -103
- data/lib/icu_tournament/result.rb +82 -89
- data/lib/icu_tournament/team.rb +45 -52
- data/lib/icu_tournament/tournament.rb +153 -163
- data/lib/icu_tournament/tournament_fcsv.rb +123 -129
- data/lib/icu_tournament/tournament_krause.rb +107 -113
- data/lib/icu_tournament/tournament_sp.rb +93 -99
- data/lib/icu_tournament/util.rb +22 -41
- data/lib/icu_tournament/version.rb +3 -1
- metadata +23 -26
- data/lib/icu_tournament/name.rb +0 -274
- data/spec/name_spec.rb +0 -208
@@ -1,91 +1,84 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module ICU
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
result.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
result.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
result =
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
result.rateable
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
tluser
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
tluser.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
result.score = '
|
72
|
-
|
73
|
-
|
74
|
-
|
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)
|
data/lib/icu_tournament/team.rb
CHANGED
@@ -1,65 +1,58 @@
|
|
1
1
|
module ICU
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
team
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
team
|
18
|
-
|
19
|
-
team.
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
team.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
team.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
team.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
team.
|
39
|
-
|
40
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
t
|
20
|
-
|
21
|
-
t.
|
22
|
-
t.
|
23
|
-
t.
|
24
|
-
|
25
|
-
t.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
puts
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
* the
|
68
|
-
* the
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
t.rerank
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
*
|
117
|
-
*
|
118
|
-
*
|
119
|
-
*
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|