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 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.csv') { |f| f.read }
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 parse method returns an object of type ICU::Tournament. On error, it returns _nil_ and an error message
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 file formats supported in the current version are:
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 other types of files such as SwissPerfect and FIDE's Krause format.
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
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 2
3
- :patch: 10
4
2
  :major: 0
3
+ :minor: 3
4
+ :patch: 0
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
- %w{util name player result tournament}.each { |file| require "#{dir}/#{file}" }
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
- @fed = fed.to_s.strip
136
- @fed.upcase! if @fed.length == 3
137
- @fed = nil if @fed == ''
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. Use the same name method in ICU::Tournament instead.
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
- raise "round number (#{result.round}) of new result should be unique" unless @results.map { |r| r.round }.grep(result.round).size == 0
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| puts "#{t.player(n).points} #{t.player(n).name}" }
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 _nil.
66
+ # Set the tournament federation. Can be _nil_.
67
67
  def fed=(fed)
68
- fed = fed.to_s.strip
69
- if fed == ''
70
- @fed = nil
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 == 'Ireland'
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.2.10
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-26 00:00:00 -07:00
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