icu_ratings 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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