Class: ICU::Tournament::SPExport

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

Overview

The SWissPerfect export format used to be important in Irish chess as it was used to submit results to the ICU’s first computerised ratings system, a MicroSoft Access database. As a text based format, it was easier to manipulate than the full binary formats of SwissPerfect. Here is an illustrative example of this format:

No Name           Feder Intl Id Loc Id Rtg  Loc  Title Total  1   2   3

1  Duck, Daffy    IRL           12345       2200 im    2     0:= 3:W 2:D
2  Mouse, Minerva       1234568        1900            1.5   3:D 0:= 1:D
3  Mouse, Mickey  USA   1234567                  gm    1     2:D 1:L 0:=

The format does not record either the name nor the start date of the tournament. Player colours are also missing. When parsing data in this format it is necessary to specify name and start date explicitly:

parser = ICU::Tournament::SPExport.new
tournament = parser.parse_file('sample.txt', :name => 'Mickey Mouse Masters', :start => '2011-02-06')

tournament.name                   # => "Mickey Mouse Masters"
tournament.start                  # => "2011-02-06"
tournament.rounds                 # => 3
tournament.player(1).name         # => "Duck, Daffy"
tournament.player(2).points       # => 1.5
tournament.player(3).fed          # => "USA"

See ICU::Tournament for further details about the object returned.

The SwissPerfect application offers a number of choices when exporting a tournament cross table, one of which is the column separator. The ICU::Tournament::SPExport parser can only handle data with tab separators but is able to cope with any other configuration choices. For example, if some of the optional columns are missing or if the data is not formatted with space padding.

To serialize an ICU::Tournament instance to the format, use the serialize method of the appropriate parser:

parser = ICU::Tournament::Krause.new
spexport = parser.serialize(tournament)

or use the serialize method of the instance with the appropraie format name:

spexport = tournament.serialize('SPExport')

In either case the method returns a string representation of the tourament in SwissPerfect export format with tab separators, space padding and (by default) all the available information about the players. To customize what is displayed, use the only option and supply an array of symbols or strings to specify which columns to include. For example:

spexport = tournament.serialize('SPExport', :only => [:id, :points])

No  Name                 Loc Id  Total    1     2     3

1   Griffiths, Ryan-Rhys 6897    3       4:W   2:W   3:W
2   Flynn, Jamie         5226    2       3:W   1:L   4:W
3   Hulleman, Leon       6409    1       2:L   4:W   1:L
4   Dunne, Thomas        10914   0       1:L   3:L   2:L

The optional attribute names, together with their column header names in SwissPerfect, are as follows: fed (Feder), fide_id (Intl Id), id (Loc Id), fide_rating (Rtg), rating (Loc), title (Title), points: (Total). To omitt the optional columns completely, supply an empty array of column names:

tournament.serialize('SPExport', :only => [])

No  Name                  1     2     3

1   Griffiths, Ryan-Rhys 4:W   2:W   3:W
2   Flynn, Jamie         3:W   1:L   4:W
3   Hulleman, Leon       2:L   4:W   1:L
4   Dunne, Thomas        1:L   3:L   2:L

Or supply whatever columns you want, for example:

tournament.serialize('SPExport', :only => %w{fide_id fide_rating})

Or to omitt rather than include, use the logically opposite except option:

tournament.serialize('SPExport', :except => [:fide_id, :fide_rating])

Note that the column order in the serialised string is the same as it is in the SwissPerfect application. The order of column names in the only option has no effect.

The default, when you leave out the only or except options, is equivalent to both of the following:

tournament.serialize('SPExport', :only => %w{fed fide_id id fide_rating rating title points})
tournament.serialize('SPExport', :except => [])

The order of players in the serialized output is always by player number and as a side effect of serialization, the player numbers will be adjusted to ensure they range from 1 to the total number of players, maintaining the original order. If you would prefer rank-order instead, then you must first renumber the players by rank before serializing. For example:

spexport = tournament.renumber(:rank).serialize('SPExport')

Or equivalently, since renumbering by rank is the default, just:

spexport = tournament.renumber.serialize('SPExport')

You may wish to set the tie-break rules before ranking:

tournament.tie_breaks = [:buchholz, :neustadtl]
spexport = tournament.rerank.renumber.serialize('SwissPerfect')

See ICU::Tournament for more about tie-breaks.

Constant Summary collapse

COLUMNS =
[
  [:num,          "No"],
  [:name,       "Name"],
  [:fed,       "Feder"],
  [:fide_id, "Intl Id"],
  [:id,       "Loc Id"],
  [:fide_rating, "Rtg"],
  [:rating,      "Loc"],
  [:title,     "Title"],
  [:points,    "Total"],
]
KEY2NAM =
COLUMNS.inject({}) { |h,c| h[c.first] = c.last; h }
NAM2KEY =
COLUMNS.inject({}) { |h,c| h[c.last] = c.first; h }

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#errorObject (readonly)

Returns the value of attribute error.



108
109
110
# File 'lib/icu_tournament/tournament_spx.rb', line 108

def error
  @error
end

Instance Method Details

#parse(spx, arg = {}) ⇒ Object

Parse SwissPerfect export text 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.



164
165
166
167
168
169
170
171
# File 'lib/icu_tournament/tournament_spx.rb', line 164

def parse(spx, arg={})
  begin
    parse!(spx, arg)
  rescue => ex
    @error = ex.message
    nil
  end
end

#parse!(spx, arg = {}) ⇒ Object

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



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
# File 'lib/icu_tournament/tournament_spx.rb', line 126

def parse!(spx, arg={})
  @tournament = init_tournament(arg)
  @lineno = 0
  @header = nil
  @results = Array.new
  spx = ICU::Util::String.to_utf8(spx) unless arg[:is_utf8]

  # Process each line.
  spx.each_line do |line|
    @lineno += 1
    line.strip!          # remove leading and trailing white space
    next if line == ''   # skip blank lines

    if @header
      process_player(line)
    else
      process_header(line)
    end
  end

  # Now that all players are present, add the results to the tournament.
  @results.each do |r|
    lineno, player, data, result = r
    begin
      @tournament.add_result(result)
    rescue => err
      raise "line #{lineno}, player #{player}, result '#{data}': #{err.message}"
    end
  end

  # Finally, exercise the tournament object's internal validation, reranking if neccessary.
  @tournament.validate!(:rerank => true)

  @tournament
end

#parse_file(file, arg = {}) ⇒ Object

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



181
182
183
184
185
186
187
188
# File 'lib/icu_tournament/tournament_spx.rb', line 181

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

#parse_file!(file, arg = {}) ⇒ Object

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



174
175
176
177
178
# File 'lib/icu_tournament/tournament_spx.rb', line 174

def parse_file!(file, arg={})
  spx = ICU::Util::File.read_utf8(file)
  arg[:is_utf8] = true
  parse!(spx, arg)
end

#serialize(t, arg = {}) ⇒ Object

Serialise a tournament to SwissPerfect text export format.



191
192
193
194
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/icu_tournament/tournament_spx.rb', line 191

def serialize(t, arg={})
  t.validate!(:type => self)

  # Ensure a nice set of player numbers and get the number of rounds.
  t.renumber(:order)
  rounds = t.last_round

  # Optional columns.
  defaults = COLUMNS.map(&:first)
  case
  when arg[:except].instance_of?(Array)
    optional = (Set.new(defaults) - arg[:except].map!(&:to_s).map!(&:to_sym)).to_a
  when arg[:only].instance_of?(Array)
    optional = arg[:only].map!(&:to_s).map!(&:to_sym)
  else
    optional = defaults
  end
  optional = optional.inject({}) { |m, a| m[a] = true; m }

  # Columns identifiers in SwissPerfect order.
  columns = Array.new
  columns.push(:num)
  columns.push(:name)
  defaults.each { |x| columns.push(x) if optional[x] && x != :num && x != :name }

  # Widths and formats for each column.
  width = Hash.new
  format = Hash.new
  columns.each do |col|
    width[col] = t.players.inject(KEY2NAM[col].length) { |l, p| p.send(col).to_s.length  > l ? p.send(col).to_s.length  : l }
    format[col] = "%-#{width[col]}s"
  end

  # The header, followed by a blank line.
  formats = columns.map{ |col| format[col] }
  (1..rounds).each { |r| formats << "%#{width[:num]}d  " % r }
  sp = formats.join("\t") % columns.map{ |col| KEY2NAM[col] }
  sp << "\r\n\r\n"

  # The round formats for players are slightly different to those for the header.
  formats.pop(rounds)
  (1..rounds).each{ |r| formats << "%#{2+width[:num]}s" }

  # Serialize the formats already.
  formats = formats.join("\t") + "\r\n"

  # Now add a line for each player.
  t.players.each { |p| sp << p.to_sp_text(rounds, columns, formats) }

  # And return the whole lot.
  sp
end

#validate!(t) ⇒ Object

Additional tournament validation rules for this specific type.



245
246
247
# File 'lib/icu_tournament/tournament_spx.rb', line 245

def validate!(t)
  # None.
end