Class: ICU::Tournament

Inherits:
Object
  • Object
show all
Defined in:
lib/chess_icu/tournament.rb,
lib/chess_icu/tournament_fcsv.rb

Overview

Generic Tournament

Normally a tournament object is created by parsing a data file (e.g. with ICU::Tournament::ForeignCSV). However, it is also possible to build a tournament by first creating a bare tournament instance and then firstly adding all the players and then adding all the results.

require 'rubygems'
require 'chess_icu'

t = ICU::Tournament.new('Bangor Masters', '2009-11-09')

t.add_player(ICU::Player.new('Bobby', 'Fischer', 10))
t.add_player(ICU::Player.new('Garry', 'Kasparov', 20))
t.add_player(ICU::Player.new('Mark', 'Orr', 30))

t.add_result(ICU::Result.new(1, 10, 'D', :opponent => 30, :colour => 'W'))
t.add_result(ICU::Result.new(2, 20, 'W', :opponent => 30, :colour => 'B'))
t.add_result(ICU::Result.new(3, 20, 'L', :opponent => 10, :colour => 'W'))

[10, 20, 30].each { |n| p = t.player(n); puts "#{p.points} #{p.name}" }

Would result in the following output.

1.5 Bobby Fischer
1.0 Gary Kasparov
0.5 Mark Orr

Note that the players should be added first because the add_result method will raise an exception if the players it references through their tournament numbers (10, 20 and 30 in this example) have not already been added to the tournament.

A tournament can be validated with either the validate! or invalid methods. On success, the first returns true while the second returns false. On error, the first throws an exception while the second returns a description of the error.

Validations checks that:

  • there are at least two players

  • every player has a least one result

  • the round numbers of the players results are consistent

  • the tournament dates (start, finish, round dates) are consistent

  • the player ranks are consistent with their scores

Side effects of calling validate! or invalid include:

  • the number of rounds will be set if not set already

  • the finish date will be set if not set already and if there are round dates

If the rerank option is set, as in this example:

t.validate!(:rerank => true)

then there are additional side effects of validating a tournament:

  • the players will be ranked if there is no existing ranking

  • the players will be reranked if the existing ranking is inconsistent

Ranking is consistent if either no players have any rank or if all players have a rank and no player is ranked higher than another player with more points.

The default tie break method used to rank players on the same score is alphabetical (by last name then first name). Other methods can be specified via the rerank option. Instead of setting the option to true, alternatives are the following (both symbols or strings will work):

  • sum_of_scores: sum of opponents’ scores

  • name: this is the default and the same as setting the option to true

Since validate and invalid only rerank a tournament with absent or inconsistent ranking, to force a particular kind of ranking of a tournament which is already ranked, use the rerank method. This method takes one argument, the tie break method and it works the same as the rerank option to validate. For example:

t.rerank(:sum_of_scores)   # rerank using sum of scores as tie break
t.rerank(:name)            # rerank using player names as tie break
t.rerank                   # same as _name_, which is the default

The players in a tournament, whose reference numbers can be any set of unique integers (including zero and negative numbers), can be renumbered in order of rank or name. After renumbering the new player numbers will start at 1 and go up to the number of players.

t.renumber(:name)       # renumber by name
t.renumber(:rank)       # renumber by rank
t.renumber              # same - rank is the default

A side effect of renumbering by rank is that if the tournament started without any player rankings or with inconsitent rankings, it will be reranked (i.e. the method rerank will be called).

Defined Under Namespace

Classes: ForeignCSV

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, start, opt = {}) ⇒ Tournament

Constructor. Name and start date must be supplied. Other attributes are optional.



96
97
98
99
100
101
102
103
# File 'lib/chess_icu/tournament.rb', line 96

def initialize(name, start, opt={})
  self.name  = name
  self.start = start
  [:finish, :rounds, :site, :city, :fed, :type, :arbiter, :deputy, :time_control].each { |a| self.send("#{a}=", opt[a]) unless opt[a].nil? }
  @player = {}
  @teams = []
  @round_dates = []
end

Instance Attribute Details

#arbiterObject

Returns the value of attribute arbiter.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def arbiter
  @arbiter
end

#cityObject

Returns the value of attribute city.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def city
  @city
end

#deputyObject

Returns the value of attribute deputy.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def deputy
  @deputy
end

#fedObject

Returns the value of attribute fed.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def fed
  @fed
end

#finishObject

Returns the value of attribute finish.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def finish
  @finish
end

#nameObject

Returns the value of attribute name.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def name
  @name
end

#round_datesObject (readonly)

Returns the value of attribute round_dates.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def round_dates
  @round_dates
end

#roundsObject

Returns the value of attribute rounds.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def rounds
  @rounds
end

#siteObject

Returns the value of attribute site.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def site
  @site
end

#startObject

Returns the value of attribute start.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def start
  @start
end

#teamsObject (readonly)

Returns the value of attribute teams.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def teams
  @teams
end

#time_controlObject

Returns the value of attribute time_control.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def time_control
  @time_control
end

#typeObject

Returns the value of attribute type.



93
94
95
# File 'lib/chess_icu/tournament.rb', line 93

def type
  @type
end

Instance Method Details

#add_player(player) ⇒ Object

Add a new player to the tournament. Must have a unique player number.



234
235
236
237
238
# File 'lib/chess_icu/tournament.rb', line 234

def add_player(player)
  raise "invalid player" unless player.class == ICU::Player
  raise "player number (#{player.num}) should be unique" if @player[player.num]
  @player[player.num] = player
end

#add_result(result, reverse_rateable = true) ⇒ Object

Add a result to a tournament. An exception is raised if the players referenced in the result (by number) do not exist in the tournament. The result, which remember is from the perspective of one of the players, is added to that player’s results. Additionally, the reverse of the result is automatically added to the player’s opponent, unless the opponent does not exist (e.g. byes, walkovers). By default, if the result is rateable then the opponent’s result will also be rateable. To make the opponent’s result unrateable, set the optional second parameter to false.



261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/chess_icu/tournament.rb', line 261

def add_result(result, reverse_rateable=true)
  raise "invalid result" unless result.class == ICU::Result
  raise "result round number (#{result.round}) inconsistent with number of tournament rounds" if @rounds && result.round > @rounds
  raise "player number (#{result.player}) does not exist" unless @player[result.player]
  @player[result.player].add_result(result)
  if result.opponent
    raise "opponent number (#{result.opponent}) does not exist" unless @player[result.opponent]
    reverse = result.reverse
    reverse.rateable = false unless reverse_rateable
    @player[result.opponent].add_result(reverse)
  end
end

#add_round_date(round_date) ⇒ Object

Add a round date.



159
160
161
162
163
164
165
# File 'lib/chess_icu/tournament.rb', line 159

def add_round_date(round_date)
  round_date = round_date.to_s.strip
  parsed_date = Util.parsedate(round_date)
  raise "invalid round date (#{round_date})" unless parsed_date
  @round_dates << parsed_date
  @round_dates.sort!
end

#add_team(team) ⇒ Object

Add a new team. The argument is either a team (possibly already with members) or the name of a new team. The team’s name must be unique in the tournament. Returns the the team instance.



221
222
223
224
225
226
# File 'lib/chess_icu/tournament.rb', line 221

def add_team(team)
  team = Team.new(team.to_s) unless team.is_a? Team
  raise "a team with a name similar to '#{team.name}' already exists" if self.get_team(team.name)
  @teams << team
  team
end

#find_player(player) ⇒ Object

Lookup a player in the tournament by player number, returning nil if the player number does not exist.



251
252
253
# File 'lib/chess_icu/tournament.rb', line 251

def find_player(player)
  players.find { |p| p == player }
end

#get_team(name) ⇒ Object

Return the team object that matches a given name, or nil if not found.



229
230
231
# File 'lib/chess_icu/tournament.rb', line 229

def get_team(name)
  @teams.find{ |t| t.matches(name) }
end

#invalid(options = {}) ⇒ Object

Is a tournament invalid? Either returns false (if it’s valid) or an error message.



308
309
310
311
312
313
314
315
# File 'lib/chess_icu/tournament.rb', line 308

def invalid(options={})
  begin
    validate!(options)
  rescue => err
    return err.message
  end
  false
end

#last_roundObject

Return the greatest round number according to the players results (which may not be the same as the set number of rounds).



173
174
175
176
177
178
179
180
181
# File 'lib/chess_icu/tournament.rb', line 173

def last_round
  last_round = 0
  @player.values.each do |p|
    p.results.each do |r|
      last_round = r.round if r.round > last_round
    end
  end
  last_round
end

#player(num) ⇒ Object

Get a player by their number.



241
242
243
# File 'lib/chess_icu/tournament.rb', line 241

def player(num)
  @player[num]
end

#playersObject

Return an array of all players in order of their player number.



246
247
248
# File 'lib/chess_icu/tournament.rb', line 246

def players
  @player.values.sort_by{ |p| p.num }
end

#renumber(criterion = :rank) ⇒ Object

Renumber the players according to a given criterion.



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/chess_icu/tournament.rb', line 287

def renumber(criterion = :rank)
  map = Hash.new
  
  # Decide how to rank.
  if criterion == :name
    @player.values.sort_by{ |p| p.name }.each_with_index{ |p, i| map[p.num] = i + 1 }
  else
    begin check_ranks rescue rerank end
    @player.values.each{ |p| map[p.num] = p.rank}
  end
  
  # Apply ranking.
  @teams.each{ |t| t.renumber(map) }
  @player = @player.values.inject({}) do |hash, player|
    player.renumber(map)
    hash[player.num] = player
    hash
  end
end

#rerank(tie_break_method = :name) ⇒ Object

Rerank the tournament by score first and if necessary using a configurable tie breaker method.



275
276
277
278
279
280
281
282
283
284
# File 'lib/chess_icu/tournament.rb', line 275

def rerank(tie_break_method = :name)
  points, tie_break_scores, tie_break_order = rerank_data(tie_break_method)
  sortable = @player.values.map { |p| [p, points[p.num], tie_break_scores[p.num]] }
  sortable.sort do |a,b|
    diff = b[1] <=> a[1]
    diff == 0 ? (b[2] <=> a[2]) * tie_break_order : diff
  end.each_with_index do |s,i|
    s[0].rank = i + 1
  end
end

#round_date(round) ⇒ Object

Return the date of a given round, or nil if unavailable.



168
169
170
# File 'lib/chess_icu/tournament.rb', line 168

def round_date(round)
  @round_dates[round-1]
end

#validate!(options = {}) ⇒ Object

Raise an exception if a tournament is not valid. Covers all the ways a tournament can be invalid not already enforced by the setters.



319
320
321
322
323
324
325
326
327
# File 'lib/chess_icu/tournament.rb', line 319

def validate!(options={})
  begin check_ranks rescue rerank(options[:rerank]) end if options[:rerank]
  check_players
  check_rounds
  check_dates
  check_teams
  check_ranks(:allow_none => true)
  true
end