icu_ratings 0.2.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.
@@ -0,0 +1,618 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module ICU
4
+ describe RatedTournament do
5
+ context "restrictions, or lack thereof, on attributes" do
6
+ it "a tournament can have an optional description, such as a name, but any object is allowed" do
7
+ ICU::RatedTournament.new(:desc => 'Irish Championship 2010').desc.should == 'Irish Championship 2010'
8
+ ICU::RatedTournament.new(:desc => 1.0).desc.should be_an_instance_of(Float)
9
+ ICU::RatedTournament.new.desc.should be_nil
10
+ end
11
+ end
12
+
13
+ context "#players and #player" do
14
+ before(:each) do
15
+ @t = ICU::RatedTournament.new
16
+ @t.add_player(2)
17
+ @t.add_player(1)
18
+ end
19
+
20
+ it "should return the players in number-order" do
21
+ @t.players.size.should == 2
22
+ @t.players[0].num.should == 1
23
+ @t.players[1].num.should == 2
24
+ end
25
+
26
+ it "should return the player object with the matching number" do
27
+ @t.player(1).num.should == 1
28
+ @t.player(2).num.should == 2
29
+ @t.player(3).should be_nil
30
+ end
31
+ end
32
+
33
+ context "#add_result" do
34
+ before(:each) do
35
+ @t = ICU::RatedTournament.new
36
+ @p1 = @t.add_player(1)
37
+ @p2 = @t.add_player(2)
38
+ @p3 = @t.add_player(3)
39
+ end
40
+
41
+ it "should be added to both players" do
42
+ @t.add_result(1, @p1, @p2, 'L')
43
+ @p1.results.size.should == 1
44
+ @p1.results[0].should == ICU::RatedResult.new(1, @p2, 'L')
45
+ @p2.results.size.should == 1
46
+ @p2.results[0].should == ICU::RatedResult.new(1, @p1, 'W')
47
+ @p3.results.size.should == 0
48
+ @t.add_result(2, @p3, @p2, 'W')
49
+ @p1.results.size.should == 1
50
+ @p2.results.size.should == 2
51
+ @p2.results[0].should == ICU::RatedResult.new(1, @p1, 'W')
52
+ @p2.results[1].should == ICU::RatedResult.new(2, @p3, 'L')
53
+ @p3.results.size.should == 1
54
+ @p3.results[0].should == ICU::RatedResult.new(2, @p2, 'W')
55
+ end
56
+
57
+ it "player objects or numbers can be used" do
58
+ lambda { @t.add_result(1, @p1, @p2, 'L') }.should_not raise_error
59
+ lambda { @t.add_result(2, 1, 2, 'W') }.should_not raise_error
60
+ end
61
+
62
+ it "the player numbers should exist already in the tournament" do
63
+ lambda { @t.add_result(2, 1, 4, 'W') }.should raise_error(/player number.*4/)
64
+ lambda { @t.add_result(2, 5, 2, 'W') }.should raise_error(/player number.*5/)
65
+ end
66
+
67
+ it "adding precisely the same result more than once is okay and changes nothing" do
68
+ @t.add_result(1, @p1, @p2, 'L')
69
+ @t.add_result(1, @p1, @p2, 'L')
70
+ @t.add_result(1, @p2, @p1, 'W')
71
+ @p1.results.size.should == 1
72
+ @p2.results.size.should == 1
73
+ end
74
+
75
+ it "a player cannot have two different results in the same round" do
76
+ @t.add_result(1, @p1, @p2, 'L')
77
+ lambda { @t.add_result(1, @p1, @p2, 'W') }.should raise_error(/inconsistent/)
78
+ lambda { @t.add_result(1, @p1, @p3, 'W') }.should raise_error(/inconsistent/)
79
+ lambda { @t.add_result(1, @p3, @p2, 'W') }.should raise_error(/inconsistent/)
80
+ end
81
+ end
82
+
83
+ context "#rate - corner case - tournament is empy" do
84
+ it "should not throw an exception" do
85
+ @t = ICU::RatedTournament.new
86
+ lambda { @t.rate! }.should_not raise_error
87
+ end
88
+ end
89
+
90
+ context "#rate - all players rated, artificial example" do
91
+ before(:each) do
92
+ @t = ICU::RatedTournament.new
93
+ (1..4).each do |num|
94
+ @t.add_player(num, :kfactor => 10 * num, :rating => 2200 - 100 * (num - 1))
95
+ end
96
+ @t.add_result(1, 1, 2, 'W')
97
+ @t.add_result(1, 3, 4, 'W')
98
+ @t.add_result(2, 1, 3, 'W')
99
+ @t.add_result(2, 2, 4, 'W')
100
+ @t.add_result(3, 1, 4, 'W')
101
+ @t.add_result(3, 2, 3, 'W')
102
+ end
103
+
104
+ it "before the tournament is rated" do
105
+ (1..4).each do |num|
106
+ p = @t.player(num)
107
+ p.expected_score.should == 0.0
108
+ p.rating_change.should == 0.0
109
+ p.new_rating.should == p.rating
110
+ end
111
+ end
112
+
113
+ it "after the tournament is rated" do
114
+ @t.rate!
115
+
116
+ @t.player(1).expected_score.should be_close(2.249, 0.001)
117
+ @t.player(2).expected_score.should be_close(1.760, 0.001)
118
+ @t.player(3).expected_score.should be_close(1.240, 0.001)
119
+ @t.player(4).expected_score.should be_close(0.751, 0.001)
120
+
121
+ @t.player(1).rating_change.should be_close(7.51, 0.01)
122
+ @t.player(2).rating_change.should be_close(4.81, 0.01)
123
+ @t.player(3).rating_change.should be_close(-7.21, 0.01)
124
+ @t.player(4).rating_change.should be_close(-30.05, 0.01)
125
+
126
+ @t.player(1).new_rating.should be_close(2207.5, 0.1)
127
+ @t.player(2).new_rating.should be_close(2104.8, 0.1)
128
+ @t.player(3).new_rating.should be_close(1992.8, 0.1)
129
+ @t.player(4).new_rating.should be_close(1870.0, 0.1)
130
+ end
131
+ end
132
+
133
+ context "#rate - all players rated, real swiss example" do
134
+ # 1 Killane, Jack 740 4 2:D 3:W 4:W 5:D 6:W
135
+ # 2 Cassidy, Paul 194 4 1:D 3:D 4:W 5:W 6:W
136
+ # 3 King, Melvyn 10214 3.5 1:L 2:D 4:W 5:W 6:W
137
+ # 4 Dempsey, Denis 322 1.5 1:L 2:L 3:L 5:D 6:W
138
+ # 5 Thomson, Andrew 1613 1 1:D 2:L 3:L 4:D 6:L
139
+ # 6 Coveney, Maurice 255 1 1:L 2:L 3:L 4:L 5:W
140
+ # see also http://en.wikipedia.org/wiki/Round-robin_tournament#Scheduling_algorithm
141
+ before(:each) do
142
+ @t = ICU::RatedTournament.new(:desc => 'Irish Veterans 2008')
143
+
144
+ @t.add_player(1, :kfactor => 24, :rating => 1852, :desc => 'Jack Killane (740)')
145
+ @t.add_player(2, :kfactor => 24, :rating => 1845, :desc => 'Paul Cassidy (194)')
146
+ @t.add_player(3, :kfactor => 32, :rating => 1632, :desc => 'Melvin King (10214)')
147
+ @t.add_player(4, :kfactor => 24, :rating => 1337, :desc => 'Dennis Dempsey (322)')
148
+ @t.add_player(5, :kfactor => 24, :rating => 1493, :desc => 'Andrew Thomson (1613)')
149
+ @t.add_player(6, :kfactor => 24, :rating => 1297, :desc => 'Maurice J. Coveney (255)')
150
+
151
+ @t.add_result(1, 1, 2, 'D')
152
+ @t.add_result(2, 1, 3, 'W')
153
+ @t.add_result(3, 1, 4, 'W')
154
+ @t.add_result(4, 1, 5, 'D')
155
+ @t.add_result(5, 1, 6, 'W')
156
+
157
+ @t.add_result(4, 2, 3, 'D')
158
+ @t.add_result(2, 2, 4, 'W')
159
+ @t.add_result(5, 2, 5, 'W')
160
+ @t.add_result(3, 2, 6, 'W')
161
+
162
+ @t.add_result(5, 3, 4, 'W')
163
+ @t.add_result(3, 3, 5, 'W')
164
+ @t.add_result(1, 3, 6, 'W')
165
+
166
+ @t.add_result(1, 4, 5, 'D')
167
+ @t.add_result(4, 4, 6, 'W')
168
+
169
+ @t.add_result(2, 5, 6, 'L')
170
+
171
+ @t.rate!
172
+ end
173
+
174
+ it "should get same results as ICU rating database" do
175
+ [
176
+ [1, 4.089, 1850],
177
+ [2, 4.055, 1844],
178
+ [3, 2.855, 1653],
179
+ [4, 1.101, 1347],
180
+ [5, 2.005, 1469],
181
+ [6, 0.894, 1300],
182
+ ].each do |item|
183
+ num, expected_score, new_rating = item
184
+ p = @t.player(num)
185
+ p.expected_score.should be_close(expected_score, 0.001)
186
+ p.new_rating.should be_close(new_rating, 0.5)
187
+ end
188
+ end
189
+
190
+ it "tournament performances are not the same as ICU year-to-date performances" do
191
+ [
192
+ [1, 1867, 1761],
193
+ [2, 1803, 1762],
194
+ [3, 1650, 1725],
195
+ [4, 1338, 1464],
196
+ [5, 1358, 1353],
197
+ [6, 1289, 1392],
198
+ ].each do |item|
199
+ num, ytd_performance, tournament_performance = item
200
+ p = @t.player(num)
201
+ p.performance.should_not be_close(ytd_performance, 0.5)
202
+ p.performance.should be_close(tournament_performance, 0.5)
203
+ end
204
+ end
205
+ end
206
+
207
+ context "#rate - all players rated, real match example" do
208
+ before(:each) do
209
+ @t = ICU::RatedTournament.new(:desc => "Dempsey-O'Raghallaigh Match 2009")
210
+ @t.add_player(1, :kfactor => 24, :rating => 1347, :desc => 'Dennis Dempsey (322)')
211
+ @t.add_player(2, :kfactor => 32, :rating => 1303, :desc => "Paul O'Raghallaigh (10219)")
212
+ @t.add_result(1, 1, 2, 'W')
213
+ @t.add_result(2, 1, 2, 'W')
214
+ @t.add_result(3, 1, 2, 'W')
215
+ @t.rate!
216
+ end
217
+
218
+ it "should get same results as ICU rating database" do
219
+ @t.player(1).expected_score.should be_close(1.689, 0.001)
220
+ @t.player(2).expected_score.should be_close(1.311, 0.001)
221
+ @t.player(1).new_rating.should be_close(1378, 0.5)
222
+ @t.player(2).new_rating.should be_close(1261, 0.5)
223
+ end
224
+ end
225
+
226
+ context "#rate - typical foreign tournament" do
227
+ # Player,456,Fox,Anthony
228
+ # 1,0,B,Berg,Antonin,1877,,CZE
229
+ # 2,0,W,Zanikov,Konstantin,1972,,CZE
230
+ # 3,=,B,Pozzi,Claudio,1931,,ITA
231
+ # 4,=,W,Horak,Karel,2105,,CZE
232
+ # 5,=,B,Chromovsky,Hynek,1808,,CZE
233
+ # 6,1,W,Hulin,Petr,1975,,CZE
234
+ # 7,=,B,Jancarik,Joel,1990,,CZE
235
+ # 8,1,W,Andersen,Carsten,1982,,DEN
236
+ # 9,=,B,Gooding,Ian,2107,,ENG
237
+ #
238
+ # Player,159,Cafolla,Peter
239
+ # 1,0,W,Dolezal,Radoslav,2423,IM,CZE
240
+ # 2,1,B,Pozzi,Claudio,1931,,ITA
241
+ # 3,0,W,Srirhanzl,Radek,1621,,CZE
242
+ # 4,1,B,Milfort,Vaclav,1779,,CZE
243
+ # 5,0,W,Srba,Milan,2209,,CZE
244
+ # 6,1,B,Hajek,Jaroslav,1967,,CZE
245
+ # 7,=,B,Soucek,Milan,2151,,CZE
246
+ # 8,=,W,Babar,Michael,2145,FM,GER
247
+ # 9,0,B,Bartos,Jan,2227,FM,CZE
248
+ before(:all) do
249
+ @t = ICU::RatedTournament.new(:desc => 'Prague Open 2008')
250
+
251
+ @t.add_player(1, :rating => 2105, :kfactor => 16, :desc => 'Fox, Anthony (456)')
252
+ @t.add_player(2, :rating => 1976, :kfactor => 24, :desc => 'Cafolla, Peter (159)')
253
+ @t.add_player(3, :rating => 1877, :desc => 'Berg, Antonin')
254
+ @t.add_player(4, :rating => 1972, :desc => 'Zanikov, Konstantin')
255
+ @t.add_player(5, :rating => 1931, :desc => 'Pozzi, Claudio')
256
+ @t.add_player(6, :rating => 2105, :desc => 'Horak, Karel')
257
+ @t.add_player(7, :rating => 1808, :desc => 'Chromovsky, Hynek')
258
+ @t.add_player(8, :rating => 1975, :desc => 'Hulin, Petr')
259
+ @t.add_player(9, :rating => 1990, :desc => 'Jancarik, Joel')
260
+ @t.add_player(10, :rating => 1982, :desc => 'Andersen, Carsten')
261
+ @t.add_player(11, :rating => 2107, :desc => 'Gooding, Ian')
262
+ @t.add_player(12, :rating => 2423, :desc => 'Dolezal, Radoslav')
263
+ @t.add_player(14, :rating => 1621, :desc => 'Srirhanzl, Radek')
264
+ @t.add_player(15, :rating => 1779, :desc => 'Milfort, Vaclav')
265
+ @t.add_player(16, :rating => 2209, :desc => 'Srba, Milan')
266
+ @t.add_player(17, :rating => 1967, :desc => 'Hajek, Jaroslav')
267
+ @t.add_player(18, :rating => 2151, :desc => 'Soucek, Milan')
268
+ @t.add_player(19, :rating => 2187, :desc => 'Babar, Michael')
269
+ @t.add_player(20, :rating => 2227, :desc => 'Bartos, Jan')
270
+
271
+ @t.add_result(1, 1, 3, 'L')
272
+ @t.add_result(2, 1, 4, 'L')
273
+ @t.add_result(3, 1, 5, 'D')
274
+ @t.add_result(4, 1, 6, 'D')
275
+ @t.add_result(5, 1, 7, 'D')
276
+ @t.add_result(6, 1, 8, 'W')
277
+ @t.add_result(7, 1, 9, 'D')
278
+ @t.add_result(8, 1, 10, 'W')
279
+ @t.add_result(9, 1, 11, 'D')
280
+
281
+ @t.add_result(1, 2, 12, 'L')
282
+ @t.add_result(2, 2, 5, 'W')
283
+ @t.add_result(3, 2, 14, 'L')
284
+ @t.add_result(4, 2, 15, 'W')
285
+ @t.add_result(5, 2, 16, 'L')
286
+ @t.add_result(6, 2, 17, 'W')
287
+ @t.add_result(7, 2, 18, 'D')
288
+ @t.add_result(8, 2, 19, 'D')
289
+ @t.add_result(9, 2, 20, 'L')
290
+
291
+ @t.rate!
292
+ end
293
+
294
+ it "foreign players should have no rating change but non-zero expected scores" do
295
+ (3..20).each do |num|
296
+ unless num == 13
297
+ p = @t.player(num)
298
+ p.expected_score.should_not == 0.0
299
+ p.rating_change.should == 0.0
300
+ p.new_rating.should == p.rating
301
+ end
302
+ end
303
+ end
304
+
305
+ it "foreign players should have tournament performnce ratings" do
306
+ [
307
+ [3, 2505.0],
308
+ [4, 2505.0],
309
+ [5, 1840.5],
310
+ [6, 2105.0],
311
+ [7, 2105.0],
312
+ [8, 1705.0],
313
+ [9, 2105.0],
314
+ [10, 1705.0],
315
+ [11, 2105.0],
316
+ [12, 2376.0],
317
+ [14, 2376.0],
318
+ [15, 1576.0],
319
+ [16, 2376.0],
320
+ [17, 1576.0],
321
+ [18, 1976.0],
322
+ [19, 1976.0],
323
+ [20, 2376.0],
324
+ ].each do |item|
325
+ num, performance = item
326
+ p = @t.player(num)
327
+ p.performance.should == performance
328
+ end
329
+ end
330
+
331
+ it "should get the same results as the ICU database for the Irish players" do
332
+ af = @t.player(1)
333
+ pc = @t.player(2)
334
+
335
+ af.score.should == 4.5
336
+ af.expected_score.should be_close(6.054, 0.001)
337
+ af.new_rating.should be_close(2080, 0.5)
338
+
339
+ pc.score.should == 4.0
340
+ pc.expected_score.should be_close(3.685, 0.001)
341
+ pc.new_rating.should be_close(1984, 0.5)
342
+ end
343
+ end
344
+
345
+ context "#rate - a tournament with some provisionally rated players and some players without results" do
346
+ before(:all) do
347
+ @t = ICU::RatedTournament.new(:desc => "Malahide CC's 2007")
348
+ @t.add_player(1, :rating => 1751, :kfactor => 32, :desc => 'DALY, JUSTIN')
349
+ @t.add_player(2, :rating => 1716, :kfactor => 32, :desc => 'REILLY, PAUL')
350
+ @t.add_player(3, :rating => 1583, :kfactor => 24, :desc => "O'SULLIVAN, TOM")
351
+ @t.add_player(4, :rating => 1659, :kfactor => 24, :desc => 'McGRANE, KEVIN')
352
+ @t.add_player(5, :rating => 1647, :kfactor => 24, :desc => 'BISSETT, VINCENT')
353
+ @t.add_player(6, :rating => 1442, :kfactor => 24, :desc => 'ROCHE, CORMAC')
354
+ @t.add_player(7, :rating => 1665, :kfactor => 24, :desc => 'BUCKLEY, GERARD')
355
+ @t.add_player(8, :rating => 1601, :kfactor => 24, :desc => 'WHALLEY, ANTHONY')
356
+ @t.add_player(9, :rating => 1283, :kfactor => 24, :desc => 'SCOTT, SHAY')
357
+ @t.add_player(10, :rating => 1469, :kfactor => 24, :desc => "O'CONGHAILE, DIARMUID")
358
+ @t.add_player(11, :rating => 1376, :kfactor => 24, :desc => 'SHEARAN, JOHN')
359
+ @t.add_player(12, :rating => 1096, :games => 11, :desc => 'MOLDT, TOBIAS')
360
+ @t.add_player(13, :rating => 1317, :kfactor => 24, :desc => 'MARTIN, SEAMUS')
361
+ @t.add_player(14, :rating => 1613, :kfactor => 24, :desc => 'FREDJ, NIZAR')
362
+ @t.add_player(15, :rating => 1554, :kfactor => 24, :desc => 'REID, BRIAN')
363
+ @t.add_player(16, :rating => 1016, :games => 7, :desc => 'DONNELLY, VINCENT')
364
+
365
+ # 1 DALY, JUSTIN 4.5 10:W 14:W 5:D 7:D 2:W 4:D
366
+ @t.add_result(1, 1, 10, 'W')
367
+ @t.add_result(2, 1, 14, 'W')
368
+ @t.add_result(3, 1, 5, 'D')
369
+ @t.add_result(4, 1, 7, 'D')
370
+ @t.add_result(5, 1, 2, 'W')
371
+ @t.add_result(6, 1, 4, 'D')
372
+
373
+ # 2 REILLY, PAUL 4.5 13:W 7:D 3:W 5:W 1:L 8:W
374
+ @t.add_result(1, 2, 13, 'W')
375
+ @t.add_result(2, 2, 7, 'D')
376
+ @t.add_result(3, 2, 3, 'W')
377
+ @t.add_result(4, 2, 5, 'W')
378
+ @t.add_result(5, 2, 1, 'L')
379
+ @t.add_result(6, 2, 8, 'W')
380
+
381
+ # 3 O'SULLIVAN, TOM 4 16:+ 4:- 2:L 9:W 12:W 5:W
382
+ @t.add_result(3, 3, 2, 'L')
383
+ @t.add_result(4, 3, 9, 'W')
384
+ @t.add_result(5, 3, 12, 'W')
385
+ @t.add_result(6, 3, 5, 'W')
386
+
387
+ # 4 McGRANE, KEVIN 4 11:W 3:- 7:D 12:W 8:W 1:D
388
+ @t.add_result(1, 4, 11, 'W')
389
+ @t.add_result(3, 4, 7, 'D')
390
+ @t.add_result(4, 4, 12, 'W')
391
+ @t.add_result(5, 4, 8, 'W')
392
+ @t.add_result(6, 4, 1, 'D')
393
+
394
+ # 5 BISSETT, VINCENT 3.5 17:+ 8:W 1:D 2:L 7:W 3:L
395
+ @t.add_result(2, 5, 8, 'W')
396
+ @t.add_result(3, 5, 1, 'D')
397
+ @t.add_result(4, 5, 2, 'L')
398
+ @t.add_result(5, 5, 7, 'W')
399
+ @t.add_result(6, 5, 3, 'L')
400
+
401
+ # 6 ROCHE, CORMAC 3.5 7:L 13:W 8:L 10:W 9:D 12:W
402
+ @t.add_result(1, 6, 7, 'L')
403
+ @t.add_result(2, 6, 13, 'W')
404
+ @t.add_result(3, 6, 8, 'L')
405
+ @t.add_result(4, 6, 10, 'W')
406
+ @t.add_result(5, 6, 9, 'D')
407
+ @t.add_result(6, 6, 12, 'W')
408
+
409
+ # 7 BUCKLEY, GERARD 3 6:W 2:D 4:D 1:D 5:L 11:D
410
+ @t.add_result(1, 7, 6, 'W')
411
+ @t.add_result(2, 7, 2, 'D')
412
+ @t.add_result(3, 7, 4, 'D')
413
+ @t.add_result(4, 7, 1, 'D')
414
+ @t.add_result(5, 7, 5, 'L')
415
+ @t.add_result(6, 7, 11, 'D')
416
+
417
+ # 8 WHALLEY, ANTHONY 3 12:W 5:L 6:W 14:+ 4:L 2:L
418
+ @t.add_result(1, 8, 12, 'W')
419
+ @t.add_result(2, 8, 5, 'L')
420
+ @t.add_result(3, 8, 6, 'W')
421
+ @t.add_result(5, 8, 4, 'L')
422
+ @t.add_result(6, 8, 2, 'L')
423
+
424
+ # 9 SCOTT, SHAY 3 14:L 11:D 13:W 3:L 6:D 0:W
425
+ @t.add_result(1, 9, 14, 'L')
426
+ @t.add_result(2, 9, 11, 'D')
427
+ @t.add_result(3, 9, 13, 'W')
428
+ @t.add_result(4, 9, 3, 'L')
429
+ @t.add_result(5, 9, 6, 'D')
430
+
431
+ # 10 O'CONGHAILE, DIARMUID 2.5 1:L 16:W 14:- 6:L 11:D 13:W
432
+ @t.add_result(1, 10, 1, 'L')
433
+ @t.add_result(2, 10, 16, 'W')
434
+ @t.add_result(4, 10, 6, 'L')
435
+ @t.add_result(5, 10, 11, 'D')
436
+ @t.add_result(6, 10, 13, 'W')
437
+
438
+ # 11 SHEARAN, JOHN 2.5 4:L 9:D 12:L 13:W 10:D 7:D
439
+ @t.add_result(1, 11, 4, 'L')
440
+ @t.add_result(2, 11, 9, 'D')
441
+ @t.add_result(3, 11, 12, 'L')
442
+ @t.add_result(4, 11, 13, 'W')
443
+ @t.add_result(5, 11, 10, 'D')
444
+ @t.add_result(6, 11, 7, 'D')
445
+
446
+ # 12 MOLDT, TOBIAS 2 8:L 17:+ 11:W 4:L 3:L 6:L
447
+ @t.add_result(1, 12, 8, 'L')
448
+ @t.add_result(3, 12, 11, 'W')
449
+ @t.add_result(4, 12, 4, 'L')
450
+ @t.add_result(5, 12, 3, 'L')
451
+ @t.add_result(6, 12, 6, 'L')
452
+
453
+ # 13 MARTIN, SEAMUS 1 2:L 6:L 9:L 11:L 0:W 10:L
454
+ @t.add_result(1, 13, 2, 'L')
455
+ @t.add_result(2, 13, 6, 'L')
456
+ @t.add_result(3, 13, 9, 'L')
457
+ @t.add_result(4, 13, 11, 'L')
458
+ @t.add_result(6, 13, 10, 'L')
459
+
460
+ # 14 FREDJ, NIZAR 1 9:W 1:L 10:- 8:- 0: 0:
461
+ @t.add_result(1, 14, 9, 'W')
462
+ @t.add_result(2, 14, 1, 'L')
463
+
464
+ # 15 REID, BRIAN .5 0:D 0: 0: 0: 0: 0:
465
+
466
+ # 16 DONNELLY, VINCENT 0 3:- 10:L 0: 0: 0: 0:
467
+ @t.add_result(2, 16, 10, 'L')
468
+
469
+ @t.rate!
470
+ end
471
+
472
+ it "should agree with ICU database for rated players with results" do
473
+ [
474
+ [1, 3.97, 1768],
475
+ [2, 3.87, 1736],
476
+ [3, 2.50, 1595],
477
+ [4, 3.23, 1678],
478
+ [5, 2.39, 1650],
479
+ [6, 3.19, 1449],
480
+ [7, 3.46, 1654],
481
+ [8, 2.83, 1581],
482
+ [9, 1.39, 1298],
483
+ [10, 2.97, 1458],
484
+ [11, 2.69, 1372],
485
+ [13, 1.68, 1277],
486
+ [14, 1.18, 1609],
487
+ ].each do |item|
488
+ num, expected_score, new_rating = item
489
+ p = @t.player(num)
490
+ p.expected_score.should be_close(expected_score, 0.01)
491
+ p.new_rating.should be_close(new_rating, 0.5)
492
+ p.results.inject(p.rating){ |t,r| t + r.rating_change }.should be_close(new_rating, 0.5)
493
+ end
494
+ end
495
+
496
+ it "should agree with ICU database for provisionally rated players with results" do
497
+ [
498
+ [12, 0.59, 1157],
499
+ [16, 0.07, 1023],
500
+ ].each do |item|
501
+ num, expected_score, new_rating = item
502
+ p = @t.player(num)
503
+ p.expected_score.should be_close(expected_score, 0.01)
504
+ p.new_rating.should be_close(new_rating, 0.5)
505
+ end
506
+ end
507
+
508
+ it "players who didn't play any rated games should not change their rating" do
509
+ p = @t.player(15)
510
+ p.expected_score.should == 0
511
+ p.new_rating.should == p.rating
512
+ end
513
+ end
514
+
515
+ context "#rate - a made-up tournament with players of all kinds" do
516
+ # 1 Orr, Mark 1350 3 8:W 3:W 2:W
517
+ # 2 Coughlan, Anne 251 2 7:W 4:W 1:L
518
+ # 3 Kennedy, Cian 6607 2 6:W 1:L 4:W
519
+ # 4 Ledwidge O'Brien, Aoife 10744 1 5:W 2:L 3:L
520
+ # 5 Martin, Ryan 6988 2 4:L 7:W 6:W
521
+ # 6 Hanley, Gerard 5701 1 3:L 8:W 5:L
522
+ # 7 Fagan, Joseph 5502 1 2:L 5:L 8:W
523
+ # 8 Hassett, James 10185 0 1:L 6:L 7:L
524
+ # Used in an experiment to prove the rating database is insensitive to K-factors for performance ratings.
525
+ # See the files in the SVN repository ICU/data/etc/experiments/test.txt.
526
+ before(:all) do
527
+ @t = ICU::RatedTournament.new(:desc => "K Factor Test")
528
+ @t.add_player(1, :desc => 'Orr, Mark', :rating => 2174, :kfactor => 16) # rated
529
+ @t.add_player(2, :desc => 'Coughlan, Anne', :rating => 1167, :kfactor => 24) # rated
530
+ @t.add_player(3, :desc => 'Kennedy, Cian', :rating => 751, :games => 19) # provisional, almost rated
531
+ @t.add_player(4, :desc => "Ledwidge O'Brien, Aoife", :rating => 1273, :games => 19) # provisional, almost rated
532
+ @t.add_player(5, :desc => 'Martin, Ryan', :rating => 627, :games => 10) # provisional
533
+ @t.add_player(6, :desc => 'Hanley, Gerard', :rating => 684, :games => 10) # provisional
534
+ @t.add_player(7, :desc => 'Fagan, Joseph') # unrated
535
+ @t.add_player(8, :desc => 'Hassett, James') # unrated
536
+ @t.add_result(1, 1, 8, 'W')
537
+ @t.add_result(1, 2, 7, 'W')
538
+ @t.add_result(1, 3, 6, 'W')
539
+ @t.add_result(1, 4, 5, 'W')
540
+ @t.add_result(2, 1, 3, 'W')
541
+ @t.add_result(2, 2, 4, 'W')
542
+ @t.add_result(2, 5, 7, 'W')
543
+ @t.add_result(2, 6, 8, 'W')
544
+ @t.add_result(3, 1, 2, 'W')
545
+ @t.add_result(3, 3, 4, 'W')
546
+ @t.add_result(3, 5, 6, 'W')
547
+ @t.add_result(3, 7, 8, 'W')
548
+ @t.rate!
549
+ end
550
+
551
+ it "should agree with ICU rating database" do
552
+ [
553
+ [1, 3.00, 2174],
554
+ [2, 1.36, 1182],
555
+ [3, 0.85, 851],
556
+ [4, 2.38, 1206],
557
+ [5, 1.05, 717],
558
+ [6, 1.04, 678],
559
+ [7, 1.09, 763],
560
+ [8, 1.24, 805],
561
+ ].each do |item|
562
+ num, expected_score, new_rating = item
563
+ p = @t.player(num)
564
+ p.expected_score.should be_close(expected_score, 0.01)
565
+ p.new_rating.should be_close(new_rating, 0.5)
566
+ end
567
+ end
568
+ end
569
+
570
+ context "#rate - a made-up tournament that includes a group of unrateable players" do
571
+ # 1 Orr, Mark 1350 2 2:W 3:W 0:-
572
+ # 2 Coughlan, Anne 251 1 1:L 0:- 3:W
573
+ # 3 Martin, Ryan 6988 0 0:- 1:L 2:L
574
+ # 4 Hanley, Gerard 5701 2 5:W 6:W 0:-
575
+ # 7 Meeny, Kevin 5507 1 4:L 0:- 6:W
576
+ # 8 Prior, Alo 12314 0 0:- 4:L 5:L
577
+ # Used in an experiment to prove the rating database skips unrateable games.
578
+ # See the README and test3.txt in the SVN repository ICU/data/etc/experiments.
579
+ before(:all) do
580
+ @t = ICU::RatedTournament.new(:desc => "Unrateable Test 3")
581
+ @t.add_player(1, :desc => 'Orr, Mark', :rating => 2174, :kfactor => 16) # rated
582
+ @t.add_player(2, :desc => 'Coughlan, Anne', :rating => 1182, :kfactor => 24) # rated
583
+ @t.add_player(3, :desc => 'Martin, Ryan', :rating => 717, :games => 13) # provisional
584
+ @t.add_player(4, :desc => 'Hanley, Gerard', :rating => 678, :games => 13) # provisional
585
+ @t.add_player(5, :desc => 'Meeny, Kevin') # unrated
586
+ @t.add_player(6, :desc => 'Prior, Alo') # unrated
587
+ @t.add_result(1, 1, 2, 'W')
588
+ @t.add_result(1, 4, 5, 'W')
589
+ @t.add_result(2, 1, 3, 'W')
590
+ @t.add_result(2, 4, 6, 'W')
591
+ @t.add_result(3, 2, 3, 'W')
592
+ @t.add_result(3, 5, 6, 'W')
593
+ @t.rate!
594
+ end
595
+
596
+ it "should agree with ICU rating database for rateable players" do
597
+ [
598
+ [1, 2.00, 2174],
599
+ [2, 0.91, 1184],
600
+ [3, 0.10, 792],
601
+ ].each do |item|
602
+ num, expected_score, new_rating, performance = item
603
+ p = @t.player(num)
604
+ p.expected_score.should be_close(expected_score, 0.01)
605
+ p.new_rating.should be_close(new_rating, 0.5)
606
+ end
607
+ end
608
+
609
+ it "should not rate players that have no rateable games" do
610
+ [4, 5, 6].each do |num|
611
+ p = @t.player(num)
612
+ p.expected_score.should == 0.0
613
+ p.new_rating.should be_nil
614
+ end
615
+ end
616
+ end
617
+ end
618
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: icu_ratings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Mark Orr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-03 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Build an object that represents a chess tournament then get it to calculate ratings of all the players.
17
+ email: mark.j.l.orr@googlemail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .autotest
26
+ - .gitignore
27
+ - LICENCE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION.yml
31
+ - lib/icu_ratings.rb
32
+ - lib/icu_ratings/player.rb
33
+ - lib/icu_ratings/result.rb
34
+ - lib/icu_ratings/tournament.rb
35
+ - spec/player_spec.rb
36
+ - spec/result_spec.rb
37
+ - spec/spec_helper.rb
38
+ - spec/tournament_spec.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/sanichi/icu_ratings
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset
46
+ - UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: For rating chess tournaments.
68
+ test_files:
69
+ - spec/player_spec.rb
70
+ - spec/result_spec.rb
71
+ - spec/spec_helper.rb
72
+ - spec/tournament_spec.rb