class ICU::Tournament::ForeignCSV
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 otherICU
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')
Attributes
Public Instance Methods
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.
# File lib/icu_tournament/tournament_fcsv.rb, line 176 def parse(csv) begin parse!(csv) rescue => ex @error = ex.message nil end end
Parse CSV data returning a Tournament
on success or raising an exception on error.
# 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.message}", err.backtrace unless err.message.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
Same as parse except the input is a file name rather than file contents.
# File lib/icu_tournament/tournament_fcsv.rb, line 192 def parse_file(file) begin parse_file!(file) rescue => ex @error = ex.message nil end end
Same as parse! except the input is a file name rather than file contents.
# 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
Serialise a tournament back into CSV format.
# 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.fide_rating data << o.title data << o.fed else data << '-' end csv << data end csv << ["Total", p.points] end end end
Additional tournament validation rules for this specific type.
# 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).fide_rating 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