icu_ratings 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- :patch: 1
2
+ :patch: 0
3
3
  :build:
4
- :major: 0
5
- :minor: 3
4
+ :major: 1
5
+ :minor: 0
@@ -129,7 +129,24 @@ if it's used at all, is expected to be for player names in the form of String va
129
129
 
130
130
  After the <em>rate!</em> method has been called on the ICU::RatedTournament object, the results
131
131
  of the rating calculations are available via various methods of the player objects:
132
- _new_rating_, _rating_change_, _performance_, _expected_score_.
132
+
133
+ _new_rating_:: This is the player's new rating. For rated players it is their old rating
134
+ plus their _rating_change_. For provisional players it is their performance
135
+ rating including their previous games. For unrated players it is their
136
+ tournament performance rating. New rarings are not calculated for foreign
137
+ players so this method just returned their start _rating_.
138
+ _rating_change_:: This is the difference between the old and new ratings for rated players,
139
+ based on sum of expected scores in each games and the player's K-factor.
140
+ Zero for all other types of players.
141
+ _performance_:: This returns the tournament rating performance for rated, unrated and
142
+ foreign players. For provisional players it returns a weighted average
143
+ of the player's tournament performance and their previous games. For
144
+ provisional and unrated players it is the same as _new_rating_.
145
+ _expected_score_:: This returns the sum of expected scores over all results for all player types.
146
+ For rated players, this number times the K-factor gives their rating change.
147
+ It is calculated for provisional, unrated and foreign players but not actually
148
+ used to estimate new ratings (for provisional and unrated players performance
149
+ estimates are used instead).
133
150
 
134
151
  == Unrateable Players
135
152
 
@@ -142,14 +159,11 @@ method.
142
159
 
143
160
  class RatedPlayer
144
161
  attr_reader :num, :rating, :kfactor, :games
145
- attr_accessor :desc
162
+ attr_accessor :desc, :bonus
146
163
 
147
- # After the tournament has been rated, this is the player's new rating. For rated players this is the old rating
148
- # plus the _rating_change_. For provisional players it is their performance rating, including their previous
149
- # games. For unrated players it is their tournament performance rating. For foreign players it is the same
150
- # as their start _rating_.
164
+ # After the tournament has been rated, this is the player's new rating.
151
165
  def new_rating
152
- full_rating? ? rating + rating_change : performance
166
+ full_rating? ? @rating + rating_change + @bonus : performance
153
167
  end
154
168
 
155
169
  # After the tournament has been rated, this is the difference between the old and new ratings for
@@ -160,16 +174,12 @@ method.
160
174
  end
161
175
 
162
176
  # After the tournament has been rated, this returns the sum of expected scores over all results.
163
- # Although this is calculated for provisional and unrated players it is not used to estimate their
164
- # new ratings. For rated players, this number times the K-factor gives the change in rating.
165
177
  def expected_score
166
178
  @results.inject(0.0) { |e, r| e + (r.expected_score || 0.0) }
167
179
  end
168
180
 
169
181
  # After the tournament has been rated, this returns the tournament rating performance for
170
- # rated, unrated and foreign players. For provisional players it returns a weighted average
171
- # of the player's tournament performance and their previous games. For provisional and
172
- # unrated players it is the same as _new_rating_.
182
+ # rated, unrated and foreign players. Returns zero for rated players.
173
183
  def performance
174
184
  @performance
175
185
  end
@@ -189,6 +199,14 @@ method.
189
199
  @type
190
200
  end
191
201
 
202
+ def full_rating? # :nodoc:
203
+ @type == :rated || @type == :foreign
204
+ end
205
+
206
+ def bonus_rating # :nodoc:
207
+ @bonus_rating || @rating
208
+ end
209
+
192
210
  # Calculate a K-factor according to ICU rules.
193
211
  def self.kfactor(args)
194
212
  %w{rating start dob joined}.each { |a| raise "missing #{a} for K-factor calculation" unless args[a.to_sym] }
@@ -219,17 +237,15 @@ method.
219
237
  @results.sort!{ |a,b| a.round <=> b.round }
220
238
  end
221
239
 
222
- def rate! # :nodoc:
223
- @results.each { |r| r.rate!(self) }
224
- end
225
-
226
- def full_rating? # :nodoc:
227
- @type == :rated || @type == :foreign
228
- end
229
-
230
- def init_performance # :nodoc:
240
+ def init # :nodoc:
231
241
  @performance = nil
232
242
  @estimated_performance = nil
243
+ @bonus_rating = nil
244
+ @bonus = 0
245
+ end
246
+
247
+ def rate! # :nodoc:
248
+ @results.each { |r| r.rate!(self) }
233
249
  end
234
250
 
235
251
  def estimate_performance # :nodoc:
@@ -237,7 +253,7 @@ method.
237
253
  opponent = result.opponent
238
254
  if opponent.full_rating? || opponent.performance
239
255
  sum[0]+= 1
240
- sum[1]+= (opponent.full_rating? ? opponent.rating : opponent.performance) + (2 * result.score - 1) * 400.0
256
+ sum[1]+= (opponent.full_rating? ? opponent.bonus_rating : opponent.performance) + (2 * result.score - 1) * 400.0
241
257
  end
242
258
  sum
243
259
  end
@@ -249,14 +265,33 @@ method.
249
265
 
250
266
  def update_performance # :nodoc:
251
267
  stable = case
252
- when @performance && @estimated_performance then (@performance - @estimated_performance).abs < 0.5
253
- when !@performance && !@estimated_performance then true
254
- else false
268
+ when @performance && @estimated_performance then
269
+ (@performance - @estimated_performance).abs < 0.5
270
+ when !@performance && !@estimated_performance then
271
+ true
272
+ else
273
+ false
255
274
  end
256
275
  @performance = @estimated_performance if @estimated_performance
257
276
  stable
258
277
  end
259
278
 
279
+ def calculate_bonus # :nodoc:
280
+ # Rounding is performed in places to emulate the older MSAccess implementation.
281
+ return if @type != :rated || @kfactor <= 24 || @results.size <= 4 || @rating >= 2100
282
+ change = rating_change
283
+ return if change <= 35 || @rating + change >= 2100
284
+ threshold = 32 + 3 * (@results.size - 4)
285
+ bonus = (change - threshold).round
286
+ return if bonus <= 0
287
+ bonus = (1.25 * bonus).round if kfactor >= 40
288
+ [2100, @performance].each { |max| bonus = max - @rating if bonus + @rating > max }
289
+ return if bonus <= 0
290
+ bonus = bonus.round
291
+ @bonus_rating = @rating + change + bonus
292
+ @bonus = bonus
293
+ end
294
+
260
295
  def ==(other) # :nodoc:
261
296
  return false unless other.is_a? ICU::RatedPlayer
262
297
  num == other.num
@@ -269,6 +304,7 @@ method.
269
304
  [:rating, :kfactor, :games, :desc].each { |atr| self.send("#{atr}=", opt[atr]) unless opt[atr].nil? }
270
305
  @results = []
271
306
  @type = deduce_type
307
+ @bonus = 0
272
308
  end
273
309
 
274
310
  def num=(num)
@@ -125,9 +125,9 @@ _expected_score_, _rating_change_.
125
125
  end
126
126
 
127
127
  def rate!(player) # :nodoc:
128
- player_rating = player.full_rating? ? player.rating : player.performance
129
- opponent_rating = opponent.full_rating? ? opponent.rating : opponent.performance
130
- if (player_rating && opponent_rating)
128
+ player_rating = player.full_rating? ? player.rating : player.performance
129
+ opponent_rating = opponent.full_rating? ? opponent.bonus_rating : opponent.performance
130
+ if player_rating && opponent_rating
131
131
  @expected_score = 1 / (1 + 10 ** ((opponent_rating - player_rating) / 400.0))
132
132
  @rating_change = (@score - @expected_score) * player.kfactor if player.type == :rated
133
133
  end
@@ -6,17 +6,19 @@ module ICU
6
6
 
7
7
  == Creating Tournaments
8
8
 
9
- ICU::RatedTournament object are created directly.
9
+ ICU::RatedTournament objects are created directly.
10
10
 
11
11
  t = ICU::RatedTournament.new
12
12
 
13
- They have two optional parameters. One is called _desc_ (short for description) the value of which can be
14
- any object but will, if utilized, typically be the name of the tournament as a string.
13
+ They have some optional parameters which can be set via the constructor or by calling
14
+ the same-named setter methods. One is called _desc_ (short for description) the value
15
+ of which can be any object but will, if utilized, typically be the name of the
16
+ tournament as a string.
15
17
 
16
18
  t = ICU::RatedTournament.new(:desc => "Irish Championships 2010")
17
19
  puts t.desc # "Irish Championships 2010"
18
20
 
19
- The other optional parameter is _start_ for the start date. A Date object or a string that can be
21
+ Another optional parameter is _start_ for the start date. A Date object or a string that can be
20
22
  parsed as a string can be used to set it. The European convention is preferred for dates like
21
23
  "03/06/2013" (3rd of June, not 6th of March). Attempting to set an invalid date will raise an
22
24
  exception.
@@ -25,6 +27,15 @@ exception.
25
27
  puts t.start.class # Date
26
28
  puts t.start.to_s # "2010-07-01"
27
29
 
30
+ Also, there is the option _no_bonuses_. Bonuses are a feature of the ICU rating system. If you are
31
+ rating a non-ICU tournament (such as a FIDE tournament) where bonues are not awarded use this option.
32
+
33
+ t = ICU::RatedTournament.new(:desc => 'Linares', :start => "07/02/2009", :no_bonuses => true)
34
+
35
+ Note, however, that the ICU system also has its own unique way of treating provisional, unrated and
36
+ foreign players, so the only FIDE tournaments that can be rated using this software are those that
37
+ consist solely of rated players.
38
+
28
39
  == Rating Tournaments
29
40
 
30
41
  To rate a tournament, first add the players (see ICU::RatedPlayer for details):
@@ -54,8 +65,8 @@ Some of the above methods have the potential to raise RuntimeError exceptions.
54
65
  In the case of _add_player_ and _add_result_, the use of invalid arguments
55
66
  would cause such an error. Theoretically, the <em>rate!</em> method could also throw an
56
67
  exception if the iterative algorithm it uses to estimate performance ratings
57
- of unrated players failed to converge. However, practical experience has shown that
58
- this is highly unlikely.
68
+ of unrated players failed to converge. However an instance of non-convergence
69
+ has yet to be observed in practice.
59
70
 
60
71
  Since exception throwing is how errors are signalled, you should arrange for them
61
72
  to be caught and handled in some suitable place in your code.
@@ -64,7 +75,7 @@ to be caught and handled in some suitable place in your code.
64
75
 
65
76
  class RatedTournament
66
77
  attr_accessor :desc
67
- attr_reader :start
78
+ attr_reader :start, :no_bonuses
68
79
 
69
80
  # Add a new player to the tournament. Returns the instance of ICU::RatedPlayer created.
70
81
  # See ICU::RatedPlayer for details.
@@ -91,8 +102,14 @@ to be caught and handled in some suitable place in your code.
91
102
 
92
103
  # Rate the tournament. Called after all players and results have been added.
93
104
  def rate!
94
- performance_ratings
105
+ players.each { |p| p.init }
106
+ performance_ratings(30)
95
107
  players.each { |p| p.rate! }
108
+ if !no_bonuses && calculate_bonuses > 0
109
+ players.each { |p| p.rate! }
110
+ performance_ratings(1)
111
+ calculate_bonuses
112
+ end
96
113
  end
97
114
 
98
115
  # Return an array of all players, in order of player number.
@@ -110,23 +127,33 @@ to be caught and handled in some suitable place in your code.
110
127
  @start = ICU::Util.parsedate!(date)
111
128
  end
112
129
 
130
+ # Set whether there are no bonuses (false by default)
131
+ def no_bonuses=(no_bonuses)
132
+ @no_bonuses = no_bonuses ? true : false
133
+ end
134
+
113
135
  private
114
136
 
115
137
  # Create a new, empty (no players, no results) tournament.
116
138
  def initialize(opt={})
117
- [:desc, :start].each { |atr| self.send("#{atr}=", opt[atr]) unless opt[atr].nil? }
139
+ [:desc, :start, :no_bonuses].each { |atr| self.send("#{atr}=", opt[atr]) unless opt[atr].nil? }
118
140
  @player = Hash.new
119
141
  end
120
142
 
121
- def performance_ratings
122
- @player.values.each { |p| p.init_performance }
143
+ # Calculate performance ratings either iteratively or with just one sweep for bonus calculations.
144
+ def performance_ratings(max)
123
145
  stable, count = false, 0
124
- while !stable && count < 30
146
+ while !stable && count < max
125
147
  @player.values.each { |p| p.estimate_performance }
126
148
  stable = @player.values.inject(true) { |ok, p| p.update_performance && ok }
127
149
  count+= 1
128
150
  end
129
- raise "performance rating estimation did not converge" unless stable
151
+ raise "performance rating estimation did not converge" if max > 1 && !stable
152
+ end
153
+
154
+ # Calculate bonuses for all players and return the number who got one.
155
+ def calculate_bonuses
156
+ @player.values.inject(0) { |t,p| t + (p.calculate_bonus ? 1 : 0) }
130
157
  end
131
158
  end
132
159
  end
@@ -614,6 +614,259 @@ module ICU
614
614
  end
615
615
  end
616
616
 
617
+ context "#rate - a tournament with only rated players that included one player who received a bonus" do
618
+ # 1 Howley, Kieran 3509 4 2:W 3:W 4:L 5:W 6:W
619
+ # 2 O'Brien, Pat 12057 3 1:L 3:L 4:W 5:W 6:W
620
+ # 3 Eyers, Michael 6861 2.5 1:L 2:W 4:L 5:W 6:D
621
+ # 4 Guinan, Sean 12161 2.5 1:W 2:L 3:W 5:L 6:D
622
+ # 5 Cooke, Frank 10771 1.5 1:L 2:L 3:L 4:W 6:D
623
+ # 6 Benson, Nicola 6916 1.5 1:L 2:L 3:D 4:D 5:D
624
+ before(:all) do
625
+ @t = ICU::RatedTournament.new(:desc => "Drogheda Club Championship, 2009, Section G")
626
+ @t.add_player(1, :desc => 'Howley, Kieran', :rating => 1046, :kfactor => 24)
627
+ @t.add_player(2, :desc => "O'Brien, Pat", :rating => 953, :kfactor => 24)
628
+ @t.add_player(3, :desc => 'Eyers, Michael', :rating => 922, :kfactor => 32)
629
+ @t.add_player(4, :desc => 'Guinan, Sean', :rating => 760, :kfactor => 40)
630
+ @t.add_player(5, :desc => 'Cooke, Frank', :rating => 825, :kfactor => 32)
631
+ @t.add_player(6, :desc => 'Benson, Nicola', :rating => 1002, :kfactor => 32)
632
+
633
+ @t.add_result(1, 1, 6, 'W')
634
+ @t.add_result(1, 2, 5, 'W')
635
+ @t.add_result(1, 3, 4, 'L')
636
+ @t.add_result(2, 6, 4, 'D')
637
+ @t.add_result(2, 5, 3, 'L')
638
+ @t.add_result(2, 1, 2, 'W')
639
+ @t.add_result(3, 2, 6, 'W')
640
+ @t.add_result(3, 3, 1, 'L')
641
+ @t.add_result(3, 4, 5, 'L')
642
+ @t.add_result(4, 6, 5, 'D')
643
+ @t.add_result(4, 1, 4, 'L')
644
+ @t.add_result(4, 2, 3, 'L')
645
+ @t.add_result(5, 3, 6, 'D')
646
+ @t.add_result(5, 4, 2, 'L')
647
+ @t.add_result(5, 5, 1, 'L')
648
+
649
+ @t.rate!
650
+ end
651
+
652
+ it "should agree with ICU rating database" do
653
+ [
654
+ [1, 4.0, 3.43, 1060, 0], # Howley
655
+ [2, 3.0, 2.70, 960, 0], # O'Brien
656
+ [3, 2.5, 2.44, 924, 0], # Eyers
657
+ [4, 2.5, 1.30, 824, 16], # Guinan
658
+ [5, 1.5, 1.67, 819, 0], # Cooke
659
+ [6, 1.5, 3.09, 951, 0], # Benson
660
+ ].each do |item|
661
+ num, score, expected_score, new_rating, bonus = item
662
+ p = @t.player(num)
663
+ p.score.should == score
664
+ p.expected_score.should be_close(expected_score, 0.01)
665
+ p.new_rating.should be_close(new_rating, 0.5)
666
+ p.bonus.should == bonus
667
+ end
668
+ end
669
+ end
670
+
671
+ context "#rate - a tournament with one rated player who got a bonus and the rest foreigners" do
672
+ # 1 Magee, Ronan 10470 6 8:L 0:+ 3:W 0:+ 5:L 7:W 6:W 4:W 2:L
673
+ # 2 Antal Tibor, Kende 17014 1 0: 0: 0: 0: 0: 0: 0: 0: 1:W
674
+ # 3 Arsic, Djordje 17015 0 0: 0: 1:L 0: 0: 0: 0: 0: 0:
675
+ # 4 Donchenko, Alexander 17016 0 0: 0: 0: 0: 0: 0: 0: 1:L 0:
676
+ # 5 Emdin, Mark 17017 1 0: 0: 0: 0: 1:W 0: 0: 0: 0:
677
+ # 6 Lushnikov, Evgeny 17018 0 0: 0: 0: 0: 0: 0: 1:L 0: 0:
678
+ # 7 Pulpan, Jakub 17019 0 0: 0: 0: 0: 0: 1:L 0: 0: 0:
679
+ # 8 Toma, Radu-Cristian 17020 1 1:W 0: 0: 0: 0: 0: 0: 0: 0:
680
+ before(:all) do
681
+ @t = ICU::RatedTournament.new(:desc => "European Youth Chess Championships, 2008")
682
+ @t.add_player(1, :desc => 'Magee, Ronan', :rating => 1667, :kfactor => 40)
683
+ @t.add_player(2, :desc => "Antal Tibor, Kende", :rating => 2036)
684
+ @t.add_player(3, :desc => 'Arsic, Djordje', :rating => 1790)
685
+ @t.add_player(4, :desc => 'Donchenko, Alexander', :rating => 1832)
686
+ @t.add_player(5, :desc => 'Emdin, Mark', :rating => 1832)
687
+ @t.add_player(6, :desc => 'Lushnikov, Evgeny', :rating => 1939)
688
+ @t.add_player(7, :desc => 'Pulpan, Jakub', :rating => 1955)
689
+ @t.add_player(8, :desc => 'Toma, Radu-Cristian', :rating => 1893)
690
+
691
+ @t.add_result(1, 1, 8, 'L')
692
+ @t.add_result(3, 1, 3, 'W')
693
+ @t.add_result(5, 1, 5, 'L')
694
+ @t.add_result(6, 1, 7, 'W')
695
+ @t.add_result(7, 1, 6, 'W')
696
+ @t.add_result(8, 1, 4, 'W')
697
+ @t.add_result(9, 1, 2, 'L')
698
+
699
+ @t.rate!
700
+ end
701
+
702
+ it "should agree with ICU rating database except for new ratings of foreigners" do
703
+ [
704
+ [1, 4.0, 1.54, 1954], # Magee
705
+ [2, 1.0, 0.76, 2236], # Antal
706
+ [3, 0.0, 0.43, 1436], # Arsic
707
+ [4, 0.0, 0.49, 1436], # Donchenko
708
+ [5, 1.0, 0.49, 2236], # Emdin
709
+ [6, 0.0, 0.64, 1436], # Lushnikov
710
+ [7, 0.0, 0.66, 1436], # Pulpan
711
+ [8, 1.0, 0.58, 2236], # Toma
712
+ ].each do |item|
713
+ num, score, expected_score, performance = item
714
+ p = @t.player(num)
715
+ p.score.should == score
716
+ p.expected_score.should be_close(expected_score, 0.01)
717
+ p.performance.should be_close(performance, 0.5)
718
+ if num == 1
719
+ p.new_rating.should be_close(1836, 0.5)
720
+ p.bonus.should == 71
721
+ else
722
+ p.new_rating.should == p.rating
723
+ end
724
+ end
725
+ end
726
+ end
727
+
728
+ context "#rate - a tournament with a one provisional and one player who got a bonus" do
729
+ # 1 Blake, Austin 3403 3.5 6:D 2:D 3:W 4:W 5:D
730
+ # 2 Fitzpatrick, Kevin 6968 4 5:W 1:D 6:D 3:W 4:W
731
+ # 3 George Rajesh, Nikhil 10411 1 4:W 5:L 1:L 2:L 6:L
732
+ # 4 Mullooly, Sean 6721 0 3:L 6:L 5:L 1:L 2:L
733
+ # 5 Mullooly, Michael 6623 3.5 2:L 3:W 4:W 6:W 1:D
734
+ # 6 O'Dwyer, Eoin 5931 3 1:D 4:W 2:D 5:L 3:W
735
+ before(:all) do
736
+ @t = ICU::RatedTournament.new(:desc => "Drogheda Congress Sec 5 2007")
737
+ @t.add_player(1, :desc => 'Austin Blake (3403)', :rating => 1081, :kfactor => 24)
738
+ @t.add_player(2, :desc => 'Kevin Fitzpatrick (6968)', :rating => 1026, :kfactor => 32)
739
+ @t.add_player(3, :desc => 'Nikhil George Rajesh (10411)', :rating => 603, :games => 5)
740
+ @t.add_player(4, :desc => 'Sean Mullooly (6721)', :rating => 568, :kfactor => 40)
741
+ @t.add_player(5, :desc => 'Michael Mullooly (6623)', :rating => 719, :kfactor => 40)
742
+ @t.add_player(6, :desc => "Eoin O'Dwyer (5931)", :rating => 1032, :kfactor => 40)
743
+
744
+ @t.add_result(1, 1, 6, 'D')
745
+ @t.add_result(1, 2, 5, 'W')
746
+ @t.add_result(1, 3, 4, 'W')
747
+ @t.add_result(2, 6, 4, 'W')
748
+ @t.add_result(2, 5, 3, 'W')
749
+ @t.add_result(2, 1, 2, 'D')
750
+ @t.add_result(3, 6, 2, 'D')
751
+ @t.add_result(3, 3, 1, 'L')
752
+ @t.add_result(3, 4, 5, 'L')
753
+ @t.add_result(4, 5, 6, 'W')
754
+ @t.add_result(4, 1, 4, 'W')
755
+ @t.add_result(4, 2, 3, 'W')
756
+ @t.add_result(5, 3, 6, 'L')
757
+ @t.add_result(5, 4, 2, 'L')
758
+ @t.add_result(5, 5, 1, 'D')
759
+
760
+ @t.rate!
761
+ end
762
+
763
+ it "should agree with ICU rating database" do
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
771
+ ].each do |item|
772
+ num, score, expected_score, performance, new_rating, bonus = item
773
+ p = @t.player(num)
774
+ p.score.should == score
775
+ p.bonus.should == bonus
776
+ p.performance.should be_close(performance, 0.5)
777
+ p.expected_score.should be_close(expected_score, 0.01)
778
+ p.new_rating.should be_close(new_rating, 0.5)
779
+ end
780
+ end
781
+ end
782
+
783
+ context "#rate - a tournament with a mixture of player types that included 2 players who received bonuses" do
784
+ # 1 Fanjimni, Kayode Daniel 12221 4 2:L 3:W 4:- 5:W 6:W 7:W
785
+ # 2 Guinan, Cian 12160 3.5 1:W 3:D 4:W 5:- 6:L 7:W
786
+ # 3 Duffy, Sinead 12185 3.5 1:L 2:D 4:- 5:W 6:W 7:W
787
+ # 4 Cooke, Peter 12169 1 1:- 2:L 3:- 5:L 6:W 7:-
788
+ # 5 Callaghan, Tony 10728 1 1:L 2:- 3:L 4:W 6:- 7:-
789
+ # 6 Montenegro, May Yol 10901 1 1:L 2:W 3:L 4:L 5:- 7:-
790
+ # 7 Lowry-O'Reilly, Johanna 5535 0 1:L 2:L 3:L 4:- 5:- 6:-
791
+ before(:all) do
792
+ @t = ICU::RatedTournament.new(:desc => "Drogheda Club Championship, 2009, Section H")
793
+ @t.add_player(1, :desc => 'Fanjimni, Kayode Daniel', :rating => 1079, :games => 17)
794
+ @t.add_player(2, :desc => 'Guinan, Cian', :rating => 659, :kfactor => 40)
795
+ @t.add_player(3, :desc => 'Duffy, Sinead', :rating => 731, :kfactor => 40)
796
+ @t.add_player(4, :desc => 'Cooke, Peter', :rating => 728, :kfactor => 40)
797
+ @t.add_player(5, :desc => 'Callaghan, Tony', :rating => 894, :games => 5)
798
+ @t.add_player(6, :desc => 'Montenegro, May Yol')
799
+ @t.add_player(7, :desc => "Lowry-O'Reilly, Johanna", :rating => 654, :kfactor => 24)
800
+
801
+ @t.add_result(1, 2, 7, 'W')
802
+ @t.add_result(1, 3, 6, 'W')
803
+ @t.add_result(1, 4, 5, 'L')
804
+ @t.add_result(2, 6, 4, 'L')
805
+ @t.add_result(2, 7, 3, 'L')
806
+ @t.add_result(2, 1, 2, 'L')
807
+ @t.add_result(3, 3, 1, 'L')
808
+ @t.add_result(4, 2, 3, 'D')
809
+ @t.add_result(5, 4, 2, 'L')
810
+ @t.add_result(5, 5, 1, 'L')
811
+ @t.add_result(6, 1, 6, 'W')
812
+ @t.add_result(7, 5, 3, 'L')
813
+ @t.add_result(7, 6, 2, 'W')
814
+ @t.add_result(7, 7, 1, 'L')
815
+
816
+ @t.rate!
817
+
818
+ @m = [
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
827
+ ]
828
+ end
829
+
830
+ it "should agree with ICU rating database" do
831
+ @m.each do |item|
832
+ num, score, expected_score, performance, new_rating, bonus = item
833
+ p = @t.player(num)
834
+ p.score.should == score
835
+ p.bonus.should == bonus
836
+ p.performance.should be_close(performance, num == 2 ? 0.6 : 0.5)
837
+ p.expected_score.should be_close(expected_score, 0.01)
838
+ p.new_rating.should be_close(new_rating, 0.5)
839
+ end
840
+ end
841
+
842
+ it "should give the same results if rated twice" do
843
+ @t.rate!
844
+ @m.each do |item|
845
+ num, score, expected_score, performance, new_rating, bonus = item
846
+ p = @t.player(num)
847
+ p.score.should == score
848
+ p.bonus.should == bonus
849
+ p.performance.should be_close(performance, num == 2 ? 0.6 : 0.5)
850
+ p.expected_score.should be_close(expected_score, 0.01)
851
+ p.new_rating.should be_close(new_rating, 0.5)
852
+ end
853
+ end
854
+
855
+ it "should be completely different if bonuses are turned off" do
856
+ @t.no_bonuses = true
857
+ @t.rate!
858
+ @m.each do |item|
859
+ num, score, expected_score, performance, new_rating, bonus = item
860
+ p = @t.player(num)
861
+ p.score.should == score
862
+ p.bonus.should == 0
863
+ p.performance.should_not be_close(performance, 1.0)
864
+ p.expected_score.should_not be_close(expected_score, 0.01)
865
+ p.new_rating.should_not be_close(new_rating, 1.0)
866
+ end
867
+ end
868
+ end
869
+
617
870
  context "#rate - a made-up tournament that includes a group of unrateable players" do
618
871
  # 1 Orr, Mark 1350 2 2:W 3:W 0:-
619
872
  # 2 Coughlan, Anne 251 1 1:L 0:- 3:W
@@ -646,7 +899,7 @@ module ICU
646
899
  [2, 0.91, 1184],
647
900
  [3, 0.10, 792],
648
901
  ].each do |item|
649
- num, expected_score, new_rating, performance = item
902
+ num, expected_score, new_rating = item
650
903
  p = @t.player(num)
651
904
  p.expected_score.should be_close(expected_score, 0.01)
652
905
  p.new_rating.should be_close(new_rating, 0.5)
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: 0.3.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Orr
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-17 00:00:00 +00:00
12
+ date: 2010-02-14 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies: []
15
15