icu_ratings 0.3.1 → 1.0.0
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.
- 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
|
|