Class: ICU::Tournament::ForeignCSV

Inherits:
Object
  • Object
show all
Defined in:
lib/icu_tournament/tournament_fcsv.rb

Overview

Foreign CSV

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
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,,MAU
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.rounds                                   # => 9
tournament.website                                  # => "http://www.bcmchess.co.uk/monarch2007/"

The main player (the 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. The main player always occurs first in this type of file, so his number is 1.

player = tournament.player(1)
player.name                                         # => "Fox, Anthony"

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

However, none of the opponents’ results are rateable because they are foreign to the domestic rating list to which the main player belongs. For example:

opponent = tournament.players(2)
opponent.name                                       # => "Taylor, Peter P."
opponent.results[0].rateable                        # => false

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')

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.

t = ICU::Tournament.new("Isle of Man Masters, 2007", '2007-09-22', :rounds => 9)
t.site = 'http://www.bcmchess.co.uk/monarch2007/'
t.add_player(ICU::Player.new('Anthony',  'Fox',      1, :rating => 2100, :fed => 'IRL', :id => 456))
t.add_player(ICU::Player.new('Peter P.', 'Taylor',   2, :rating => 2209, :fed => 'ENG'))
t.add_player(ICU::Player.new('Egozi',    'Nadav',    3, :rating => 2205, :fed => 'ISR'))
t.add_player(ICU::Player.new('Peter',    'Cafolla',  4, :rating => 2048, :fed => 'IRL'))
t.add_player(ICU::Player.new('Tim R.',   'Spanton',  5, :rating => 1982, :fed => 'ENG'))
t.add_player(ICU::Player.new('Alan',     'Grant',    6, :rating => 2223, :fed => 'SCO'))
t.add_player(ICU::Player.new('Alan J.',  'Walton',   7, :rating => 2223, :fed => 'ENG'))
t.add_player(ICU::Player.new('Bernard',  'Bannink',  8, :rating => 2271, :fed => 'NED', :title => 'FM'))
t.add_player(ICU::Player.new('Roy',      'Phillips', 9, :rating => 2271, :fed => 'MAU'))
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'))
t.validate!
puts t.serialize('ForeignCSV')

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#errorObject (readonly)

Returns the value of attribute error.



118
119
120
# File 'lib/icu_tournament/tournament_fcsv.rb', line 118

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.



169
170
171
172
173
174
175
176
# File 'lib/icu_tournament/tournament_fcsv.rb', line 169

def parse(csv)
  begin
    parse!(csv)
  rescue => ex
    @error = ex.message
    nil
  end
end

#parse!(csv) ⇒ Object

Parse CSV data returning a Tournament on success or raising an exception on error.



121
122
123
124
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
# File 'lib/icu_tournament/tournament_fcsv.rb', line 121

def parse!(csv)
  @state, @line, @round, @sum, @error = 0, 0, nil, nil, nil
  @tournament = Tournament.new('Dummy', '2000-01-01')
  
  Util::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 rounds
        when 3 then website
        when 4 then player
        when 5 then result
        when 6 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 == 4
    exp = case @state
          when 0 then "the event name"
          when 1 then "the start date"
          when 2 then "the number of rounds"
          when 3 then "the website address"
          when 5 then "a result for round #{@round+1}"
          when 6 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.



185
186
187
188
189
190
191
192
# File 'lib/icu_tournament/tournament_fcsv.rb', line 185

def parse_file(file)
  begin
    parse_file!(file)
  rescue => ex
    @error = ex.message
    nil
  end
end

#parse_file!(file) ⇒ Object

Same as parse! except the input is a file name rather than file contents.



179
180
181
182
# File 'lib/icu_tournament/tournament_fcsv.rb', line 179

def parse_file!(file)
  csv = open(file) { |f| f.read }
  parse!(csv)
end

#serialize(t) ⇒ Object

Serialise a tournament back into CSV format.



195
196
197
198
199
200
201
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
# File 'lib/icu_tournament/tournament_fcsv.rb', line 195

def serialize(t)
  return nil unless t.class == ICU::Tournament;
  Util::CSV.generate do |csv|
    csv << ["Event", t.name]
    csv << ["Start", t.start]
    csv << ["Rounds", t.rounds]
    csv << ["Website", t.site]
    t.players.each do |p|
      next unless p.id
      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.rateable
          data << r.colour
          o = t.player(r.opponent)
          data << o.last_name
          data << o.first_name
          data << o.rating
          data << o.title
          data << o.fed
        else
          data << '-'
        end
        csv << data
      end
      csv << ["Total", p.points]
    end
  end
end