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.

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

error[R]

Public Instance Methods

parse(csv) click to toggle source

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, arg={}) click to toggle source

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
parse_file(file) click to toggle source

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
parse_file!(file) click to toggle source

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
serialize(t, arg={}) click to toggle source

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
validate!(t) click to toggle source

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