icu_ratings 1.5.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -117,8 +117,8 @@ module ICU
117
117
  # There is one other optional parameter, _desc_ (short for "description"). It has no effect on player
118
118
  # type or rating calculations and it cannot be used to retrieve players from a tournament (only the
119
119
  # player number can be used for that). Its only use is to attach additional arbitary data to players.
120
- # Any object can be used and descriptions don't have to be unique. The attribute's typical use,
121
- # if it's used at all, is expected to be for player names in the form of String values.
120
+ # Any object can be used and descriptions don't have to be unique. The attribute's typical use, if
121
+ # it's used at all, is expected to be for player names and/or ID numbers, in the form of String values.
122
122
  #
123
123
  # t.add_player(8, :rating => 2800, :desc => 'Gary Kasparov (4100018)')
124
124
  # t.player(8).desc # "Gary Kasparov (4100018)"
@@ -128,24 +128,24 @@ module ICU
128
128
  # After the <em>rate!</em> method has been called on the ICU::RatedTournament object, the results
129
129
  # of the rating calculations are available via various methods of the player objects:
130
130
  #
131
- # _new_rating_:: This is the player's new rating. For rated players it is their old rating
132
- # plus their _rating_change_ plus their _bonus_ (if any). For provisional players
133
- # it is their performance rating including their previous games. For unrated
134
- # players it is their tournament performance rating. New ratings are not
135
- # calculated for foreign players so this method just returns their start _rating_.
136
- # _rating_change_:: This is calculated from a rated player's old rating, their K-factor and the sum
137
- # of expected scores in each game. The same as the difference between the old and
138
- # new ratings (unless there is a bonus). Zero for all other types of players.
139
- # _performance_:: This returns the tournament rating performance for rated, unrated and
140
- # foreign players. For provisional players it returns a weighted average
141
- # of the player's tournament performance and their previous games. For
142
- # provisional and unrated players it is the same as _new_rating_.
143
- # _expected_score_:: This returns the sum of expected scores over all results for all player types.
144
- # For rated players, this number times the K-factor gives their rating change.
145
- # It is calculated for provisional, unrated and foreign players but not actually
146
- # used to estimate new ratings (for provisional and unrated players performance
147
- # estimates are used instead).
148
- # _bonus_:: The bonus received by a rated player (usually zero). Nil for other player types.
131
+ # _new_rating_:: This is the player's new rating. For rated players it is their old rating
132
+ # plus their _rating_change_ plus their _bonus_ (if any). For provisional players
133
+ # it is their performance rating including their previous games. For unrated
134
+ # players it is their tournament performance rating. New ratings are not
135
+ # calculated for foreign players so this method just returns their start _rating_.
136
+ # _rating_change_:: This is calculated from a rated player's old rating, their K-factor and the sum
137
+ # of expected scores in each game. The same as the difference between the old and
138
+ # new ratings (unless there is a bonus). Not available for other player types.
139
+ # _performance_:: This returns the tournament rating performance for rated, unrated and
140
+ # foreign players. For provisional players it returns a weighted average
141
+ # of the player's tournament performance and their previous games. For
142
+ # provisional and unrated players it is the same as _new_rating_.
143
+ # _expected_score_:: This returns the sum of expected scores over all results for all player types.
144
+ # For rated players, this number times the K-factor gives their rating change.
145
+ # It is calculated for provisional, unrated and foreign players but not actually
146
+ # used to estimate new ratings (for provisional and unrated players performance
147
+ # estimates are used instead).
148
+ # _bonus_:: The bonus received by a rated player (usually zero). Not available for other player types.
149
149
  #
150
150
  # == Unrateable Players
151
151
  #
@@ -155,53 +155,49 @@ module ICU
155
155
  # method.
156
156
  #
157
157
  class RatedPlayer
158
- attr_reader :num, :rating, :kfactor, :games, :bonus
158
+ attr_reader :num, :type, :performance, :results
159
159
  attr_accessor :desc
160
160
 
161
- # After the tournament has been rated, this is the player's new rating.
162
- def new_rating
163
- full_rating? ? @rating + rating_change + @bonus : performance
164
- end
165
-
166
- # After the tournament has been rated, this is the difference between the old and new ratings for
167
- # rated players, based on sum of expected scores in each games and the player's K-factor.
168
- # Zero for all other types of players.
169
- def rating_change
170
- @results.inject(0.0) { |c, r| c + (r.rating_change || 0.0) }
171
- end
172
-
173
- # After the tournament has been rated, this returns the sum of expected scores over all results.
174
- def expected_score
175
- @results.inject(0.0) { |e, r| e + (r.expected_score || 0.0) }
176
- end
177
-
178
- # After the tournament has been rated, this returns the tournament rating performance for
179
- # rated, unrated and foreign players. Returns zero for rated players.
180
- def performance
181
- @performance
182
- end
183
-
184
- # Returns an array of the player's results (ICU::RatedResult) in round order.
185
- def results
186
- @results
161
+ def self.factory(num, args={}) # :nodoc:
162
+ num = check_num(num)
163
+ rating = check_rating(args[:rating])
164
+ kfactor = check_kfactor(args[:kfactor])
165
+ games = check_games(args[:games])
166
+ desc = args[:desc]
167
+ case
168
+ when rating && kfactor && !games then FullRating.new(num, desc, rating, kfactor)
169
+ when rating && !kfactor && games then ProvRating.new(num, desc, rating, games)
170
+ when rating && !kfactor && !games then FrgnRating.new(num, desc, rating)
171
+ when !rating && !kfactor && !games then NoneRating.new(num, desc)
172
+ else raise "invalid combination of player attributes"
173
+ end
187
174
  end
188
175
 
189
- # The sum of the player's scores in all rounds in which they have a result.
190
- def score
191
- @results.inject(0.0) { |e, r| e + r.score }
176
+ def self.check_num(arg) # :nodoc:
177
+ num = arg.to_i
178
+ raise "invalid player num (#{arg})" if num == 0 && !arg.to_s.match(/^\s*\d/)
179
+ num
192
180
  end
193
181
 
194
- # Returns the type of player as a symbol: one of _rated_, _provisional_, _unrated_ or _foreign_.
195
- def type
196
- @type
182
+ def self.check_rating(arg) # :nodoc:
183
+ return unless arg
184
+ rating = arg.to_f
185
+ raise "invalid player rating (#{arg})" if rating == 0.0 && !arg.to_s.match(/^\s*\d/)
186
+ rating
197
187
  end
198
188
 
199
- def full_rating? # :nodoc:
200
- @type == :rated || @type == :foreign
189
+ def self.check_kfactor(arg) # :nodoc:
190
+ return unless arg
191
+ kfactor = arg.to_f
192
+ raise "invalid player k-factor (#{arg})" if kfactor <= 0.0
193
+ kfactor
201
194
  end
202
195
 
203
- def bonus_rating # :nodoc:
204
- @bonus_rating || @rating
196
+ def self.check_games(arg) # :nodoc:
197
+ return unless arg
198
+ games = arg.to_i
199
+ raise "invalid number of games (#{arg})" if games <= 0 || games >= 20
200
+ games
205
201
  end
206
202
 
207
203
  # Calculate a K-factor according to ICU rules.
@@ -219,6 +215,117 @@ module ICU
219
215
  end
220
216
  end
221
217
 
218
+ def reset # :nodoc:
219
+ @performance = nil
220
+ @estimated_performance = nil
221
+ end
222
+
223
+ class FullRating < RatedPlayer # :nodoc:
224
+ attr_reader :rating, :kfactor, :bonus
225
+
226
+ def initialize(num, desc, rating, kfactor)
227
+ @type = :rated
228
+ @rating = rating
229
+ @kfactor = kfactor
230
+ @bonus = 0
231
+ super(num, desc)
232
+ end
233
+
234
+ def reset
235
+ @bonus_rating = nil
236
+ @bonus = 0
237
+ super
238
+ end
239
+
240
+ def rating_change
241
+ @results.inject(0.0) { |c, r| c + (r.rating_change || 0.0) }
242
+ end
243
+
244
+ def new_rating(type=nil)
245
+ case type
246
+ when :start
247
+ @rating # the player's start rating
248
+ when :opponent
249
+ @bonus_rating || @rating # the rating used for opponents during the calculations
250
+ else
251
+ @rating + rating_change + @bonus # the player's final rating
252
+ end
253
+ end
254
+
255
+ def calculate_bonus
256
+ return if @kfactor <= 24 || @results.size <= 4 || @rating >= 2100
257
+ change = rating_change
258
+ return if change <= 35 || @rating + change >= 2100
259
+ threshold = 32 + 3 * (@results.size - 4)
260
+ bonus = (change - threshold).round
261
+ return if bonus <= 0
262
+ bonus = (1.25 * bonus).round if kfactor >= 40
263
+ [2100, @performance].each { |max| bonus = max - @rating if bonus + @rating > max }
264
+ return if bonus <= 0
265
+ bonus = bonus.round
266
+ @bonus_rating = @rating + change + bonus
267
+ @bonus = bonus
268
+ end
269
+
270
+ def update_bonus
271
+ @bonus_rating = @rating + @bonus + rating_change if @bonus_rating
272
+ end
273
+ end
274
+
275
+ class ProvRating < RatedPlayer # :nodoc:
276
+ attr_reader :rating, :games
277
+
278
+ def initialize(num, desc, rating, games)
279
+ @type = :provisional
280
+ @rating = rating
281
+ @games = games
282
+ super(num, desc)
283
+ end
284
+
285
+ def new_rating(type=nil)
286
+ performance
287
+ end
288
+
289
+ def average_performance(new_performance, new_games)
290
+ old_performance = games * rating
291
+ old_games = games
292
+ (new_performance + old_performance) / (new_games + old_games)
293
+ end
294
+ end
295
+
296
+ class NoneRating < RatedPlayer # :nodoc:
297
+ def initialize(num, desc)
298
+ @type = :unrated
299
+ super(num, desc)
300
+ end
301
+
302
+ def new_rating(type=nil)
303
+ performance
304
+ end
305
+ end
306
+
307
+ class FrgnRating < RatedPlayer # :nodoc:
308
+ attr_reader :rating
309
+
310
+ def initialize(num, desc, rating)
311
+ @type = :foreign
312
+ @rating = rating
313
+ super(num, desc)
314
+ end
315
+
316
+ def new_rating(type=nil)
317
+ rating
318
+ end
319
+ end
320
+
321
+ def expected_score
322
+ @results.inject(0.0) { |e, r| e + (r.expected_score || 0.0) }
323
+ end
324
+
325
+ def score
326
+ @results.inject(0.0) { |e, r| e + r.score }
327
+ end
328
+
222
329
  def add_result(result) # :nodoc:
223
330
  raise "invalid result (#{result.class})" unless result.is_a? ICU::RatedResult
224
331
  raise "players cannot score results against themselves" if self == result.opponent
@@ -234,31 +341,25 @@ module ICU
234
341
  @results.sort!{ |a,b| a.round <=> b.round }
235
342
  end
236
343
 
237
- def init # :nodoc:
238
- @performance = nil
239
- @estimated_performance = nil
240
- @bonus_rating = nil
241
- @bonus = 0
242
- end
243
-
244
344
  def rate!(update_bonus=false) # :nodoc:
245
345
  @results.each { |r| r.rate!(self) }
246
- @bonus_rating = @rating + @bonus + rating_change if update_bonus && @bonus_rating
346
+ self.update_bonus if update_bonus && respond_to?(:update_bonus)
247
347
  end
248
348
 
249
349
  def estimate_performance # :nodoc:
250
- new_games, new_performance = results.inject([0,0.0]) do |sum, result|
251
- opponent = result.opponent
252
- if opponent.full_rating? || opponent.performance
350
+ games, performance = results.inject([0,0.0]) do |sum, result|
351
+ rating = result.opponent.new_rating(:opponent)
352
+ if rating
253
353
  sum[0]+= 1
254
- sum[1]+= (opponent.full_rating? ? opponent.bonus_rating : opponent.performance) + (2 * result.score - 1) * 400.0
354
+ sum[1]+= rating + (2 * result.score - 1) * 400.0
255
355
  end
256
356
  sum
257
357
  end
258
- if new_games > 0
259
- old_games, old_performance = type == :provisional ? [games, games * rating] : [0, 0.0]
260
- @estimated_performance = (new_performance + old_performance) / (new_games + old_games)
261
- end
358
+ @estimated_performance = average_performance(performance, games) if games > 0
359
+ end
360
+
361
+ def average_performance(performance, games) # :nodoc:
362
+ performance / games
262
363
  end
263
364
 
264
365
  def update_performance(thresh) # :nodoc:
@@ -274,22 +375,6 @@ module ICU
274
375
  stable
275
376
  end
276
377
 
277
- def calculate_bonus # :nodoc:
278
- # Rounding is performed in places to emulate the older MSAccess implementation.
279
- return if @type != :rated || @kfactor <= 24 || @results.size <= 4 || @rating >= 2100
280
- change = rating_change
281
- return if change <= 35 || @rating + change >= 2100
282
- threshold = 32 + 3 * (@results.size - 4)
283
- bonus = (change - threshold).round
284
- return if bonus <= 0
285
- bonus = (1.25 * bonus).round if kfactor >= 40
286
- [2100, @performance].each { |max| bonus = max - @rating if bonus + @rating > max }
287
- return if bonus <= 0
288
- bonus = bonus.round
289
- @bonus_rating = @rating + change + bonus
290
- @bonus = bonus
291
- end
292
-
293
378
  def ==(other) # :nodoc:
294
379
  return false unless other.is_a? ICU::RatedPlayer
295
380
  num == other.num
@@ -297,42 +382,10 @@ module ICU
297
382
 
298
383
  private
299
384
 
300
- def initialize(num, opt={}) # :nodoc:
301
- self.num = num
302
- [:rating, :kfactor, :games, :desc].each { |atr| self.send("#{atr}=", opt[atr]) unless opt[atr].nil? }
385
+ def initialize(num, desc) # :nodoc:
386
+ @num = num
387
+ @desc = desc
303
388
  @results = []
304
- @type = deduce_type
305
- @bonus = 0
306
- end
307
-
308
- def num=(num)
309
- @num = num.to_i
310
- raise "invalid player num (#{num})" if @num == 0 && !num.to_s.match(/^\s*\d/)
311
- end
312
-
313
- def rating=(rating)
314
- @rating = rating.to_f
315
- raise "invalid player rating (#{rating})" if @rating == 0.0 && !rating.to_s.match(/^\s*\d/)
316
- end
317
-
318
- def kfactor=(kfactor)
319
- @kfactor = kfactor.to_f
320
- raise "invalid player k-factor (#{kfactor})" if @kfactor <= 0.0
321
- end
322
-
323
- def games=(games)
324
- @games = games.to_i
325
- raise "invalid number of games (#{games})" if @games <= 0 || @games >= 20
326
- end
327
-
328
- def deduce_type
329
- case
330
- when @rating && @kfactor && !@games then :rated
331
- when @rating && !@kfactor && @games then :provisional
332
- when @rating && !@kfactor && !@games then :foreign
333
- when !@rating && !@kfactor && !@games then :unrated
334
- else raise "invalid combination of player attributes"
335
- end
336
389
  end
337
390
  end
338
391
  end
@@ -87,32 +87,17 @@ module ICU
87
87
  #
88
88
  # The main rating calculations are available from player methods (see ICU::RatedPlayer)
89
89
  # but additional details are available via methods of each player's individual results:
90
- # _expected_score_, _rating_change_.
90
+ # _expected_score_, _rating_change_ (rated players only).
91
91
  #
92
92
  class RatedResult
93
- attr_reader :round, :opponent, :score
94
-
95
- # After the tournament has been rated, this returns the expected score (between 0 and 1)
96
- # for the player based on the rating difference with the opponent scaled by 400.
97
- # The standard Elo formula is used: 1/(1 + 10^(diff/400)).
98
- def expected_score
99
- @expected_score
100
- end
101
-
102
- # After the tournament has been rated, returns the change in rating due to this particular
103
- # result. Only for rated players (returns _nil_ for other types of players). Computed from
104
- # the difference between actual and expected scores multiplied by the player's K-factor.
105
- # The sum of these changes is the overall rating change for rated players.
106
- def rating_change
107
- @rating_change
108
- end
93
+ attr_reader :round, :opponent, :score, :expected_score, :rating_change
109
94
 
110
95
  def rate!(player) # :nodoc:
111
- player_rating = player.full_rating? ? player.rating : player.performance
112
- opponent_rating = opponent.full_rating? ? opponent.bonus_rating : opponent.performance
96
+ player_rating = player.new_rating(:start)
97
+ opponent_rating = opponent.new_rating(:opponent)
113
98
  if player_rating && opponent_rating
114
99
  @expected_score = 1 / (1 + 10 ** ((opponent_rating - player_rating) / 400.0))
115
- @rating_change = (@score - @expected_score) * player.kfactor if player.type == :rated
100
+ @rating_change = (score - expected_score) * player.kfactor if player.type == :rated
116
101
  end
117
102
  end
118
103
 
@@ -86,7 +86,7 @@ module ICU
86
86
  def add_player(num, args={})
87
87
  raise "player with number #{num} already exists" if @player[num]
88
88
  args[:kfactor] = ICU::RatedPlayer.kfactor(args[:kfactor].merge({ :start => start, :rating => args[:rating] })) if args[:kfactor].is_a?(Hash)
89
- @player[num] = ICU::RatedPlayer.new(num, args)
89
+ @player[num] = ICU::RatedPlayer.factory(num, args)
90
90
  end
91
91
 
92
92
  # Add a new result to the tournament. Two instances of ICU::RatedResult are
@@ -126,7 +126,7 @@ module ICU
126
126
  end
127
127
 
128
128
  # Phase 1.
129
- players.each { |p| p.init }
129
+ players.each { |p| p.reset }
130
130
  @iterations1 = performance_ratings(max_iterations[0], threshold)
131
131
  players.each { |p| p.rate! }
132
132
 
@@ -182,7 +182,7 @@ module ICU
182
182
 
183
183
  # Calculate bonuses for all players and return the number who got one.
184
184
  def calculate_bonuses
185
- @player.values.inject(0) { |t,p| t + (p.calculate_bonus ? 1 : 0) }
185
+ @player.values.select{ |p| p.respond_to?(:bonus) }.inject(0) { |t,p| t + (p.calculate_bonus ? 1 : 0) }
186
186
  end
187
187
  end
188
188
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ICU
4
4
  class Ratings
5
- VERSION = "1.5.0"
5
+ VERSION = "1.5.1"
6
6
  end
7
7
  end
data/spec/player_spec.rb CHANGED
@@ -3,48 +3,44 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
 
4
4
  module ICU
5
5
  describe RatedPlayer do
6
- context "#new - different types of players" do
6
+ context "#factory - different types of players" do
7
7
  before(:all) do
8
- @r = ICU::RatedPlayer.new(1, :rating => 2000, :kfactor => 10.0)
9
- @p = ICU::RatedPlayer.new(2, :rating => 1500, :games => 10)
10
- @f = ICU::RatedPlayer.new(3, :rating => 2500)
11
- @u = ICU::RatedPlayer.new(4)
8
+ @r = ICU::RatedPlayer.factory(1, :rating => 2000, :kfactor => 10.0)
9
+ @p = ICU::RatedPlayer.factory(2, :rating => 1500, :games => 10)
10
+ @f = ICU::RatedPlayer.factory(3, :rating => 2500)
11
+ @u = ICU::RatedPlayer.factory(4)
12
12
  end
13
13
 
14
14
  it "rated players have a rating and k-factor" do
15
15
  @r.num.should == 1
16
16
  @r.rating.should == 2000
17
17
  @r.kfactor.should == 10.0
18
- @r.games.should be_nil
19
18
  @r.type.should == :rated
20
- @r.full_rating?.should be_true
19
+ @r.should_not respond_to(:games)
21
20
  end
22
21
 
23
22
  it "provisionally rated players have a rating and number of games" do
24
- @p.num.should == 2
25
- @p.rating.should == 1500
26
- @p.kfactor.should be_nil
27
- @p.games.should == 10
28
- @p.type.should == :provisional
29
- @p.full_rating?.should be_false
23
+ @p.num.should == 2
24
+ @p.rating.should == 1500
25
+ @p.games.should == 10
26
+ @p.type.should == :provisional
27
+ @p.should_not respond_to(:kfactor)
30
28
  end
31
29
 
32
30
  it "foreign players just have a rating" do
33
- @f.num.should == 3
34
- @f.rating.should == 2500
35
- @f.kfactor.should be_nil
36
- @f.games.should be_nil
37
- @f.type.should == :foreign
38
- @f.full_rating?.should be_true
31
+ @f.num.should == 3
32
+ @f.rating.should == 2500
33
+ @f.type.should == :foreign
34
+ @f.should_not respond_to(:kfactor)
35
+ @f.should_not respond_to(:games)
39
36
  end
40
37
 
41
38
  it "unrated players just have nothing other than their number" do
42
- @u.num.should == 4
43
- @u.rating.should be_nil
44
- @u.kfactor.should be_nil
45
- @u.games.should be_nil
46
- @u.type.should == :unrated
47
- @u.full_rating?.should be_false
39
+ @u.num.should == 4
40
+ @u.type.should == :unrated
41
+ @u.should_not respond_to(:rating)
42
+ @u.should_not respond_to(:kfactor)
43
+ @u.should_not respond_to(:games)
48
44
  end
49
45
 
50
46
  it "other combinations are invalid" do
@@ -53,13 +49,13 @@ module ICU
53
49
  { :games => 10, :kfactor => 10 },
54
50
  { :games => 10, :kfactor => 10, :rating => 1000 },
55
51
  { :kfactor => 10 },
56
- ].each { |opts| lambda { ICU::RatedPlayer.new(1, opts) }.should raise_error(/invalid.*combination/i) }
52
+ ].each { |opts| lambda { ICU::RatedPlayer.factory(1, opts) }.should raise_error(/invalid.*combination/i) }
57
53
  end
58
54
  end
59
55
 
60
56
  context "#new - miscellaneous" do
61
57
  it "attribute values can be given by strings, even when space padded" do
62
- p = ICU::RatedPlayer.new(' 1 ', :kfactor => ' 10.0 ', :rating => ' 1000 ')
58
+ p = ICU::RatedPlayer.factory(' 1 ', :kfactor => ' 10.0 ', :rating => ' 1000 ')
63
59
  p.num.should == 1
64
60
  p.kfactor.should == 10.0
65
61
  p.rating.should == 1000
@@ -68,46 +64,46 @@ module ICU
68
64
 
69
65
  context "restrictions, or lack thereof, on attributes" do
70
66
  it "the player number can be zero or even negative" do
71
- lambda { ICU::RatedPlayer.new(-1) }.should_not raise_error
72
- lambda { ICU::RatedPlayer.new(0) }.should_not raise_error
67
+ lambda { ICU::RatedPlayer.factory(-1) }.should_not raise_error
68
+ lambda { ICU::RatedPlayer.factory(0) }.should_not raise_error
73
69
  end
74
70
 
75
71
  it "k-factors must be positive" do
76
- lambda { ICU::RatedPlayer.new(1, :kfactor => 0) }.should raise_error(/invalid.*factor/i)
77
- lambda { ICU::RatedPlayer.new(1, :kfactor => -1) }.should raise_error(/invalid.*factor/i)
72
+ lambda { ICU::RatedPlayer.factory(1, :kfactor => 0) }.should raise_error(/invalid.*factor/i)
73
+ lambda { ICU::RatedPlayer.factory(1, :kfactor => -1) }.should raise_error(/invalid.*factor/i)
78
74
  end
79
75
 
80
76
  it "the rating can be zero or even negative" do
81
- lambda { ICU::RatedPlayer.new(1, :rating => 0) }.should_not raise_error
82
- lambda { ICU::RatedPlayer.new(1, :rating => -1) }.should_not raise_error
77
+ lambda { ICU::RatedPlayer.factory(1, :rating => 0) }.should_not raise_error
78
+ lambda { ICU::RatedPlayer.factory(1, :rating => -1) }.should_not raise_error
83
79
  end
84
80
 
85
81
  it "ratings are stored as floats but can be specified with an integer" do
86
- ICU::RatedPlayer.new(1, :rating => 1234.5).rating.should == 1234.5
87
- ICU::RatedPlayer.new(1, :rating => 1234.0).rating.should == 1234
88
- ICU::RatedPlayer.new(1, :rating => 1234).rating.should == 1234
82
+ ICU::RatedPlayer.factory(1, :rating => 1234.5).rating.should == 1234.5
83
+ ICU::RatedPlayer.factory(1, :rating => 1234.0).rating.should == 1234
84
+ ICU::RatedPlayer.factory(1, :rating => 1234).rating.should == 1234
89
85
  end
90
86
 
91
87
  it "the number of games shoud not exceed 20" do
92
- lambda { ICU::RatedPlayer.new(1, :rating => 1000, :games => 19) }.should_not raise_error
93
- lambda { ICU::RatedPlayer.new(1, :rating => 1000, :games => 20) }.should raise_error
94
- lambda { ICU::RatedPlayer.new(1, :rating => 1000, :games => 21) }.should raise_error
88
+ lambda { ICU::RatedPlayer.factory(1, :rating => 1000, :games => 19) }.should_not raise_error
89
+ lambda { ICU::RatedPlayer.factory(1, :rating => 1000, :games => 20) }.should raise_error
90
+ lambda { ICU::RatedPlayer.factory(1, :rating => 1000, :games => 21) }.should raise_error
95
91
  end
96
92
 
97
93
  it "a description, such as a name, but can be any object, is optional" do
98
- ICU::RatedPlayer.new(1, :desc => 'Fischer, Robert').desc.should == 'Fischer, Robert'
99
- ICU::RatedPlayer.new(1, :desc => 1).desc.should be_an_instance_of(Fixnum)
100
- ICU::RatedPlayer.new(1, :desc => 1.0).desc.should be_an_instance_of(Float)
101
- ICU::RatedPlayer.new(1).desc.should be_nil
94
+ ICU::RatedPlayer.factory(1, :desc => 'Fischer, Robert').desc.should == 'Fischer, Robert'
95
+ ICU::RatedPlayer.factory(1, :desc => 1).desc.should be_an_instance_of(Fixnum)
96
+ ICU::RatedPlayer.factory(1, :desc => 1.0).desc.should be_an_instance_of(Float)
97
+ ICU::RatedPlayer.factory(1).desc.should be_nil
102
98
  end
103
99
  end
104
100
 
105
101
  context "results" do
106
102
  before(:each) do
107
103
  @p = ICU::RatedPlayer.new(1, :kfactor => 10, :rating => 1000)
108
- @r1 = ICU::RatedResult.new(1, ICU::RatedPlayer.new(2), 'W')
109
- @r2 = ICU::RatedResult.new(2, ICU::RatedPlayer.new(3), 'L')
110
- @r3 = ICU::RatedResult.new(3, ICU::RatedPlayer.new(4), 'D')
104
+ @r1 = ICU::RatedResult.new(1, ICU::RatedPlayer.factory(2), 'W')
105
+ @r2 = ICU::RatedResult.new(2, ICU::RatedPlayer.factory(3), 'L')
106
+ @r3 = ICU::RatedResult.new(3, ICU::RatedPlayer.factory(4), 'D')
111
107
  end
112
108
 
113
109
  it "should be returned in round order" do
data/spec/result_spec.rb CHANGED
@@ -5,20 +5,20 @@ module ICU
5
5
  describe RatedResult do
6
6
  context "a basic rated result" do
7
7
  before(:all) do
8
- @o = ICU::RatedPlayer.new(2)
8
+ @o = ICU::RatedPlayer.factory(2)
9
9
  end
10
10
 
11
11
  it "needs a round, opponent and score (win, loss or draw)" do
12
12
  r = ICU::RatedResult.new(1, @o, 'W')
13
13
  r.round.should == 1
14
- r.opponent.should be_an_instance_of(ICU::RatedPlayer)
14
+ r.opponent.should be_a_kind_of(ICU::RatedPlayer)
15
15
  r.score.should == 1.0
16
16
  end
17
17
  end
18
18
 
19
19
  context "restrictions, or lack thereof, on attributes" do
20
20
  before(:each) do
21
- @p = ICU::RatedPlayer.new(2)
21
+ @p = ICU::RatedPlayer.factory(2)
22
22
  end
23
23
 
24
24
  it "round numbers must be positive" do
@@ -41,7 +41,7 @@ module ICU
41
41
 
42
42
  context "#opponents_score" do
43
43
  before(:each) do
44
- @p = ICU::RatedPlayer.new(2)
44
+ @p = ICU::RatedPlayer.factory(2)
45
45
  end
46
46
 
47
47
  it "should give the score from the opponent's perspective" do
@@ -53,8 +53,8 @@ module ICU
53
53
 
54
54
  context "equality" do
55
55
  before(:each) do
56
- @p1 = ICU::RatedPlayer.new(1)
57
- @p2 = ICU::RatedPlayer.new(2)
56
+ @p1 = ICU::RatedPlayer.factory(1)
57
+ @p2 = ICU::RatedPlayer.factory(2)
58
58
  @r1 = ICU::RatedResult.new(1, @p1, 'W')
59
59
  @r2 = ICU::RatedResult.new(1, @p1, 'W')
60
60
  @r3 = ICU::RatedResult.new(2, @p1, 'W')
@@ -343,7 +343,7 @@ module ICU
343
343
  unless num == 13
344
344
  p = @t.player(num)
345
345
  p.expected_score.should_not == 0.0
346
- p.rating_change.should == 0.0
346
+ p.should_not respond_to(:rating_change)
347
347
  p.new_rating.should == p.rating
348
348
  end
349
349
  end
@@ -762,17 +762,17 @@ module ICU
762
762
 
763
763
  it "should agree with ICU rating database" do
764
764
  [
765
- [1, 3.5, 3.84, 977, 1073, 0], # Austin
766
- [2, 4.0, 3.51, 1068, 1042, 0], # Kevin
767
- [3, 1.0, 1.05, 636, 636, 0], # Nikhil
768
- [4, 0.0, 0.78, 520, 537, 0], # Sean
769
- [5, 3.5, 1.74, 1026, 835, 45], # Michael
770
- [6, 3.0, 3.54, 907, 1010, 0], # Eoin
765
+ [1, 3.5, 3.84, 977, 1073, 0], # Austin
766
+ [2, 4.0, 3.51, 1068, 1042, 0], # Kevin
767
+ [3, 1.0, 1.05, 636, 636, nil], # Nikhil
768
+ [4, 0.0, 0.78, 520, 537, 0], # Sean
769
+ [5, 3.5, 1.74, 1026, 835, 45], # Michael
770
+ [6, 3.0, 3.54, 907, 1010, 0], # Eoin
771
771
  ].each do |item|
772
772
  num, score, expected_score, performance, new_rating, bonus = item
773
773
  p = @t.player(num)
774
774
  p.score.should == score
775
- p.bonus.should == bonus
775
+ p.bonus.should == bonus if bonus
776
776
  p.performance.should be_within(0.5).of(performance)
777
777
  p.expected_score.should be_within(0.01).of(expected_score)
778
778
  p.new_rating.should be_within(0.5).of(new_rating)
@@ -817,13 +817,13 @@ module ICU
817
817
 
818
818
  @m = [
819
819
  # MSAccess results taken from rerun of original which is different (reason unknown).
820
- [1, 4.0, 4.28, 1052, 1052, 0], # Fanjini
821
- [2, 3.5, 1.93, 920, 757, 35], # Guinan
822
- [3, 3.5, 2.29, 932, 798, 18], # Duffy
823
- [4, 1.0, 1.52, 588, 707, 0], # Cooke
824
- [5, 1.0, 1.40, 828, 828, 0], # Callaghan
825
- [6, 1.0, 0.91, 627, 627, 0], # Montenegro
826
- [7, 0.0, 0.78, 460, 635, 0], # Lowry-O'Reilly
820
+ [1, 4.0, 4.28, 1052, 1052, nil], # Fanjini
821
+ [2, 3.5, 1.93, 920, 757, 35], # Guinan
822
+ [3, 3.5, 2.29, 932, 798, 18], # Duffy
823
+ [4, 1.0, 1.52, 588, 707, 0], # Cooke
824
+ [5, 1.0, 1.40, 828, 828, nil], # Callaghan
825
+ [6, 1.0, 0.91, 627, 627, nil], # Montenegro
826
+ [7, 0.0, 0.78, 460, 635, 0], # Lowry-O'Reilly
827
827
  ]
828
828
  end
829
829
 
@@ -832,7 +832,7 @@ module ICU
832
832
  num, score, expected_score, performance, new_rating, bonus = item
833
833
  p = @t.player(num)
834
834
  p.score.should == score
835
- p.bonus.should == bonus
835
+ p.bonus.should == bonus if bonus
836
836
  p.performance.should be_within(num == 2 ? 0.6 : 0.5).of(performance)
837
837
  p.expected_score.should be_within(0.01).of(expected_score)
838
838
  p.new_rating.should be_within(0.5).of(new_rating)
@@ -845,7 +845,7 @@ module ICU
845
845
  num, score, expected_score, performance, new_rating, bonus = item
846
846
  p = @t.player(num)
847
847
  p.score.should == score
848
- p.bonus.should == bonus
848
+ p.bonus.should == bonus if bonus
849
849
  p.performance.should be_within(num == 2 ? 0.6 : 0.5).of(performance)
850
850
  p.expected_score.should be_within(0.01).of(expected_score)
851
851
  p.new_rating.should be_within(0.5).of(new_rating)
@@ -859,7 +859,7 @@ module ICU
859
859
  num, score, expected_score, performance, new_rating, bonus = item
860
860
  p = @t.player(num)
861
861
  p.score.should == score
862
- p.bonus.should == 0
862
+ p.bonus.should == 0 if bonus
863
863
  p.performance.should_not be_within(1.0).of(performance)
864
864
  p.expected_score.should_not be_within(0.01).of(expected_score)
865
865
  p.new_rating.should_not be_within(1.0).of(new_rating)
@@ -986,7 +986,7 @@ module ICU
986
986
  end
987
987
 
988
988
  it "should behave like ratings.ciu.ie" do
989
- @p1.send("kfactor=", 32)
989
+ @p1.instance_eval { @kfactor = 32 }
990
990
  @t.rate!
991
991
  @p1.new_rating.should be_within(0.5).of(1603)
992
992
  @p1.expected_score.should be_within(0.001).of(2.868)
@@ -1230,7 +1230,7 @@ module ICU
1230
1230
  @o5.new_rating.should == @o5.performance
1231
1231
  @o6.new_rating.should == @o6.performance
1232
1232
 
1233
- ratings = [@o1, @o2, @o3, @o4, @o5, @o6].map { |o| o.type == :rated ? o.rating : o.new_rating }
1233
+ ratings = [@o1, @o2, @o3, @o4, @o5, @o6].map { |o| o.new_rating(:opponent) }
1234
1234
 
1235
1235
  average_of_ratings = ratings.inject(0.0){ |m,r| m = m + r } / 6.0
1236
1236
  average_of_ratings.should_not be_within(0.5).of(@p.new_rating)
@@ -1251,7 +1251,7 @@ module ICU
1251
1251
  @o5.new_rating.should == @o5.performance
1252
1252
  @o6.new_rating.should == @o6.performance
1253
1253
 
1254
- ratings = [@o1, @o2, @o3, @o4, @o5, @o6].map { |o| o.type == :rated ? o.rating : o.new_rating }
1254
+ ratings = [@o1, @o2, @o3, @o4, @o5, @o6].map { |o| o.new_rating(:opponent) }
1255
1255
 
1256
1256
  average_of_ratings = ratings.inject(0.0){ |m,r| m = m + r } / 6.0
1257
1257
  average_of_ratings.should be_within(0.5).of(@p.new_rating)
@@ -2079,12 +2079,12 @@ module ICU
2079
2079
  end
2080
2080
  end
2081
2081
 
2082
- context "#rate - Dierdre Turner in the Limerick U1400 2012" do
2082
+ context "#rate - Deirdre Turner in the Limerick U1400 2012" do
2083
2083
  before(:each) do
2084
2084
  @t = ICU::RatedTournament.new(desc: "Limerick U1400 2012")
2085
2085
 
2086
- # Add the players of most interest (Dierdre Turner and her opponents).
2087
- @p = @t.add_player(6697, desc: "Dierdre Turner")
2086
+ # Add the players of most interest (Deirdre Turner and her opponents).
2087
+ @p = @t.add_player(6697, desc: "Deirdre Turner")
2088
2088
  @o1 = @t.add_player(6678, desc: "John P. Dunne", rating: 980, kfactor: 40)
2089
2089
  @o2 = @t.add_player(6694, desc: "Jordan O'Sullivan")
2090
2090
  @o3 = @t.add_player(6681, desc: "Ruairi Freeman", rating: 537, kfactor: 40)
@@ -2111,7 +2111,7 @@ module ICU
2111
2111
  @t.add_player(6696, desc: "Mark David Tonita")
2112
2112
  @t.add_player(6698, desc: "Eoghan Turner")
2113
2113
 
2114
- # Dierdre's results.
2114
+ # Deirdre's results.
2115
2115
  @t.add_result(1, 6697, 6678, "L")
2116
2116
  @t.add_result(2, 6697, 6694, "W")
2117
2117
  @t.add_result(3, 6697, 6681, "W")
@@ -2183,7 +2183,7 @@ module ICU
2183
2183
  end
2184
2184
 
2185
2185
  it "should be setup properly" do
2186
- @p.desc.should == "Dierdre Turner"
2186
+ @p.desc.should == "Deirdre Turner"
2187
2187
  @o1.desc.should == "John P. Dunne"
2188
2188
  @o2.desc.should == "Jordan O'Sullivan"
2189
2189
  @o3.desc.should == "Ruairi Freeman"
@@ -2200,7 +2200,7 @@ module ICU
2200
2200
  @o6.type.should == :provisional
2201
2201
 
2202
2202
  @o1.rating.should == 980
2203
- @o2.rating.should be_nil
2203
+ @o2.should_not respond_to(:rating)
2204
2204
  @o3.rating.should == 537
2205
2205
  @o4.rating.should == 682
2206
2206
  @o5.rating.should == 1320
@@ -2216,16 +2216,14 @@ module ICU
2216
2216
  @p.new_rating.should == @p.performance
2217
2217
 
2218
2218
  @o1.bonus.should == 23
2219
- @o2.bonus.should == 0
2220
2219
  @o3.bonus.should == 0
2221
2220
  @o4.bonus.should == 0
2222
2221
  @o5.bonus.should == 0
2223
- @o6.bonus.should == 0
2224
2222
 
2225
- ratings = [@o1, @o2, @o3, @o4, @o5, @o6].map { |o| o.bonus > 0 || o.type != :rated ? o.new_rating : o.rating }
2223
+ ratings = [@o1, @o2, @o3, @o4, @o5, @o6].map { |o| o.new_rating(:opponent) }
2226
2224
 
2227
2225
  performance = ratings.inject(0.0){ |m,r| m = m + r } / 6.0
2228
- performance.should be_within(0.5).of(@p.new_rating)
2226
+ performance.should be_within(0.1).of(@p.new_rating)
2229
2227
 
2230
2228
  @t.iterations1.should be > 1
2231
2229
  @t.iterations2.should be > 1
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icu_ratings
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.5.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-26 00:00:00.000000000 Z
12
+ date: 2012-12-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -111,7 +111,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
111
111
  version: '0'
112
112
  segments:
113
113
  - 0
114
- hash: -4449600080095585228
114
+ hash: 1156445910149714022
115
115
  required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
@@ -120,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
120
  version: '0'
121
121
  segments:
122
122
  - 0
123
- hash: -4449600080095585228
123
+ hash: 1156445910149714022
124
124
  requirements: []
125
125
  rubyforge_project: icu_ratings
126
126
  rubygems_version: 1.8.23