Class: ICU::RatedPlayer

Inherits:
Object
  • Object
show all
Defined in:
lib/icu_ratings/player.rb

Overview

Adding Players to Tournaments

You don’t directly create players, rather you add them to tournaments with the add_player method.

t = ICU::RatedTournament.new
t.add_player(1)

There is only one mandatory parameter - the player number - which can be any integer value except player numbers must be unique in each tournament:

t.add_player(2)    # fine
t.add_player(2)    # attempt to add a second player with the same number - exception!

Retrieving Players from Tournaments

Player objects can be retrieved from ICU::RatedTournament objects with the latter’s player method in conjunction with the appropriate player number:

p = t.player(2)
p.num              # 2

Or the player object can be saved from the return value from add_player:

p = t.add_player(-2)
p.num             # -2

If the number supplied to player is an invalid player number, the method returns nil.

Different types of players are signalled by different combinations of the three optional parameters: rating, kfactor and games.

Full Ratings

Rated players have a full rating and a K-factor and are added by including valid values for those two parameters:

p = t.add_player(3, :rating => 2000, :kfactor => 16)
p.type             # :rated

Provisional Ratings

Players that don’t yet have a full rating but do have a provisonal rating estimated on some number of games played prior to the tournament are indicated by values for the rating and games parameters:

p = t.add_player(4, :rating => 1600, :games => 10)
p.type             # :provisional

The value for the number of games should not exceed 19 since players with 20 or more games should have a full rating.

Fixed Ratings

Players with fixed ratings just have a rating - no K-factor or number of previous games. When the tournament is rated, these players will have their tournament performance ratings calculated but the value returned by the method new_rating will just be the rating they started with. Typically these are foreign players with FIDE ratings who are not members of the ICU and for whom ICU ratings are not desired.

p = t.add_player(6, :rating => 2500)
p.type             # :foreign

No Rating

Unrated players who do not have any previous rated games at all are indicated by leaving out any values for rating, kfactor or games.

p = t.add_player(5)
p.type             # :unrated

Invalid Combinations

The above four types of players (rated, provisional, unrated, foreign) are the only valid ones and any attempt to add players with other combinations of the attributes rating, kfactor and games will cause an exception. For example:

t.add_player(7, :rating => 2000, :kfactor => 16, :games => 10)   # exception! - cannot have both kfactor and games
t.add_plater(7, :kfactor => 16)                                  # exception! - kfactor makes no sense without a rating

String Input Values

Although rating and kfactor are held as Float values and games and num (the player number) as Fixnums, all these parameters can be specified using strings, even when padded with whitespace.

p = t.add_player("  0  ", :rating => "  2000.5  ", :kfactor => "  20.5  ")
p.num            # 0 (Fixnum)
p.rating         # 2000.5 (Float)
p.kfactor        # 20.5 (Float)

Calculation of K-factors

Rather than pre-calculating the value to set for a rated player’s K-factor, the RatedPlayer class can itself calculate K-factors if the releavant information is supplied. ICU K-factors depend not only on a player’s rating, but also on their age and experience. Therefore, supply a hash, instead of a numerical value, for the kfactor attribute with values set for date-of-birth (dob) and date joined (joined):

t = Tournament.new(:start => "2010-07-10")
p = t.add_player(1, :rating => 2225, :kfactor => { :dob => "1993-12-20", :joined => "2004-11-28" })
p.kfactor        # 16.0

For this to work the tournament’s optional start date must be set to enable the player’s age and experience at the start of the tournament be to calculated. The ICU K-factor rules are:

  • 16 for players rated 2100 and over, otherwise

  • 40 for players aged under 21, otherwise

  • 32 for players who have been members for less than 8 years, otherwise

  • 24

If you want to calculate K-factors accrding to some other, non-ICU scheme, then override the static method kfactor of the RatedPlayer class and pass in a hash of whatever key-value pairs it requires as the value associated with kfactor key in the add_player method.

Description Parameter

There is one other optional parameter, desc (short for “description”). It has no effect on player type or rating calculations and it cannot be used to retrieve players from a tournament (only the player number can be used for that). Its only use is to attach additional arbitary data to players. Any object can be used and descriptions don’t have to be unique. The attribute’s typical use, if it’s used at all, is expected to be for player names and/or ID numbers, in the form of String values.

t.add_player(8, :rating => 2800, :desc => 'Gary Kasparov (4100018)')
t.player(8).desc    # "Gary Kasparov (4100018)"

After the Tournament is Rated

After the rate! method has been called on the ICU::RatedTournament object, the results of the rating calculations are available via various methods of the player objects:

new_rating

This is the player’s new rating. For rated players it is their old rating plus their rating_change plus their bonus (if any). For provisional players it is their performance rating including their previous games. For unrated players it is their tournament performance rating. New ratings are not calculated for foreign players so this method just returns their start rating.

rating_change

This is calculated from a rated player’s old rating, their K-factor and the sum of expected scores in each game. The same as the difference between the old and new ratings (unless there is a bonus). Not available for other player types.

performance

This returns the tournament rating performance for rated, unrated and foreign players. For provisional players it returns a weighted average of the player’s tournament performance and their previous games. For provisional and unrated players it is the same as new_rating.

expected_score

This returns the sum of expected scores over all results for all player types. For rated players, this number times the K-factor gives their rating change. It is calculated for provisional, unrated and foreign players but not actually used to estimate new ratings (for provisional and unrated players performance estimates are used instead).

bonus

The bonus received by a rated player (usually zero). Only available for rated players.

pb_rating

A rated player’s pre-bonus rating (rounded). Only for rated players and returns nil for players who are ineligible for a bonus.

pb_performance

A rated player’s pre-bonus performance (rounded). Only for rated players and returns nil for players ineligible for a bonus.

Unrateable Players

If a tournament contains groups of provisonal or unrated players who play games only amongst themselves and not against any rated or foreign opponents, they can’t be rated. This is indicated by a value of nil returned from the new_rating method.

Direct Known Subclasses

FrgnRating, FullRating, NoneRating, ProvRating

Defined Under Namespace

Classes: FrgnRating, FullRating, NoneRating, ProvRating

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#descObject

Returns the value of attribute desc.



164
165
166
# File 'lib/icu_ratings/player.rb', line 164

def desc
  @desc
end

#numObject (readonly)

Returns the value of attribute num.



163
164
165
# File 'lib/icu_ratings/player.rb', line 163

def num
  @num
end

#performanceObject (readonly)

Returns the value of attribute performance.



163
164
165
# File 'lib/icu_ratings/player.rb', line 163

def performance
  @performance
end

#resultsObject (readonly)

Returns the value of attribute results.



163
164
165
# File 'lib/icu_ratings/player.rb', line 163

def results
  @results
end

#typeObject (readonly)

Returns the value of attribute type.



163
164
165
# File 'lib/icu_ratings/player.rb', line 163

def type
  @type
end

Class Method Details

.check_games(arg) ⇒ Object

:nodoc:



201
202
203
204
205
206
# File 'lib/icu_ratings/player.rb', line 201

def self.check_games(arg) # :nodoc:
  return unless arg
  games = arg.to_i
  raise "invalid number of games (#{arg})" if games <= 0 || games >= 20
  games
end

.check_kfactor(arg) ⇒ Object

:nodoc:



194
195
196
197
198
199
# File 'lib/icu_ratings/player.rb', line 194

def self.check_kfactor(arg) # :nodoc:
  return unless arg
  kfactor = arg.to_f
  raise "invalid player k-factor (#{arg})" if kfactor <= 0.0
  kfactor
end

.check_num(arg) ⇒ Object

:nodoc:



181
182
183
184
185
# File 'lib/icu_ratings/player.rb', line 181

def self.check_num(arg) # :nodoc:
  num = arg.to_i
  raise "invalid player num (#{arg})" if num == 0 && !arg.to_s.match(/^\s*\d/)
  num
end

.check_rating(arg) ⇒ Object

:nodoc:



187
188
189
190
191
192
# File 'lib/icu_ratings/player.rb', line 187

def self.check_rating(arg) # :nodoc:
  return unless arg
  rating = arg.to_f
  raise "invalid player rating (#{arg})" if rating == 0.0 && !arg.to_s.match(/^\s*\d/)
  rating
end

.factory(num, args = {}) ⇒ Object

:nodoc:



166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/icu_ratings/player.rb', line 166

def self.factory(num, args={}) # :nodoc:
  num     = check_num(num)
  rating  = check_rating(args[:rating])
  kfactor = check_kfactor(args[:kfactor])
  games   = check_games(args[:games])
  desc    = args[:desc]
  case
    when  rating &&  kfactor && !games then FullRating.new(num, desc, rating, kfactor)
    when  rating && !kfactor &&  games then ProvRating.new(num, desc, rating, games)
    when  rating && !kfactor && !games then FrgnRating.new(num, desc, rating)
    when !rating && !kfactor && !games then NoneRating.new(num, desc)
    else  raise "invalid combination of player attributes"
  end
end

.kfactor(args) ⇒ Object

Calculate a K-factor according to ICU rules.



209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/icu_ratings/player.rb', line 209

def self.kfactor(args)
  %w{rating start dob joined}.each { |a| raise "missing #{a} for K-factor calculation" unless args[a.to_sym] }
  case
  when args[:rating] >= 2100 then
    16
  when ICU::Util.age(args[:dob], args[:start]) < 21 then
    40
  when ICU::Util.age(args[:joined], args[:start]) < 8 then
    32
  else
    24
  end
end

Instance Method Details

#==(other) ⇒ Object

:nodoc:



390
391
392
393
# File 'lib/icu_ratings/player.rb', line 390

def ==(other) # :nodoc:
  return false unless other.is_a? ICU::RatedPlayer
  num == other.num
end

#add_result(result) ⇒ Object

:nodoc:



341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/icu_ratings/player.rb', line 341

def add_result(result) # :nodoc:
  raise "invalid result (#{result.class})" unless result.is_a? ICU::RatedResult
  raise "players cannot score results against themselves" if self == result.opponent
  duplicate = false
  @results.each do |r|
    if r.round == result.round
      raise "inconsistent result in round #{r.round}" unless r == result
      duplicate = true
    end
  end
  return if duplicate
  @results << result
  @results.sort!{ |a,b| a.round <=> b.round }
end

#average_performance(performance, games) ⇒ Object

:nodoc:



373
374
375
# File 'lib/icu_ratings/player.rb', line 373

def average_performance(performance, games) # :nodoc:
  performance / games
end

#estimate_performanceObject

:nodoc:



361
362
363
364
365
366
367
368
369
370
371
# File 'lib/icu_ratings/player.rb', line 361

def estimate_performance # :nodoc:
  games, performance = results.inject([0,0.0]) do |sum, result|
    rating = result.opponent.new_rating(:opponent)
    if rating
      sum[0]+= 1
      sum[1]+= rating + (2 * result.score - 1) * 400.0
    end
    sum
  end
  @estimated_performance = average_performance(performance, games) if games > 0
end

#expected_scoreObject



333
334
335
# File 'lib/icu_ratings/player.rb', line 333

def expected_score
  @results.inject(0.0) { |e, r| e + (r.expected_score || 0.0) }
end

#rate!(update_bonus = false) ⇒ Object

:nodoc:



356
357
358
359
# File 'lib/icu_ratings/player.rb', line 356

def rate!(update_bonus=false) # :nodoc:
  @results.each { |r| r.rate!(self) }
  self.update_bonus if update_bonus && respond_to?(:update_bonus)
end

#resetObject

:nodoc:



223
224
225
226
# File 'lib/icu_ratings/player.rb', line 223

def reset # :nodoc:
  @performance = nil
  @estimated_performance = nil
end

#scoreObject



337
338
339
# File 'lib/icu_ratings/player.rb', line 337

def score
  @results.inject(0.0) { |e, r| e + r.score }
end

#update_performance(thresh) ⇒ Object

:nodoc:



377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/icu_ratings/player.rb', line 377

def update_performance(thresh) # :nodoc:
  stable = case
  when  @performance &&  @estimated_performance then
    (@performance - @estimated_performance).abs < thresh
  when !@performance && !@estimated_performance then
    true
  else
    false
  end
  @performance = @estimated_performance if @estimated_performance
  stable
end