class ICU::Tournament::SPExport

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.

Constants

COLUMNS
KEY2NAM
NAM2KEY

Attributes

error[R]

Public Instance Methods

parse(spx, arg={}) click to toggle source

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.

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

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

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

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

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

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

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

Serialise a tournament to SwissPerfect text export format.

# 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) click to toggle source

Additional tournament validation rules for this specific type.

# File lib/icu_tournament/tournament_spx.rb, line 245
def validate!(t)
  # None.
end