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 +3 -3
- data/lib/icu_ratings/player.rb +61 -25
- data/lib/icu_ratings/result.rb +3 -3
- data/lib/icu_ratings/tournament.rb +40 -13
- data/spec/tournament_spec.rb +254 -1
- metadata +2 -2
data/VERSION.yml
CHANGED
data/lib/icu_ratings/player.rb
CHANGED
@@ -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
|
-
|
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.
|
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.
|
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
|
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.
|
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
|
253
|
-
|
254
|
-
|
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)
|
data/lib/icu_ratings/result.rb
CHANGED
@@ -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
|
129
|
-
opponent_rating = opponent.full_rating? ? opponent.
|
130
|
-
if
|
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
|
9
|
+
ICU::RatedTournament objects are created directly.
|
10
10
|
|
11
11
|
t = ICU::RatedTournament.new
|
12
12
|
|
13
|
-
They have
|
14
|
-
|
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
|
-
|
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
|
58
|
-
|
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
|
-
|
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
|
-
|
122
|
-
|
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 <
|
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"
|
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
|
data/spec/tournament_spec.rb
CHANGED
@@ -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
|
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.
|
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-
|
12
|
+
date: 2010-02-14 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|