Class: ICU::Tournament::ForeignCSV
- Inherits:
-
Object
- Object
- ICU::Tournament::ForeignCSV
- Defined in:
- lib/icu_tournament/tournament_fcsv.rb
Overview
This is a format (specification) used by the ICU for players to submit their individual results in foreign tournaments for domestic rating.
Suppose, for example, that the following data is the file tournament.csv:
Event,"Isle of Man Masters, 2007"
Start,2007-09-22
End,2007-09-30
Rounds,9
Website,http://www.bcmchess.co.uk/monarch2007/
Player,456,Fox,Anthony
1,0,B,Taylor,Peter P.,2209,,ENG
2,=,W,Nadav,Egozi,2205,,ISR
3,=,B,Cafolla,Peter,2048,,IRL
4,1,W,Spanton,Tim R.,1982,,ENG
5,1,B,Grant,Alan,2223,,SCO
6,0,-
7,=,W,Walton,Alan J.,2223,,ENG
8,0,B,Bannink,Bernard,2271,FM,NED
9,=,W,Phillips,Roy,2271,,MRI
Total,4
This file can be parsed as follows.
parser = ICU::Tournament::ForeignCSV.new
tournament = parser.parse_file('tournament.csv')
If the file is correctly specified, the return value from the parse_file method is an instance of ICU::Tournament (rather than nil, which indicates an error). In this example the file is valid, so:
tournament.name # => "Isle of Man Masters, 2007"
tournament.start # => "2007-09-22"
tournament.finish # => "2007-09-30"
tournament.rounds # => 9
tournament.website # => "http://www.bcmchess.co.uk/monarch2007/"
The main player (the ICU player whose results are being reported for rating) played 9 rounds but only 8 other players (he had a bye in round 6), so the total number of players is 9.
tournament.players.size # => 9
Each player has a unique number for the tournament, starting at 1 for the first ICU player.
player = tournament.player(1)
player.name # => "Fox, Anthony"
In the example, this player has 4 points from 9 rounds but only 8 of his results are are rateable (because of the bye).
player.points # => 4.0
player.results.size # => 9
player.results.find_all{ |r| r.rateable }.size # => 8
The other players all have numbers greater than 1.
opponents = tournamnet.players.reject { |o| o.num == 1 }
There are 8 opponents (of the main player) each with exactly one game.
opponents.size # => 8
opponents.find_all{ |o| o.results.size == 1 }.size # => 8
If the file contains errors, then the return value from parse_file is nil and an error message is returned by the error method of the parser object. The method parse_file! is similar except that it raises errors, and the methods parse and parse! are similar except their inputs are strings rather than file names.
A tournament can be serialized back to CSV format (the reverse of parsing) with the serialize method of the parser object.
csv = parser.serialize(tournament)
Or equivalently, the serialize instance method of the tournament, if the appropriate parser name is supplied.
csv = tournament.serialize('ForeignCSV')
Extra condtions, over and above the normal validation rules, apply before any tournament validates or can be serialized in this format.
-
the tournament must have a site attribute and a finish date
-
there must be at least one player with an id (ICU ID number)
-
all foreign players (those without an ICU ID) must have a fed attribute (federation)
-
all ICU players must have a result in every round (even if it is just bye or is unrateable)
-
all the opponents of each ICU player must have a federation (this could include other ICU players with federation IRL)
-
at least one of each ICU player’s opponents must have a rating
If any of these are not satisfied, then the following method calls will all raise an exception:
tournament.validate!(:type => 'ForeignCSV')
tournament.serialize('ForeignCSV')
ICU::Tournament::ForeignCSV.new.serialize(tournament)
You can also build the tournament object from scratch using your own data and then serialize it. For example, here are the commands to reproduce the example above. Note that in this format opponents’ ratings are FIDE.
t = ICU::Tournament.new("Isle of Man Masters, 2007", '2007-09-22', :finish => '2007-09-30', :rounds => 9)
t.site = 'http://www.bcmchess.co.uk/monarch2007/'
t.add_player(ICU::Player.new('Anthony', 'Fox', 1, :fide_rating => 2100, :fed => 'IRL', :id => 456))
t.add_player(ICU::Player.new('Peter P.', 'Taylor', 2, :fide_rating => 2209, :fed => 'ENG'))
t.add_player(ICU::Player.new('Egozi', 'Nadav', 3, :fide_rating => 2205, :fed => 'ISR'))
t.add_player(ICU::Player.new('Peter', 'Cafolla', 4, :fide_rating => 2048, :fed => 'IRL'))
t.add_player(ICU::Player.new('Tim R.', 'Spanton', 5, :fide_rating => 1982, :fed => 'ENG'))
t.add_player(ICU::Player.new('Alan', 'Grant', 6, :fide_rating => 2223, :fed => 'SCO'))
t.add_player(ICU::Player.new('Alan J.', 'Walton', 7, :fide_rating => 2223, :fed => 'ENG'))
t.add_player(ICU::Player.new('Bernard', 'Bannink', 8, :fide_rating => 2271, :fed => 'NED', :title => 'FM'))
t.add_player(ICU::Player.new('Roy', 'Phillips', 9, :fide_rating => 2271, :fed => 'MRI'))
t.add_result(ICU::Result.new(1, 1, 'L', :opponent => 2, :colour => 'B'))
t.add_result(ICU::Result.new(2, 1, 'D', :opponent => 3, :colour => 'W'))
t.add_result(ICU::Result.new(3, 1, 'D', :opponent => 4, :colour => 'B'))
t.add_result(ICU::Result.new(4, 1, 'W', :opponent => 5, :colour => 'W'))
t.add_result(ICU::Result.new(5, 1, 'W', :opponent => 6, :colour => 'B'))
t.add_result(ICU::Result.new(6, 1, 'L'))
t.add_result(ICU::Result.new(7, 1, 'D', :opponent => 7, :colour => 'W'))
t.add_result(ICU::Result.new(8, 1, 'L', :opponent => 8, :colour => 'B'))
t.add_result(ICU::Result.new(9, 1, 'D', :opponent => 9, :colour => 'W'))
puts t.serialize('ForeignCSV')
Instance Attribute Summary collapse
-
#error ⇒ Object
readonly
Returns the value of attribute error.
Instance Method Summary collapse
-
#parse(csv) ⇒ Object
Parse CSV data returning a Tournament on success or a nil on failure.
-
#parse!(csv, arg = {}) ⇒ Object
Parse CSV data returning a Tournament on success or raising an exception on error.
-
#parse_file(file) ⇒ Object
Same as parse except the input is a file name rather than file contents.
-
#parse_file!(file) ⇒ Object
Same as parse! except the input is a file name rather than file contents.
-
#serialize(t, arg = {}) ⇒ Object
Serialise a tournament back into CSV format.
-
#validate!(t) ⇒ Object
Additional tournament validation rules for this specific type.
Instance Attribute Details
#error ⇒ Object (readonly)
Returns the value of attribute error.
122 123 124 |
# File 'lib/icu_tournament/tournament_fcsv.rb', line 122 def error @error end |
Instance Method Details
#parse(csv) ⇒ Object
Parse CSV data returning a Tournament on success or a nil on failure. In the case of failure, an error message can be retrived via the error method.
176 177 178 179 180 181 182 183 |
# File 'lib/icu_tournament/tournament_fcsv.rb', line 176 def parse(csv) begin parse!(csv) rescue => ex @error = ex. nil end end |
#parse!(csv, arg = {}) ⇒ Object
Parse CSV data returning a Tournament on success or raising an exception on error.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/icu_tournament/tournament_fcsv.rb', line 125 def parse!(csv, arg={}) @state, @line, @round, @sum, @error = 0, 0, nil, nil, nil @tournament = Tournament.new('Unspecified', '2000-01-01') csv = ICU::Util::String.to_utf8(csv) unless arg[:is_utf8] CSV.parse(csv, :row_sep => :auto) do |r| @line += 1 # increment line number next if r.size == 0 # skip empty lines r = r.map{|c| c.nil? ? '' : c.strip} # trim all spaces, turn nils to blanks next if r[0] == '' # skip blanks in column 1 @r = r # remember this record for later begin case @state when 0 then event when 1 then start when 2 then finish when 3 then rounds when 4 then website when 5 then player when 6 then result when 7 then total else raise "internal error - state #{@state} does not exist" end rescue => err raise err.class, "line #{@line}: #{err.}", err.backtrace unless err..match(/^line [1-9]/) raise end end unless @state == 5 exp = case @state when 0 then "the event name" when 1 then "the start date" when 2 then "the end date" when 3 then "the number of rounds" when 4 then "the website address" when 6 then "a result for round #{@round+1}" when 7 then "a total score" end raise "line #{@line}: premature termination - expected #{exp}" end raise "line #{@line}: no players found in file" if @tournament.players.size == 0 @tournament.validate! @tournament end |
#parse_file(file) ⇒ Object
Same as parse except the input is a file name rather than file contents.
192 193 194 195 196 197 198 199 |
# File 'lib/icu_tournament/tournament_fcsv.rb', line 192 def parse_file(file) begin parse_file!(file) rescue => ex @error = ex. nil end end |
#parse_file!(file) ⇒ Object
Same as parse! except the input is a file name rather than file contents.
186 187 188 189 |
# File 'lib/icu_tournament/tournament_fcsv.rb', line 186 def parse_file!(file) csv = ICU::Util::File.read_utf8(file) parse!(csv, :is_utf8 => true) end |
#serialize(t, arg = {}) ⇒ Object
Serialise a tournament back into CSV format.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/icu_tournament/tournament_fcsv.rb', line 202 def serialize(t, arg={}) t.validate!(:type => self) CSV.generate do |csv| csv << ["Event", t.name] csv << ["Start", t.start] csv << ["End", t.finish] csv << ["Rounds", t.rounds] csv << ["Website", t.site] t.players.each do |p| next unless p.id next unless p.results.size == t.rounds csv << [] csv << ["Player", p.id, p.last_name, p.first_name] (1..t.rounds).each do |n| data = [] data << n r = p.find_result(n) data << case r.score; when 'W' then '1'; when 'L' then '0'; else '='; end if r.opponent data << r.colour o = t.player(r.opponent) data << o.last_name data << o.first_name data << o. data << o.title data << o.fed else data << '-' end csv << data end csv << ["Total", p.points] end end end |
#validate!(t) ⇒ Object
Additional tournament validation rules for this specific type.
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/icu_tournament/tournament_fcsv.rb', line 239 def validate!(t) raise "missing end date" unless t.finish raise "missing site" unless t.site.to_s.length > 0 icu = t.players.find_all { |p| p.id } raise "there must be at least one ICU player (with an ID number)" if icu.size == 0 foreign = t.players.find_all { |p| !p.id } raise "all foreign players must have a federation" if foreign.detect { |f| !f.fed } enough = false icu.each do |p| rated = 0 results = 0 (1..t.rounds).each do |r| result = p.find_result(r) if result results += 1 raise "all opponents of ICU players must have a federation" if result.opponent && !t.player(result.opponent).fed rated += 1 if result.opponent && t.player(result.opponent). end end raise "player #{p.num} (#{p.name}) has no rated opponents" if rated == 0 enough = true if results == t.rounds end raise "at least one ICU player (with an ID number) must have a result in every round" unless enough end |