sanichi-chess_icu 0.2.10 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +6 -4
- data/Rakefile +6 -0
- data/VERSION.yml +2 -2
- data/lib/chess_icu.rb +7 -2
- data/lib/federation.rb +272 -0
- data/lib/player.rb +9 -6
- data/lib/result.rb +7 -0
- data/lib/tournament.rb +53 -10
- data/lib/tournament_krause.rb +296 -0
- data/spec/federation_spec.rb +117 -0
- data/spec/player_spec.rb +3 -1
- data/spec/result_spec.rb +17 -0
- data/spec/tournament_krause_spec.rb +287 -0
- metadata +8 -2
data/README.rdoc
CHANGED
@@ -17,10 +17,11 @@ Tournament objects are created by parsing files of various formats. An instance
|
|
17
17
|
with the file's contents as the only argument.
|
18
18
|
|
19
19
|
require 'chess_icu'
|
20
|
-
data = open('tournament.
|
20
|
+
data = open('tournament.txt') { |f| f.read }
|
21
|
+
parser = ICU::Tournament::Parser.new # substitute the name of an available parser (see below) for "Parser"
|
21
22
|
tournament = parser.parse(data)
|
22
23
|
|
23
|
-
On success, the
|
24
|
+
On success, the _parse_ method returns an object of type ICU::Tournament. On error, it returns _nil_ and an error message
|
24
25
|
can be retrieved from the parser instance:
|
25
26
|
|
26
27
|
parser.error # => error message or nil on success
|
@@ -29,14 +30,15 @@ Alternatively, if an exception is preferred on error, the <em>parse!</em> method
|
|
29
30
|
|
30
31
|
tournament = parser.parse!(data)
|
31
32
|
|
32
|
-
The
|
33
|
+
The currently available parsers are:
|
33
34
|
|
34
35
|
* Foreign CSV (ICU::Tournament::ForeignCSV) - for players to report their individual results in foreign tournaments
|
36
|
+
* Krause (ICU::Tournament::Krause) - the format used by FIDE
|
35
37
|
|
36
38
|
|
37
39
|
== TODO
|
38
40
|
|
39
|
-
Future versions of this software will be able to parse
|
41
|
+
Future versions of this software will be able to parse additional file formats (such as SwissPerfect).
|
40
42
|
|
41
43
|
|
42
44
|
== Author
|
data/Rakefile
CHANGED
@@ -34,6 +34,12 @@ Spec::Rake::SpecTask.new(:fcsv) do |spec|
|
|
34
34
|
spec.spec_opts = ['--colour --format nested']
|
35
35
|
end
|
36
36
|
|
37
|
+
Spec::Rake::SpecTask.new(:krs) do |spec|
|
38
|
+
spec.libs << 'lib' << 'spec'
|
39
|
+
spec.spec_files = FileList['spec/tournament_krause_spec.rb']
|
40
|
+
spec.spec_opts = ['--colour --format nested']
|
41
|
+
end
|
42
|
+
|
37
43
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
38
44
|
if File.exist?('VERSION.yml')
|
39
45
|
config = YAML.load(File.read('VERSION.yml'))
|
data/VERSION.yml
CHANGED
data/lib/chess_icu.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
# :enddoc:
|
2
|
+
|
3
|
+
chess_icu_files = Array.new
|
4
|
+
chess_icu_files.concat %w{util name federation}
|
5
|
+
chess_icu_files.concat %w{player result tournament}
|
6
|
+
chess_icu_files.concat %w{fcsv krause}.map{ |f| "tournament_#{f}"}
|
7
|
+
|
2
8
|
dir = File.dirname(__FILE__)
|
3
|
-
|
4
|
-
%w{fcsv}.each { |file| require "#{dir}/tournament_#{file}" }
|
9
|
+
chess_icu_files.each { |file| require "#{dir}/#{file}" }
|
data/lib/federation.rb
ADDED
@@ -0,0 +1,272 @@
|
|
1
|
+
module ICU
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
== Federations
|
6
|
+
|
7
|
+
This class can be used to map a string into an object representing a chess federation.
|
8
|
+
In FIDE, chess federations are generally either referred to by their full names such as
|
9
|
+
_Ireland_ or _Russia_ or by three letter codes such as _IRL_ or _RUS_. The three letter
|
10
|
+
codes are mostly the same as those found in the international standard known as
|
11
|
+
{ISO 3166-1 alpha-3}[http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3], but with
|
12
|
+
some differences (e.g. for England, Scotland and Wales).
|
13
|
+
|
14
|
+
You cannot directly create instances of this class using _new_. Instead, you supply
|
15
|
+
a string to the class method _find_ and, if the string supplied uniguely identifies a
|
16
|
+
federation, an instance is returned which responds to _name_ and _code_.
|
17
|
+
|
18
|
+
fed = ICU::Federation.find('IRL')
|
19
|
+
fed.name # => "Ireland"
|
20
|
+
fed.code # => "IRL"
|
21
|
+
|
22
|
+
If the string is not sufficient to identify a federation, the _find_ method returns _nil_.
|
23
|
+
|
24
|
+
fed = ICU::Federation.find('ZYX') # => nil
|
25
|
+
|
26
|
+
If the string is three letters long and matches (case insenstively) one of the unique
|
27
|
+
federation codes, then the instance corresponding to that federation is returned.
|
28
|
+
|
29
|
+
ICU::Federation.find('rUs').code # => "RUS"
|
30
|
+
|
31
|
+
If the string is more than three letters long and if it is a substring (case insensitive)
|
32
|
+
of exactly one federation name, then that federation is returned.
|
33
|
+
|
34
|
+
ICU::Federation.find('ongoli').name # => "Mongolia"
|
35
|
+
|
36
|
+
In all other cases, nil is returned. In the following example, the string more than one federation.
|
37
|
+
|
38
|
+
ICU::Federation.find('land') # => nil
|
39
|
+
|
40
|
+
The method is not fooled by irrelevant white space.
|
41
|
+
|
42
|
+
ICU::Federation.find(' united states ').code # => 'USA'
|
43
|
+
|
44
|
+
=end
|
45
|
+
|
46
|
+
class Federation
|
47
|
+
attr_reader :code, :name
|
48
|
+
private_class_method :new
|
49
|
+
|
50
|
+
# Given a code, name or part of a name, return the corresponding federation instance.
|
51
|
+
# If there is no match or more than one match, _nil_ is returned.
|
52
|
+
def self.find(str=nil)
|
53
|
+
return nil unless str
|
54
|
+
str = str.to_s
|
55
|
+
return nil if str.length < 3
|
56
|
+
compile unless @@objects
|
57
|
+
str = str.strip.squeeze(' ').downcase
|
58
|
+
return @@codes[str] if str.length == 3
|
59
|
+
return @@names[str] if @@names[str]
|
60
|
+
matches = Array.new
|
61
|
+
@@names.each_key do |name|
|
62
|
+
matches << @@names[name] if name.index(str)
|
63
|
+
end
|
64
|
+
matches.uniq!
|
65
|
+
return nil unless matches.length == 1
|
66
|
+
matches[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(code, name) # :nodoc: because new is private
|
70
|
+
@code = code
|
71
|
+
@name = name
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def self.compile
|
77
|
+
return if @@objects
|
78
|
+
@@names = Hash.new
|
79
|
+
@@codes = Hash.new
|
80
|
+
@@objects = Array.new
|
81
|
+
@@data.each do |d|
|
82
|
+
object = new(d[0], d[1])
|
83
|
+
@@objects << object
|
84
|
+
@@codes[d[0].downcase] = object
|
85
|
+
(1..d.length-1).each do |i|
|
86
|
+
@@names[d[i].downcase] = object
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# The data structures compiled.
|
92
|
+
@@objects, @@codes, @@names = nil, nil, nil
|
93
|
+
|
94
|
+
# An array of data that gets compiled into other data structures.
|
95
|
+
@@data =
|
96
|
+
[
|
97
|
+
['AFG', 'Afghanistan'],
|
98
|
+
['ALB', 'Albania'],
|
99
|
+
['ALG', 'Algeria'],
|
100
|
+
['AND', 'Andorra'],
|
101
|
+
['ANG', 'Angola'],
|
102
|
+
['ANT', 'Antigua'],
|
103
|
+
['ARG', 'Argentina'],
|
104
|
+
['ARM', 'Armenia'],
|
105
|
+
['ARU', 'Aruba'],
|
106
|
+
['AUS', 'Australia'],
|
107
|
+
['AUT', 'Austria'],
|
108
|
+
['AZE', 'Azerbaijan'],
|
109
|
+
['BAH', 'Bahamas'],
|
110
|
+
['BRN', 'Bahrain'],
|
111
|
+
['BAN', 'Bangladesh'],
|
112
|
+
['BAR', 'Barbados'],
|
113
|
+
['BLR', 'Belarus'],
|
114
|
+
['BEL', 'Belgium'],
|
115
|
+
['BIZ', 'Belize'],
|
116
|
+
['BEN', 'Benin Republic'],
|
117
|
+
['BER', 'Bermuda'],
|
118
|
+
['BHU', 'Bhutan'],
|
119
|
+
['BOL', 'Bolivia'],
|
120
|
+
['BIH', 'Bosnia and Herzegovina'],
|
121
|
+
['BOT', 'Botswana'],
|
122
|
+
['BRA', 'Brazil'],
|
123
|
+
['IVB', 'British Virgin Islands'],
|
124
|
+
['BRU', 'Brunei Darussalam'],
|
125
|
+
['BUL', 'Bulgaria'],
|
126
|
+
['BUR', 'Burkina Faso'],
|
127
|
+
['BDI', 'Burundi'],
|
128
|
+
['CAM', 'Cambodia'],
|
129
|
+
['CMR', 'Cameroon'],
|
130
|
+
['CAN', 'Canada'],
|
131
|
+
['CHA', 'Chad'],
|
132
|
+
['CHI', 'Chile'],
|
133
|
+
['CHN', 'China'],
|
134
|
+
['TPE', 'Chinese Taipei'],
|
135
|
+
['COL', 'Colombia'],
|
136
|
+
['CRC', 'Costa Rica'],
|
137
|
+
['CRO', 'Croatia'],
|
138
|
+
['CUB', 'Cuba'],
|
139
|
+
['CYP', 'Cyprus'],
|
140
|
+
['CZE', 'Czech Republic'],
|
141
|
+
['DEN', 'Denmark'],
|
142
|
+
['DJI', 'Djibouti'],
|
143
|
+
['DOM', 'Dominican Republic'],
|
144
|
+
['ECU', 'Ecuador'],
|
145
|
+
['EGY', 'Egypt'],
|
146
|
+
['ESA', 'El Salvador'],
|
147
|
+
['ENG', 'England'],
|
148
|
+
['EST', 'Estonia'],
|
149
|
+
['ETH', 'Ethiopia'],
|
150
|
+
['FAI', 'Faroe Islands'],
|
151
|
+
['FIJ', 'Fiji'],
|
152
|
+
['FIN', 'Finland'],
|
153
|
+
['FRA', 'France'],
|
154
|
+
['GAB', 'Gabon'],
|
155
|
+
['GAM', 'Gambia'],
|
156
|
+
['GEO', 'Georgia'],
|
157
|
+
['GER', 'Germany'],
|
158
|
+
['GHA', 'Ghana'],
|
159
|
+
['GRE', 'Greece'],
|
160
|
+
['GUA', 'Guatemala'],
|
161
|
+
['GCI', 'Guernsey'],
|
162
|
+
['GUY', 'Guyana'],
|
163
|
+
['HAI', 'Haiti'],
|
164
|
+
['HON', 'Honduras'],
|
165
|
+
['HKG', 'Hong Kong'],
|
166
|
+
['HUN', 'Hungary'],
|
167
|
+
['ISL', 'Iceland'],
|
168
|
+
['IND', 'India'],
|
169
|
+
['INA', 'Indonesia'],
|
170
|
+
['IRI', 'Iran'],
|
171
|
+
['IRQ', 'Iraq'],
|
172
|
+
['IRL', 'Ireland'],
|
173
|
+
['ISR', 'Israel'],
|
174
|
+
['ITA', 'Italy'],
|
175
|
+
['CIV', 'Ivory Coast'],
|
176
|
+
['JAM', 'Jamaica'],
|
177
|
+
['JPN', 'Japan'],
|
178
|
+
['JCI', 'Jersey'],
|
179
|
+
['JOR', 'Jordan'],
|
180
|
+
['KAZ', 'Kazakhstan'],
|
181
|
+
['KEN', 'Kenya'],
|
182
|
+
['KUW', 'Kuwait'],
|
183
|
+
['KGZ', 'Kyrgyzstan'],
|
184
|
+
['LAT', 'Latvia'],
|
185
|
+
['LIB', 'Lebanon'],
|
186
|
+
['LBA', 'Libya'],
|
187
|
+
['LIE', 'Liechtenstein'],
|
188
|
+
['LTU', 'Lithuania'],
|
189
|
+
['LUX', 'Luxembourg'],
|
190
|
+
['MAC', 'Macau'],
|
191
|
+
['MKD', 'Macedonia', 'Former YUG Rep of Macedonia', 'Former Yugoslav Republic of Macedonia', 'FYROM'],
|
192
|
+
['MAD', 'Madagascar'],
|
193
|
+
['MAW', 'Malawi'],
|
194
|
+
['MAS', 'Malaysia'],
|
195
|
+
['MDV', 'Maldives'],
|
196
|
+
['MLI', 'Mali'],
|
197
|
+
['MLT', 'Malta'],
|
198
|
+
['MAU', 'Mauritania'],
|
199
|
+
['MRI', 'Mauritius'],
|
200
|
+
['MEX', 'Mexico'],
|
201
|
+
['MDA', 'Moldova'],
|
202
|
+
['MNC', 'Monaco'],
|
203
|
+
['MGL', 'Mongolia'],
|
204
|
+
['MNE', 'Montenegro'],
|
205
|
+
['MAR', 'Morocco'],
|
206
|
+
['MOZ', 'Mozambique'],
|
207
|
+
['MYA', 'Myanmar'],
|
208
|
+
['NAM', 'Namibia'],
|
209
|
+
['NEP', 'Nepal'],
|
210
|
+
['NED', 'Netherlands'],
|
211
|
+
['AHO', 'Netherlands Antilles'],
|
212
|
+
['NZL', 'New Zealand'],
|
213
|
+
['NCA', 'Nicaragua'],
|
214
|
+
['NGR', 'Nigeria'],
|
215
|
+
['NOR', 'Norway'],
|
216
|
+
['PAK', 'Pakistan'],
|
217
|
+
['PLW', 'Palau'],
|
218
|
+
['PLE', 'Palestine'],
|
219
|
+
['PAN', 'Panama'],
|
220
|
+
['PNG', 'Papua New Guinea'],
|
221
|
+
['PAR', 'Paraguay'],
|
222
|
+
['PER', 'Peru'],
|
223
|
+
['PHI', 'Philippines'],
|
224
|
+
['POL', 'Poland'],
|
225
|
+
['POR', 'Portugal'],
|
226
|
+
['PUR', 'Puerto Rico'],
|
227
|
+
['QAT', 'Qatar'],
|
228
|
+
['ROU', 'Romania'],
|
229
|
+
['RUS', 'Russia'],
|
230
|
+
['RWA', 'Rwanda'],
|
231
|
+
['SMR', 'San Marino'],
|
232
|
+
['STP', 'Sao Tome and Principe'],
|
233
|
+
['SCO', 'Scotland'],
|
234
|
+
['SEN', 'Senegal'],
|
235
|
+
['SRB', 'Serbia'],
|
236
|
+
['SEY', 'Seychelles'],
|
237
|
+
['SIN', 'Singapore'],
|
238
|
+
['SVK', 'Slovakia'],
|
239
|
+
['SLO', 'Slovenia'],
|
240
|
+
['SOM', 'Somalia'],
|
241
|
+
['RSA', 'South Africa'],
|
242
|
+
['KOR', 'South Korea'],
|
243
|
+
['ESP', 'Spain'],
|
244
|
+
['SRI', 'Sri Lanka'],
|
245
|
+
['SUD', 'Sudan'],
|
246
|
+
['SUR', 'Surinam'],
|
247
|
+
['SWE', 'Sweden'],
|
248
|
+
['SUI', 'Switzerland'],
|
249
|
+
['SYR', 'Syria'],
|
250
|
+
['TJK', 'Tajikistan'],
|
251
|
+
['TAN', 'Tanzania'],
|
252
|
+
['THA', 'Thailand'],
|
253
|
+
['TRI', 'Trinidad and Tobago'],
|
254
|
+
['TUN', 'Tunisia'],
|
255
|
+
['TUR', 'Turkey'],
|
256
|
+
['TKM', 'Turkmenistan'],
|
257
|
+
['UGA', 'Uganda'],
|
258
|
+
['UKR', 'Ukraine'],
|
259
|
+
['UAE', 'United Arab Emirates'],
|
260
|
+
['USA', 'United States of America'],
|
261
|
+
['URU', 'Uruguay'],
|
262
|
+
['ISV', 'US Virgin Islands'],
|
263
|
+
['UZB', 'Uzbekistan'],
|
264
|
+
['VEN', 'Venezuela'],
|
265
|
+
['VIE', 'Vietnam'],
|
266
|
+
['WLS', 'Wales'],
|
267
|
+
['YEM', 'Yemen'],
|
268
|
+
['ZAM', 'Zambia'],
|
269
|
+
['ZIM', 'Zimbabwe'],
|
270
|
+
]
|
271
|
+
end
|
272
|
+
end
|
data/lib/player.rb
CHANGED
@@ -132,10 +132,9 @@ All other attributes are unaffected.
|
|
132
132
|
|
133
133
|
# Federation. Is either unknown (nil) or a string containing at least three letters.
|
134
134
|
def fed=(fed)
|
135
|
-
|
136
|
-
@fed
|
137
|
-
|
138
|
-
raise "invalid federation (#{fed})" unless @fed.nil? || @fed.match(/[a-z]{3}/i)
|
135
|
+
obj = Federation.find(fed)
|
136
|
+
@fed = obj ? obj.code : nil
|
137
|
+
raise "invalid federation (#{fed})" if @fed.nil? && fed.to_s.strip.length > 0
|
139
138
|
end
|
140
139
|
|
141
140
|
# Chess title. Is either unknown (nil) or one of: _GM_, _IM_, _FM_, _CM_, _NM_,
|
@@ -143,6 +142,8 @@ All other attributes are unaffected.
|
|
143
142
|
def title=(title)
|
144
143
|
@title = title.to_s.strip.upcase
|
145
144
|
@title << 'M' if @title.match(/[A-LN-Z]$/)
|
145
|
+
@title = 'IM' if @title == 'M'
|
146
|
+
@title = 'WIM' if @title == 'WM'
|
146
147
|
@title = nil if @title == ''
|
147
148
|
raise "invalid chess title (#{title})" unless @title.nil? || @title.match(/^W?[GIFCN]M$/)
|
148
149
|
end
|
@@ -184,11 +185,13 @@ All other attributes are unaffected.
|
|
184
185
|
raise "invalid gender (#{gender})" unless @gender.nil? || @gender.match(/^[MF]$/)
|
185
186
|
end
|
186
187
|
|
187
|
-
# Add a result.
|
188
|
+
# Add a result. Don't use this method directly - use ICU::Tournament#add_result instead.
|
188
189
|
def add_result(result)
|
189
190
|
raise "invalid result" unless result.class == ICU::Result
|
190
191
|
raise "player number (#{@num}) is not matched to result player number (#{result.player})" unless @num == result.player
|
191
|
-
|
192
|
+
already = @results.find_all { |r| r.round == result.round }
|
193
|
+
return if already.size == 1 && already[0].eql?(result)
|
194
|
+
raise "round number (#{result.round}) of new result is not unique and new result is not the same as existing one" unless already.size == 0
|
192
195
|
@results << result
|
193
196
|
end
|
194
197
|
|
data/lib/result.rb
CHANGED
@@ -178,5 +178,12 @@ The _points_ read-only accessor always returns a floating point number: either 0
|
|
178
178
|
end
|
179
179
|
true
|
180
180
|
end
|
181
|
+
|
182
|
+
# Strict equality. True if the there's loose equality and also the rateablity is the same.
|
183
|
+
def eql?(other)
|
184
|
+
return true if equal?(other)
|
185
|
+
return false unless self == other
|
186
|
+
self.rateable == other.rateable
|
187
|
+
end
|
181
188
|
end
|
182
189
|
end
|
data/lib/tournament.rb
CHANGED
@@ -21,7 +21,7 @@ firstly adding all the players and then adding all the results.
|
|
21
21
|
t.add_result(ICU::Result.new(2, 20, 'W', :opponent => 30, :colour => 'B'))
|
22
22
|
t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))
|
23
23
|
|
24
|
-
[10, 20, 30].each { |n|
|
24
|
+
[10, 20, 30].each { |n| p = t.player(n); puts "#{p.points} #{p.name}" }
|
25
25
|
|
26
26
|
Would result in the following output.
|
27
27
|
|
@@ -63,16 +63,11 @@ raise an exception if the players it references through their tournament numbers
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
# Set the tournament federation. Can be
|
66
|
+
# Set the tournament federation. Can be _nil_.
|
67
67
|
def fed=(fed)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
else
|
72
|
-
raise "invalid tournament federation (#{fed})" unless fed.match(/^[-a-z ]+/i) && fed.length >= 3
|
73
|
-
fed.upcase! if fed.length == 3
|
74
|
-
@fed = fed
|
75
|
-
end
|
68
|
+
obj = Federation.find(fed)
|
69
|
+
@fed = obj ? obj.code : nil
|
70
|
+
raise "invalid tournament federation (#{fed})" if @fed.nil? && fed.to_s.strip.length > 0
|
76
71
|
end
|
77
72
|
|
78
73
|
# Set a start date in yyyy-mm-dd format.
|
@@ -180,5 +175,53 @@ raise an exception if the players it references through their tournament numbers
|
|
180
175
|
@player[result.opponent].add_result(reverse)
|
181
176
|
end
|
182
177
|
end
|
178
|
+
|
179
|
+
# Return true if the players ranking is consistent, false otherwise.
|
180
|
+
# The players ranking is consistent if:
|
181
|
+
# * every player has a rank
|
182
|
+
# * no two players have the same rank
|
183
|
+
# * the highest rank is 1
|
184
|
+
# * the lowest rank is equal to the total of players
|
185
|
+
def ranking_consistent?
|
186
|
+
# No two players can have the same rank.
|
187
|
+
ranks = Hash.new
|
188
|
+
@player.values.each do |p|
|
189
|
+
if p.rank
|
190
|
+
return false if ranks[p.rank]
|
191
|
+
ranks[p.rank] = p
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Every player has to have a rank.
|
196
|
+
return false unless ranks.size == @player.size
|
197
|
+
|
198
|
+
# The higest and lowest ranks respectively should be 1 and the number of players.
|
199
|
+
by_rank = @player.values.sort{ |a,b| a.rank <=> b.rank}
|
200
|
+
return false unless by_rank[0].rank == 1
|
201
|
+
return false unless by_rank[-1].rank == ranks.size
|
202
|
+
|
203
|
+
# If scores are ordered by ranks, they should go from highest to lowest.
|
204
|
+
if by_rank.size > 1
|
205
|
+
(1..by_rank.size-1).each do |i|
|
206
|
+
p1 = by_rank[i-1]
|
207
|
+
p2 = by_rank[i]
|
208
|
+
return false if p1.points < p2.points
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
true
|
213
|
+
end
|
214
|
+
|
215
|
+
# Rerank the tournament.
|
216
|
+
def rerank
|
217
|
+
@player.values.map{ |p| [p, p.points] }.sort do |a,b|
|
218
|
+
d = b[1] <=> a[1]
|
219
|
+
d = a[0].last_name <=> b[0].last_name if d == 0
|
220
|
+
d = a[0].first_name <=> b[0].first_name if d == 0
|
221
|
+
d
|
222
|
+
end.each_with_index do |v,i|
|
223
|
+
v[0].rank = i + 1
|
224
|
+
end
|
225
|
+
end
|
183
226
|
end
|
184
227
|
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
module ICU
|
2
|
+
class Tournament
|
3
|
+
|
4
|
+
=begin rdoc
|
5
|
+
|
6
|
+
== Krause
|
7
|
+
|
8
|
+
This is the {format}[http://www.fide.com/component/content/article/5-whats-news/2245-736-general-data-exchange-format-for-tournament-results]
|
9
|
+
used to submit tournament results to FIDE[http://www.fide.com] for rating.
|
10
|
+
|
11
|
+
Suppose, for example, that the following data is the file <em>tournament.tab</em>:
|
12
|
+
|
13
|
+
012 Fantasy Tournament
|
14
|
+
032 IRL
|
15
|
+
042 2009.09.09
|
16
|
+
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
|
17
|
+
001 1 w Mouse,Minerva 1900 USA 1234567 1928.05.15 1.0 2 2 b 0 3 w 1
|
18
|
+
001 2 m m Duck,Daffy 2200 IRL 7654321 1937.04.17 2.0 1 1 w 1 3 b 1
|
19
|
+
001 3 m g Mouse,Mickey 2600 USA 1726354 1928.05.15 0.0 3 1 b 0 2 w 0
|
20
|
+
|
21
|
+
This file can be parsed as follows.
|
22
|
+
|
23
|
+
data = open('tournament.tab') { |f| f.read }
|
24
|
+
parser = ICU::Tournament::Krause.new
|
25
|
+
tournament = parser.parse(data)
|
26
|
+
|
27
|
+
If the file is correctly specified, the return value from the <em>parse</em> method is an instance of
|
28
|
+
ICU::Tournament (rather than <em>nil</em>, which indicates an error). In this example the file is valid, so:
|
29
|
+
|
30
|
+
tournament.name # => "Fantasy Tournament"
|
31
|
+
tournament.start # => "2009-09-09"
|
32
|
+
tournament.rounds # => 3
|
33
|
+
tournament.fed # => "IRL"
|
34
|
+
tournament.players.size # => 9
|
35
|
+
|
36
|
+
A player can be retrieved from the tournament via the _players_ array or by sending a valid player number to the _player_ method.
|
37
|
+
|
38
|
+
minnie = tournament.player(1)
|
39
|
+
minnie.name # => "Mouse, Minerva"
|
40
|
+
minnie.points # => 1.0
|
41
|
+
minnie.results.size # => 2
|
42
|
+
|
43
|
+
daffy = tournament.player(2)
|
44
|
+
daffy.title # => "IM"
|
45
|
+
daffy.rating # => 2200
|
46
|
+
daffy.fed # => "IRL"
|
47
|
+
daffy.id # => 7654321
|
48
|
+
daffy.dob # => "1937-04-17"
|
49
|
+
|
50
|
+
Comments in the input file (lines that do not start with a valid data identification number) are available from the parser
|
51
|
+
instance via its _comments_ method. Note that these comments are reset evry time the instance is used to parse another file.
|
52
|
+
|
53
|
+
parser.comments # => "0123456789..."
|
54
|
+
|
55
|
+
A tournament can be serialized back to Krause format (the reverse of parsing) with the _serialize_ method.
|
56
|
+
|
57
|
+
krause = parser.serialize(tournament)
|
58
|
+
|
59
|
+
The following lists Krause data identification numbers, their description and, where available, their corresponding attributes in an ICU::Tournament instance.
|
60
|
+
|
61
|
+
[001 Player record] Use _players_ to get all players or _player_ with a player number to get a single instance.
|
62
|
+
[012 Name] Get or set with _name_. Free text. A tounament name is mandatory.
|
63
|
+
[013 Teams] Not implemented yet.
|
64
|
+
[022 City] Get or set with _city_. Free text.
|
65
|
+
[032 Federation] Get or set with _fed_. Getter returns either _nil_ or a three letter code. Setter can take various formats (see ICU::Federation).
|
66
|
+
[042 Start date] Get or set with _start_. Getter returns _yyyy-mm-dd_ format, but setter can use any reasonable date format. Start date is mandadory.
|
67
|
+
[052 End date] Get or set with _finish_. Returns either _yyyy-mm-dd_ format or _nil_ if not set. Like _start_, can be set with various date formats.
|
68
|
+
[062 Number of players] Not used. Treated as comment in parsed files. Can be determined from the size of the _players_ array.
|
69
|
+
[072 Number of rated players] Not used. Treated as comment in parsed files. Can be determined by analysing the array returned by _players_.
|
70
|
+
[082 Number of teams] Not used. Treated as comment in parsed files.
|
71
|
+
[092 Type of tournament] Get or set with _type_. Free text.
|
72
|
+
[102 Arbiter(s)] Get or set with -arbiter_. Free text.
|
73
|
+
[112 Deputy(ies)] Get or set with _deputy_. Free text.
|
74
|
+
[122 Time control] Get or set with _time_control_. Free text.
|
75
|
+
[132 Round dates] Not implemented yet.
|
76
|
+
|
77
|
+
=end
|
78
|
+
|
79
|
+
class Krause
|
80
|
+
attr_reader :error, :comments
|
81
|
+
|
82
|
+
# Parse Krause data returning a Tournament on success or a nil on failure.
|
83
|
+
# In the case of failure, an error message can be retrived via the <em>error</em> method.
|
84
|
+
def parse(krs)
|
85
|
+
begin
|
86
|
+
parse!(krs)
|
87
|
+
rescue => ex
|
88
|
+
@error = ex.message
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Parse Krause data returning a Tournament on success or raising an exception on error.
|
94
|
+
def parse!(krs)
|
95
|
+
@lineno = 0
|
96
|
+
@tournament = Tournament.new('Dummy', '2000-01-01')
|
97
|
+
@name_set, @start_set = false, false
|
98
|
+
@comments = ''
|
99
|
+
@results = Array.new
|
100
|
+
|
101
|
+
# Process all lines.
|
102
|
+
krs.each_line do |line|
|
103
|
+
@lineno += 1 # increment line number
|
104
|
+
line.strip! # remove leading and trailing white space
|
105
|
+
next if line == '' # skip blank lines
|
106
|
+
@line = line # remember this line for later
|
107
|
+
|
108
|
+
# Does it havea DIN or is it just a comment?
|
109
|
+
if @line.match(/^(\d{3}) (.*)$/)
|
110
|
+
din = $1 # data identification number (DIN)
|
111
|
+
@data = $2 # the data after the DIN
|
112
|
+
else
|
113
|
+
add_comment
|
114
|
+
next
|
115
|
+
end
|
116
|
+
|
117
|
+
# Process the line given the DIN.
|
118
|
+
begin
|
119
|
+
case din
|
120
|
+
when '001' then add_player # player and results record
|
121
|
+
when '012' then set_name # name (mandatory)
|
122
|
+
when '013' then add_comment # team name and members (not implemented yet)
|
123
|
+
when '022' then @tournament.city = @data # city
|
124
|
+
when '032' then @tournament.fed = @data # federation
|
125
|
+
when '042' then set_start # start date (mandatory)
|
126
|
+
when '052' then @tournament.finish = @data # end date
|
127
|
+
when '062' then add_comment # number of players (calculated from 001 records)
|
128
|
+
when '072' then add_comment # number of rated players (calculated from 001 records)
|
129
|
+
when '082' then add_comment # number of teams (calculated from 013 records)
|
130
|
+
when '092' then @tournament.type = @data # type of tournament
|
131
|
+
when '102' then @tournament.arbiter = @data # arbiter(s)
|
132
|
+
when '112' then @tournament.deputy = @data # deputy(ies)
|
133
|
+
when '122' then @tournament.time_control = @data # time control
|
134
|
+
when '132' then add_comment # round dates (not implemented yet)
|
135
|
+
else raise "invalid DIN #{din}"
|
136
|
+
end
|
137
|
+
rescue => err
|
138
|
+
raise err.class, "line #{@lineno}: #{err.message}", err.backtrace
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Now that all players are present, add the results to the tournament.
|
143
|
+
@results.each do |r|
|
144
|
+
lineno, player, data, result = r
|
145
|
+
begin
|
146
|
+
@tournament.add_result(result)
|
147
|
+
rescue => err
|
148
|
+
raise "line #{lineno}, player #{player}, result '#{data}': #{err.message}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Validate the data now that we have everything.
|
153
|
+
validate
|
154
|
+
|
155
|
+
@tournament
|
156
|
+
end
|
157
|
+
|
158
|
+
# Serialise a tournament back into Krause format.
|
159
|
+
def serialize(t)
|
160
|
+
return nil unless t.class == ICU::Tournament;
|
161
|
+
krause = ''
|
162
|
+
krause << "012 #{t.name}\n"
|
163
|
+
krause << "022 #{t.city}\n" if t.city
|
164
|
+
krause << "032 #{t.fed}\n" if t.fed
|
165
|
+
krause << "042 #{t.start}\n"
|
166
|
+
krause << "052 #{t.finish}\n" if t.finish
|
167
|
+
krause << "092 #{t.type}\n" if t.type
|
168
|
+
krause << "102 #{t.arbiter}\n" if t.arbiter
|
169
|
+
krause << "112 #{t.deputy}\n" if t.deputy
|
170
|
+
krause << "122 #{t.time_control}\n" if t.time_control
|
171
|
+
t.players.each{ |p| krause << p.to_krause(@tournament.rounds) }
|
172
|
+
krause
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def set_name
|
178
|
+
@tournament.name = @data
|
179
|
+
@name_set = true
|
180
|
+
end
|
181
|
+
|
182
|
+
def set_start
|
183
|
+
@tournament.start = @data
|
184
|
+
@start_set = true
|
185
|
+
end
|
186
|
+
|
187
|
+
def add_player
|
188
|
+
raise "player record less than minimum length" if @line.length < 99
|
189
|
+
|
190
|
+
# Player details.
|
191
|
+
num = @data[0, 4]
|
192
|
+
nam = Name.new(@data[10, 32])
|
193
|
+
opt =
|
194
|
+
{
|
195
|
+
:gender => @data[5, 1],
|
196
|
+
:title => @data[6, 3],
|
197
|
+
:rating => @data[44, 4],
|
198
|
+
:fed => @data[49, 3],
|
199
|
+
:id => @data[53, 11],
|
200
|
+
:dob => @data[65, 10],
|
201
|
+
:rank => @data[81, 4],
|
202
|
+
}
|
203
|
+
player = Player.new(nam.first, nam.last, num, opt)
|
204
|
+
@tournament.add_player(player)
|
205
|
+
|
206
|
+
# Results.
|
207
|
+
points = @data[77, 4].strip
|
208
|
+
points = points == '' ? nil : points.to_f
|
209
|
+
index = 87
|
210
|
+
round = 1
|
211
|
+
total = 0.0
|
212
|
+
while @data.length >= index + 8
|
213
|
+
total+= add_result(round, player.num, @data[index, 8])
|
214
|
+
index+= 10
|
215
|
+
round+= 1
|
216
|
+
end
|
217
|
+
raise "declared points total (#{points}) does not agree with total from summed results (#{total})" if points && points != total
|
218
|
+
end
|
219
|
+
|
220
|
+
def add_result(round, player, data)
|
221
|
+
return 0.0 if data.strip! == '' # no result for this round
|
222
|
+
raise "invalid result '#{data}'" unless data.match(/^(0{1,4}|[1-9]\d{0,3}) (w|b|-) (1|0|=|\+|-)$/)
|
223
|
+
opponent = $1.to_i
|
224
|
+
colour = $2
|
225
|
+
score = $3
|
226
|
+
options = Hash.new
|
227
|
+
options[:opponent] = opponent unless opponent == 0
|
228
|
+
options[:colour] = colour unless colour == '-'
|
229
|
+
options[:rateable] = false unless score.match(/^(1|0|=)$/)
|
230
|
+
result = Result.new(round, player, score, options)
|
231
|
+
@results << [@lineno, player, data, result]
|
232
|
+
result.points
|
233
|
+
end
|
234
|
+
|
235
|
+
def add_comment
|
236
|
+
@comments << @line
|
237
|
+
@comments << "\n"
|
238
|
+
end
|
239
|
+
|
240
|
+
def validate
|
241
|
+
# Certain attributes are mandatory.
|
242
|
+
raise "tournament name missing" unless @name_set
|
243
|
+
raise "tournament start date missing" unless @start_set
|
244
|
+
|
245
|
+
# There must be at least two players.
|
246
|
+
raise "minimum number of players is 2" if @tournament.players.length < 2
|
247
|
+
|
248
|
+
# Every player must have at least one result.
|
249
|
+
@tournament.players.each { |p| raise "player #{p.num} has no results" if p.results.size == 0 }
|
250
|
+
|
251
|
+
# Rerank the tournament if there are no ranking values or if there are but they're not consistent.
|
252
|
+
@tournament.rerank unless @tournament.ranking_consistent?
|
253
|
+
|
254
|
+
# Set the number of rounds.
|
255
|
+
@tournament.rounds = @tournament.players.inject(0) do |pa, p|
|
256
|
+
pm = p.results.inject(0){ |ra, r| ra < r.round ? r.round : ra }
|
257
|
+
pa < pm ? pm : pa
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class Player
|
264
|
+
# Format a player's 001 record as it would appear in a Krause formatted file (including the final newline).
|
265
|
+
def to_krause(rounds)
|
266
|
+
krause = '001'
|
267
|
+
krause << sprintf(' %4d', @num)
|
268
|
+
krause << sprintf(' %1s', case @gender; when 'M': 'm'; when 'F': 'w'; else ''; end)
|
269
|
+
krause << sprintf(' %2s', case @title; when nil: ''; when 'IM': 'm'; when 'WIM': 'wm'; else @title[0, @title.length-1].downcase; end)
|
270
|
+
krause << sprintf(' %-33s', "#{@last_name},#{@first_name}")
|
271
|
+
krause << sprintf(' %4s', @rating)
|
272
|
+
krause << sprintf(' %3s', @fed)
|
273
|
+
krause << sprintf(' %11s', @id)
|
274
|
+
krause << sprintf(' %10s', @dob)
|
275
|
+
krause << sprintf(' %4.1f', points)
|
276
|
+
krause << sprintf(' %4s', @rank)
|
277
|
+
(1..rounds).each do |r|
|
278
|
+
result = find_result(r)
|
279
|
+
krause << sprintf(' %8s', result ? result.to_krause : '')
|
280
|
+
end
|
281
|
+
krause << "\n"
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
class Result
|
286
|
+
# Format a player's result as it would appear in a Krause formatted file (exactly 8 characters long, including leading whitespace).
|
287
|
+
def to_krause
|
288
|
+
return ' ' * 8 if !@opponent && !@colour && @score == 'L'
|
289
|
+
krause = sprintf('%4s ', @opponent || '0000')
|
290
|
+
krause << sprintf('%1s ', @colour ? @colour.downcase : '-')
|
291
|
+
krause << case @score; when 'W': '1'; when 'L': '0'; else '='; end if @rateable
|
292
|
+
krause << case @score; when 'W': '+'; when 'L': '-'; else '='; end if !@rateable
|
293
|
+
krause
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
module ICU
|
4
|
+
describe Federation do
|
5
|
+
context "#find using codes" do
|
6
|
+
it "should find a federation given a valid code" do
|
7
|
+
fed = Federation.find('IRL')
|
8
|
+
fed.code.should == 'IRL'
|
9
|
+
fed.name.should == 'Ireland'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should find a federation from code case insensitively" do
|
13
|
+
fed = Federation.find('rUs')
|
14
|
+
fed.code.should == 'RUS'
|
15
|
+
fed.name.should == 'Russia'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should find a federation despite irrelevant whitespace" do
|
19
|
+
fed = Federation.find(' mex ')
|
20
|
+
fed.code.should == 'MEX'
|
21
|
+
fed.name.should == 'Mexico'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return nil for an invalid code" do
|
25
|
+
Federation.find('XYZ').should be_nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "#find using names" do
|
30
|
+
it "should find a federation given a valid name" do
|
31
|
+
fed = Federation.find('England')
|
32
|
+
fed.code.should == 'ENG'
|
33
|
+
fed.name.should == 'England'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should find a federation from name case insensitively" do
|
37
|
+
fed = Federation.find('franCE')
|
38
|
+
fed.code.should == 'FRA'
|
39
|
+
fed.name.should == 'France'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should not be fooled by irrelevant whitespace" do
|
43
|
+
fed = Federation.find(' united states of america ')
|
44
|
+
fed.code.should == 'USA'
|
45
|
+
fed.name.should == 'United States of America'
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return nil for an invalid name" do
|
49
|
+
Federation.find('Mordor').should be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "#find using parts of names" do
|
54
|
+
it "should find a federation given a substring which is unique and at least 4 characters" do
|
55
|
+
fed = Federation.find('bosni')
|
56
|
+
fed.code.should == 'BIH'
|
57
|
+
fed.name.should == 'Bosnia and Herzegovina'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not be fooled by irrelevant whitespace" do
|
61
|
+
fed = Federation.find(' arab EMIRATES ')
|
62
|
+
fed.code.should == 'UAE'
|
63
|
+
fed.name.should == 'United Arab Emirates'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not find a federation if the substring matches more than one" do
|
67
|
+
Federation.find('land').should be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should return nil for any string smaller in length than 3" do
|
71
|
+
Federation.find('ze').should be_nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "#find federations with alternative names" do
|
76
|
+
it "should find Macedonia multiple ways" do
|
77
|
+
Federation.find('MKD').name.should == 'Macedonia'
|
78
|
+
Federation.find('FYROM').name.should == 'Macedonia'
|
79
|
+
Federation.find('macedoni').name.should == 'Macedonia'
|
80
|
+
Federation.find('Macedonia').name.should == 'Macedonia'
|
81
|
+
Federation.find('former YUG Rep').name.should == 'Macedonia'
|
82
|
+
Federation.find('Republic of Macedonia').name.should == 'Macedonia'
|
83
|
+
Federation.find('former yugoslav republic').name.should == 'Macedonia'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "#find with alternative inputs" do
|
88
|
+
it "should behave robustly with completely invalid inputs" do
|
89
|
+
Federation.find().should be_nil
|
90
|
+
Federation.find(nil).should be_nil
|
91
|
+
Federation.find('').should be_nil
|
92
|
+
Federation.find(1).should be_nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "#new is private" do
|
97
|
+
it "#new cannot be called directly" do
|
98
|
+
lambda { Federation.new('IRL', 'Ireland') }.should raise_error(/private method/)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "documentation examples" do
|
103
|
+
it "should all be correct for valid input" do
|
104
|
+
ICU::Federation.find('IRL').name.should == 'Ireland'
|
105
|
+
ICU::Federation.find('IRL').code.should == 'IRL'
|
106
|
+
ICU::Federation.find('rUs').code.should == 'RUS'
|
107
|
+
ICU::Federation.find('ongoli').name.should == 'Mongolia'
|
108
|
+
ICU::Federation.find(' united states ').code.should == 'USA'
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should return nil for invalid input" do
|
112
|
+
ICU::Federation.find('ZYX').should be_nil
|
113
|
+
ICU::Federation.find('land').should be_nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/spec/player_spec.rb
CHANGED
@@ -83,7 +83,7 @@ module ICU
|
|
83
83
|
|
84
84
|
it "should consist of at least three letters" do
|
85
85
|
Player.new('Gary', 'Kasparov', 1, :fed => 'RUS').fed.should == 'RUS'
|
86
|
-
Player.new('Mark', 'Orr', 3, :fed => ' Ireland ').fed.should == '
|
86
|
+
Player.new('Mark', 'Orr', 3, :fed => ' Ireland ').fed.should == 'IRL'
|
87
87
|
lambda { Player.new('Danny', 'Kopec', 3, :fed => 'US') }.should raise_error(/invalid federation/)
|
88
88
|
end
|
89
89
|
end
|
@@ -97,9 +97,11 @@ module ICU
|
|
97
97
|
it "should be one of national, candidate, FIDE, international or grand master" do
|
98
98
|
Player.new('Gary', 'Kasparov', 1, :title => 'GM').title.should == 'GM'
|
99
99
|
Player.new('Mark', 'Orr', 2, :title => ' im ').title.should == 'IM'
|
100
|
+
Player.new('Mark', 'Quinn', 2, :title => 'm').title.should == 'IM'
|
100
101
|
Player.new('Pia', 'Cramling', 3, :title => ' wg ').title.should == 'WGM'
|
101
102
|
Player.new('Philip', 'Short', 4, :title => 'F ').title.should == 'FM'
|
102
103
|
Player.new('Gearoidin', 'Ui Laighleis', 5, :title => 'wc').title.should == 'WCM'
|
104
|
+
Player.new('Gearoidin', 'Ui Laighleis', 7, :title => 'wm').title.should == 'WIM'
|
103
105
|
Player.new('Eamon', 'Keogh', 6, :title => 'nm').title.should == 'NM'
|
104
106
|
lambda { Player.new('Mark', 'Orr', 3, :title => 'Dr') }.should raise_error(/invalid chess title/)
|
105
107
|
end
|
data/spec/result_spec.rb
CHANGED
@@ -150,6 +150,7 @@ module ICU
|
|
150
150
|
end
|
151
151
|
|
152
152
|
it "should be equal if the round, player numbers, result and colour all match" do
|
153
|
+
(@r1 == @r1).should be_true
|
153
154
|
(@r1 == @r2).should be_true
|
154
155
|
end
|
155
156
|
|
@@ -161,5 +162,21 @@ module ICU
|
|
161
162
|
(@r1 == @r7).should be_false
|
162
163
|
end
|
163
164
|
end
|
165
|
+
|
166
|
+
context "strict equality" do
|
167
|
+
before(:each) do
|
168
|
+
@r1 = Result.new(1, 1, 0, :opponent => 2, :colour => 'W')
|
169
|
+
@r2 = Result.new(1, 1, 0, :opponent => 2, :colour => 'W')
|
170
|
+
@r3 = Result.new(1, 1, 0, :opponent => 2, :colour => 'W', :rateable => false)
|
171
|
+
@r4 = Result.new(2, 1, 0, :opponent => 2, :colour => 'W')
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should only be equal if everything is the same" do
|
175
|
+
@r1.eql?(@r1).should be_true
|
176
|
+
@r1.eql?(@r2).should be_true
|
177
|
+
@r1.eql?(@r3).should be_false
|
178
|
+
@r1.eql?(@r4).should be_false
|
179
|
+
end
|
180
|
+
end
|
164
181
|
end
|
165
182
|
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
module ICU
|
4
|
+
class Tournament
|
5
|
+
describe Krause do
|
6
|
+
def check_player(num, first, last, other={})
|
7
|
+
p = @t.player(num)
|
8
|
+
p.first_name.should == first
|
9
|
+
p.last_name.should == last
|
10
|
+
p.gender.should == other[:gender]
|
11
|
+
p.title.should == other[:title]
|
12
|
+
p.rating.should == other[:rating]
|
13
|
+
p.fed.should == other[:fed]
|
14
|
+
p.id.should == other[:id]
|
15
|
+
p.dob.should == other[:dob]
|
16
|
+
p.rank.should == other[:rank]
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_results(num, results, points)
|
20
|
+
p = @t.player(num)
|
21
|
+
p.results.size.should == results
|
22
|
+
p.points.should == points
|
23
|
+
end
|
24
|
+
|
25
|
+
context "a typical tournament" do
|
26
|
+
before(:all) do
|
27
|
+
krause = <<KRAUSE
|
28
|
+
012 Las Vegas National Open
|
29
|
+
022 Las Vegas
|
30
|
+
032 USA
|
31
|
+
042 2008.06.07
|
32
|
+
052 2008.06.10
|
33
|
+
062 3
|
34
|
+
072 3
|
35
|
+
082 1
|
36
|
+
092 All-Play-All
|
37
|
+
102 Hans Scmidt
|
38
|
+
112 Gerry Graham, Herbert Scarry
|
39
|
+
122 60 in 2hr, 30 in 1hr, rest in 1hr
|
40
|
+
013 Coaching Team 1 2
|
41
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2
|
42
|
+
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
|
43
|
+
132 08.02.01 08.02.02 08.02.03
|
44
|
+
001 1 w Ui Laighleis,Gearoidin 1985 IRL 2501171 1964.06.10 1.0 2 2 b 0 3 w 1
|
45
|
+
001 2 m m Orr,Mark 2258 IRL 2500035 1955.11.09 2.0 1 1 w 1 3 b 1
|
46
|
+
001 3 m g Bologan,Viktor 2663 MDA 13900048 1971.01.01 0.0 3 1 b 0 2 w 0
|
47
|
+
KRAUSE
|
48
|
+
@p = Krause.new
|
49
|
+
@t = @p.parse!(krause)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should have a name, city and federation" do
|
53
|
+
@t.name.should == 'Las Vegas National Open'
|
54
|
+
@t.city.should == 'Las Vegas'
|
55
|
+
@t.fed.should == 'USA'
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have start and end dates" do
|
59
|
+
@t.start.should == '2008-06-07'
|
60
|
+
@t.finish.should == '2008-06-10'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have a number of rounds, a type and a time control" do
|
64
|
+
@t.rounds.should == 3
|
65
|
+
@t.type.should == 'All-Play-All'
|
66
|
+
@t.time_control.should == '60 in 2hr, 30 in 1hr, rest in 1hr'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have an arbiter and deputies" do
|
70
|
+
@t.arbiter.should == 'Hans Scmidt'
|
71
|
+
@t.deputy.should == 'Gerry Graham, Herbert Scarry'
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should have players and their details" do
|
75
|
+
@t.should have(3).players
|
76
|
+
check_player(1, 'Gearoidin', 'Ui Laighleis', :gender => 'F', :rating => 1985, :fed => 'IRL', :id => 2501171, :dob => '1964-06-10', :rank => 2)
|
77
|
+
check_player(2, 'Mark', 'Orr', :gender => 'M', :rating => 2258, :fed => 'IRL', :id => 2500035, :dob => '1955-11-09', :rank => 1, :title => 'IM')
|
78
|
+
check_player(3, 'Viktor', 'Bologan', :gender => 'M', :rating => 2663, :fed => 'MDA', :id => 13900048, :dob => '1971-01-01', :rank => 3, :title => 'GM')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should have correct results for each player" do
|
82
|
+
check_results(1, 2, 1.0)
|
83
|
+
check_results(2, 2, 2.0)
|
84
|
+
check_results(3, 2, 0.0)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "the parser should retain comment lines" do
|
88
|
+
comments = <<COMMENTS
|
89
|
+
062 3
|
90
|
+
072 3
|
91
|
+
082 1
|
92
|
+
013 Coaching Team 1 2
|
93
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2
|
94
|
+
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
|
95
|
+
132 08.02.01 08.02.02 08.02.03
|
96
|
+
COMMENTS
|
97
|
+
@p.comments.should == comments
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "the documentation example" do
|
102
|
+
before(:all) do
|
103
|
+
krause = <<KRAUSE
|
104
|
+
012 Fantasy Tournament
|
105
|
+
032 IRL
|
106
|
+
042 2009.09.09
|
107
|
+
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
|
108
|
+
001 1 w Mouse,Minerva 1900 USA 1234567 1928.05.15 1.0 2 2 b 0 3 w 1
|
109
|
+
001 2 m m Duck,Daffy 2200 IRL 7654321 1937.04.17 2.0 1 1 w 1 3 b 1
|
110
|
+
001 3 m g Mouse,Mickey 2600 USA 1726354 1928.05.15 0.0 3 1 b 0 2 w 0
|
111
|
+
KRAUSE
|
112
|
+
@p = Krause.new
|
113
|
+
@t = @p.parse!(krause)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should have a name and federation" do
|
117
|
+
@t.name.should == 'Fantasy Tournament'
|
118
|
+
@t.fed.should == 'IRL'
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should have a startdates" do
|
122
|
+
@t.start.should == '2009-09-09'
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should have a number of rounds" do
|
126
|
+
@t.rounds.should == 3
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should have players and their details" do
|
130
|
+
@t.should have(3).players
|
131
|
+
check_player(1, 'Minerva', 'Mouse', :gender => 'F', :rating => 1900, :fed => 'USA', :id => 1234567, :dob => '1928-05-15', :rank => 2)
|
132
|
+
check_player(2, 'Daffy', 'Duck', :gender => 'M', :rating => 2200, :fed => 'IRL', :id => 7654321, :dob => '1937-04-17', :rank => 1, :title => 'IM')
|
133
|
+
check_player(3, 'Mickey', 'Mouse', :gender => 'M', :rating => 2600, :fed => 'USA', :id => 1726354, :dob => '1928-05-15', :rank => 3, :title => 'GM')
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should have correct results for each player" do
|
137
|
+
check_results(1, 2, 1.0)
|
138
|
+
check_results(2, 2, 2.0)
|
139
|
+
check_results(3, 2, 0.0)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "the parser should retain comment lines" do
|
143
|
+
@p.comments.should == "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "serialisation" do
|
148
|
+
before(:all) do
|
149
|
+
@krause = <<KRAUSE
|
150
|
+
012 Las Vegas National Open
|
151
|
+
022 Las Vegas
|
152
|
+
032 USA
|
153
|
+
042 2008-06-07
|
154
|
+
052 2008-06-10
|
155
|
+
092 All-Play-All
|
156
|
+
102 Hans Scmidt
|
157
|
+
112 Gerry Graham, Herbert Scarry
|
158
|
+
122 60 in 2hr, 30 in 1hr, rest in 1hr
|
159
|
+
001 1 w Ui Laighleis,Gearoidin 1985 IRL 2501171 1964-06-10 2.0 2 2 b 0 3 w + 4 b 1
|
160
|
+
001 2 m m Orr,Mark 2258 IRL 2500035 1955-11-09 2.5 1 1 w 1 0000 - = 3 b 1
|
161
|
+
001 3 m g Bologan,Viktor 2663 MDA 13900048 1971-01-01 0.0 4 1 b - 2 w 0
|
162
|
+
001 4 wg Cramling,Pia 2500 SWE 1700030 1963-04-23 0.5 3 0000 - = 1 w 0
|
163
|
+
KRAUSE
|
164
|
+
@p = Krause.new
|
165
|
+
@t = @p.parse!(@krause)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should serialize back to the original if the input is fully canonicalised" do
|
169
|
+
@p.serialize(@t).should == @krause
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should return nil on invalid input" do
|
173
|
+
@p.serialize('Rubbish').should be_nil
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "auto-ranking" do
|
178
|
+
before(:all) do
|
179
|
+
@krause = <<KRAUSE
|
180
|
+
012 Las Vegas National Open
|
181
|
+
042 2008-06-07
|
182
|
+
001 1 w Ui Laighleis,Gearoidin 1985 IRL 2501171 1964-06-10 1.0 2 b 0 3 w 1
|
183
|
+
001 2 m m Orr,Mark 2258 IRL 2500035 1955-11-09 2.0 1 w 1 3 b 1
|
184
|
+
001 3 m g Bologan,Viktor 2663 MDA 13900048 1971-01-01 0.0 1 b 0 2 w 0
|
185
|
+
KRAUSE
|
186
|
+
@p = Krause.new
|
187
|
+
@t = @p.parse!(@krause)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should have rankings automatically set" do
|
191
|
+
@t.player(1).rank.should == 2
|
192
|
+
@t.player(2).rank.should == 1
|
193
|
+
@t.player(3).rank.should == 3
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "errors" do
|
198
|
+
before(:each) do
|
199
|
+
@k = <<KRAUSE
|
200
|
+
012 Gonzaga Classic
|
201
|
+
022 Dublin
|
202
|
+
032 IRL
|
203
|
+
042 2008-02-01
|
204
|
+
052 2008-02-03
|
205
|
+
062 12
|
206
|
+
092 Swiss
|
207
|
+
102 Michael Germaine, mlgermaine@eircom.net
|
208
|
+
122 120 minutes per player per game
|
209
|
+
001 1 Griffiths,Ryan Rhys IRL 2502054 4.0 1 0000 - = 3 b 1 8 w 1 5 b = 7 w 1
|
210
|
+
001 2 Hotak,Marian SVK 14909677 3.5 2 3 w 0 6 b = 11 w 1 8 b 1 5 w 1
|
211
|
+
001 3 Duffy,Seamus IRL 3.0 3 2 b 1 1 w 0 4 w 1 6 b = 8 w =
|
212
|
+
001 4 Cafolla,Peter IRL 2500884 3.0 4 7 b 1 5 w = 3 b 0 11 b + 6 w =
|
213
|
+
001 5 Ferry,Edward SCO 2461587 3.0 5 10 b 1 4 b = 9 w 1 1 w = 2 b 0
|
214
|
+
001 6 Boyle,Bernard IRL 2501830 3.0 6 12 b = 2 w = 10 b 1 3 w = 4 b =
|
215
|
+
001 7 McCarthy,Tim IRL 2500710 2.5 7 4 w 0 10 w = 12 b + 9 b 1 1 b 0
|
216
|
+
001 8 Benson,Oisin P. IRL 2501821 2.0 8 0000 - = 11 w 1 1 b 0 2 w 0 3 b =
|
217
|
+
001 9 Murray,David B. IRL 2501511 2.0 9 11 b = 12 w + 5 b 0 7 w 0 10 w =
|
218
|
+
001 10 Moser,Philippe SUI 1308041 1.5 10 5 w 0 7 b = 6 w 0 0000 - = 9 b =
|
219
|
+
001 11 Barbosa,Paulo POR 1904612 1.5 11 9 w = 8 b 0 2 b 0 4 w - 0000 - +
|
220
|
+
001 12 McCabe,Darren IRL 2500760 0.5 12 6 w = 9 b - 7 w -
|
221
|
+
KRAUSE
|
222
|
+
@p = Krause.new
|
223
|
+
end
|
224
|
+
|
225
|
+
it "the unaltered example is valid Krause" do
|
226
|
+
t = @p.parse(@k).should be_instance_of(Tournament)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "removing the line on which the tournament name is specified should cause an error" do
|
230
|
+
@k.sub!('012 Gonzaga Classic', '')
|
231
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/name missing/)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "blanking the tournament name should cause an error" do
|
235
|
+
@k.sub!('Gonzaga Classic', '')
|
236
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/name missing/)
|
237
|
+
end
|
238
|
+
|
239
|
+
it "blanking the start date should cause an error" do
|
240
|
+
@k.sub!('2008-02-01', '')
|
241
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/start date missing/)
|
242
|
+
end
|
243
|
+
|
244
|
+
it "creating a duplicate player number should cause an error" do
|
245
|
+
@k.sub!(' 2 ', ' 1 ')
|
246
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/player number/)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "creating a duplicate rank number should not cause an error becuse the tournament will be reranked" do
|
250
|
+
@k.sub!('4.0 1', '4.0 2')
|
251
|
+
t = @p.parse!(@k)
|
252
|
+
t.player(1).rank.should == 1
|
253
|
+
end
|
254
|
+
|
255
|
+
it "referring to a non-existant player number should cause an error" do
|
256
|
+
@k.sub!(' 3 b 1', '33 b 1')
|
257
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/opponent number/)
|
258
|
+
end
|
259
|
+
|
260
|
+
it "inconsistent colours should cause an error" do
|
261
|
+
@k.sub!('3 b 1', '3 w 1')
|
262
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/result/)
|
263
|
+
end
|
264
|
+
|
265
|
+
it "inconsistent scores should cause an error" do
|
266
|
+
@k.sub!('3 b 1', '3 b =')
|
267
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/result/)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "inconsistent totals should cause an error" do
|
271
|
+
@k.sub!('4.0', '4.5')
|
272
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/total/)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "invalid federations should cause an error" do
|
276
|
+
@k.sub!('SCO', 'XYZ')
|
277
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/federation/)
|
278
|
+
end
|
279
|
+
|
280
|
+
it "removing any player that somebody else has played should cause an error" do
|
281
|
+
@k.sub!(/^001 12.*$/, '')
|
282
|
+
lambda { t = @p.parse!(@k) }.should raise_error(/opponent/)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sanichi-chess_icu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Orr
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-04
|
12
|
+
date: 2009-05-04 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -38,17 +38,21 @@ files:
|
|
38
38
|
- Rakefile
|
39
39
|
- VERSION.yml
|
40
40
|
- lib/chess_icu.rb
|
41
|
+
- lib/federation.rb
|
41
42
|
- lib/name.rb
|
42
43
|
- lib/player.rb
|
43
44
|
- lib/result.rb
|
44
45
|
- lib/tournament.rb
|
45
46
|
- lib/tournament_fcsv.rb
|
47
|
+
- lib/tournament_krause.rb
|
46
48
|
- lib/util.rb
|
49
|
+
- spec/federation_spec.rb
|
47
50
|
- spec/name_spec.rb
|
48
51
|
- spec/player_spec.rb
|
49
52
|
- spec/result_spec.rb
|
50
53
|
- spec/spec_helper.rb
|
51
54
|
- spec/tournament_fcsv_spec.rb
|
55
|
+
- spec/tournament_krause_spec.rb
|
52
56
|
- spec/tournament_spec.rb
|
53
57
|
- spec/util_spec.rb
|
54
58
|
has_rdoc: true
|
@@ -78,10 +82,12 @@ signing_key:
|
|
78
82
|
specification_version: 2
|
79
83
|
summary: For parsing files of chess tournament data into ruby classes.
|
80
84
|
test_files:
|
85
|
+
- spec/federation_spec.rb
|
81
86
|
- spec/name_spec.rb
|
82
87
|
- spec/player_spec.rb
|
83
88
|
- spec/result_spec.rb
|
84
89
|
- spec/spec_helper.rb
|
85
90
|
- spec/tournament_fcsv_spec.rb
|
91
|
+
- spec/tournament_krause_spec.rb
|
86
92
|
- spec/tournament_spec.rb
|
87
93
|
- spec/util_spec.rb
|