icu_ratings 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/icu_ratings/player.rb +152 -156
- data/lib/icu_ratings/result.rb +88 -92
- data/lib/icu_ratings/tournament.rb +68 -72
- data/lib/icu_ratings/util.rb +18 -27
- data/lib/icu_ratings/version.rb +3 -1
- data/spec/tournament_spec.rb +50 -50
- data/spec/util_spec.rb +3 -3
- metadata +6 -6
data/lib/icu_ratings/player.rb
CHANGED
@@ -1,162 +1,158 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module ICU
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
p
|
90
|
-
p.
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
*
|
109
|
-
*
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
method.
|
157
|
-
|
158
|
-
=end
|
159
|
-
|
4
|
+
#
|
5
|
+
# == Adding Players to Tournaments
|
6
|
+
#
|
7
|
+
# You don't directly create players, rather you add them to tournaments with the _add_player_ method.
|
8
|
+
#
|
9
|
+
# t = ICU::RatedTournament.new
|
10
|
+
# t.add_player(1)
|
11
|
+
#
|
12
|
+
# There is only one mandatory parameter - the player number - which can be any integer value
|
13
|
+
# except player numbers must be unique in each tournament:
|
14
|
+
#
|
15
|
+
# t.add_player(2) # fine
|
16
|
+
# t.add_player(2) # attempt to add a second player with the same number - exception!
|
17
|
+
#
|
18
|
+
# == Retrieving Players from Tournaments
|
19
|
+
#
|
20
|
+
# Player objects can be retrieved from ICU::RatedTournament objects with the latter's _player_ method
|
21
|
+
# in conjunction with the appropriate player number:
|
22
|
+
#
|
23
|
+
# p = t.player(2)
|
24
|
+
# p.num # 2
|
25
|
+
#
|
26
|
+
# Or the player object can be saved from the return value from _add_player_:
|
27
|
+
#
|
28
|
+
# p = t.add_player(-2)
|
29
|
+
# p.num # -2
|
30
|
+
#
|
31
|
+
# If the number supplied to _player_ is an invalid player number, the method returns _nil_.
|
32
|
+
#
|
33
|
+
# Different types of players are signalled by different combinations of the three optional
|
34
|
+
# parameters: _rating_, _kfactor_ and _games_.
|
35
|
+
#
|
36
|
+
# == Full Ratings
|
37
|
+
#
|
38
|
+
# Rated players have a full rating and a K-factor and are added by including valid values for those two parameters:
|
39
|
+
#
|
40
|
+
# p = t.add_player(3, :rating => 2000, :kfactor => 16)
|
41
|
+
# p.type # :rated
|
42
|
+
#
|
43
|
+
# == Provisional Ratings
|
44
|
+
#
|
45
|
+
# Players that don't yet have a full rating but do have a provisonal rating estimated on some number
|
46
|
+
# of games played prior to the tournament are indicated by values for the _rating_ and _games_ parameters:
|
47
|
+
#
|
48
|
+
# p = t.add_player(4, :rating => 1600, :games => 10)
|
49
|
+
# p.type # :provisional
|
50
|
+
#
|
51
|
+
# The value for the number of games should not exceed 19 since players with 20 or more games
|
52
|
+
# should have a full rating.
|
53
|
+
#
|
54
|
+
# == Fixed Ratings
|
55
|
+
#
|
56
|
+
# Players with fixed ratings just have a rating - no K-factor or number of previous games.
|
57
|
+
# When the tournament is rated, these players will have their tournament performance ratings
|
58
|
+
# calculated but the value returned by the method _new_rating_ will just be the rating they
|
59
|
+
# started with. Typically these are foreign players with FIDE ratings who are not members of
|
60
|
+
# the ICU and for whom ICU ratings are not desired.
|
61
|
+
#
|
62
|
+
# p = t.add_player(6, :rating => 2500)
|
63
|
+
# p.type # :foreign
|
64
|
+
#
|
65
|
+
# == No Rating
|
66
|
+
#
|
67
|
+
# Unrated players who do not have any rated games at all are indicated by leaving out any values for
|
68
|
+
# _rating_, _kfactor_ or _games_.
|
69
|
+
#
|
70
|
+
# p = t.add_player(5)
|
71
|
+
# p.type # :unrated
|
72
|
+
#
|
73
|
+
# == Invalid Combinations
|
74
|
+
#
|
75
|
+
# The above four types of players (_rated_, _provisional_, _unrated_, _foreign_) are the only
|
76
|
+
# valid ones and any attempt to add players with other combinations of the attributes
|
77
|
+
# _rating_, _kfactor_ and _games_ will cause an exception. For example:
|
78
|
+
#
|
79
|
+
# t.add_player(7, :rating => 2000, :kfactor => 16, :games => 10) # exception! - cannot have both kfactor and games
|
80
|
+
# t.add_plater(7, :kfactor => 16) # exception! - kfactor makes no sense without a rating
|
81
|
+
#
|
82
|
+
# == String Input Values
|
83
|
+
#
|
84
|
+
# Although _rating_ and _kfactor_ are held as Float values and _games_ and _num_ (the player number) as Fixnums,
|
85
|
+
# all these parameters can be specified using strings, even when padded with whitespace.
|
86
|
+
#
|
87
|
+
# p = t.add_player(" 0 ", :rating => " 2000.5 ", :kfactor => " 20.5 ")
|
88
|
+
# p.num # 0 (Fixnum)
|
89
|
+
# p.rating # 2000.5 (Float)
|
90
|
+
# p.kfactor # 20.5 (Float)
|
91
|
+
#
|
92
|
+
# == Calculation of K-factors
|
93
|
+
#
|
94
|
+
# Rather than pre-calculating the value to set for a rated player's K-factor, the RatedPlayer class can itself
|
95
|
+
# calculate K-factors if the releavant information is supplied. ICU K-factors depend not only on a player's
|
96
|
+
# rating, but also on their age and experience. Therefore, supply a hash, instead of a numerical value, for the
|
97
|
+
# _kfactor_ attribute with values set for date-of-birth (_dob_) and date joined (_joined_):
|
98
|
+
#
|
99
|
+
# t = Tournament.new(:start => "2010-07-10")
|
100
|
+
# p = t.add_player(1, :rating => 2225, :kfactor => { :dob => "1993-12-20", :joined => "2004-11-28" })
|
101
|
+
# p.kfactor # 16.0
|
102
|
+
#
|
103
|
+
# For this to work the tournament's optional start date must be set to enable the player's age and
|
104
|
+
# experience at the start of the tournament be to calculated. The ICU K-factor rules are:
|
105
|
+
#
|
106
|
+
# * 16 for players rated 2100 and over, otherwise
|
107
|
+
# * 40 for players aged under 21, otherwise
|
108
|
+
# * 32 for players who have been members for less than 8 years, otherwise
|
109
|
+
# * 24
|
110
|
+
#
|
111
|
+
# If you want to calculate K-factors accrding to some other, non-ICU scheme, then override the
|
112
|
+
# static method _kfactor_ of the RatedPlayer class and pass in a hash of whatever key-value pairs
|
113
|
+
# it requires as the value associated with _kfactor_ key in the _add_player_ method.
|
114
|
+
#
|
115
|
+
# == Description Parameter
|
116
|
+
#
|
117
|
+
# There is one other optional parameter, _desc_ (short for "description"). It has no effect on player
|
118
|
+
# type or rating calculations and it cannot be used to retrieve players from a tournament (only the
|
119
|
+
# player number can be used for that). Its only use is to attach additional arbitary data to players.
|
120
|
+
# Any object can be used and descriptions don't have to be unique. The attribute's typical use,
|
121
|
+
# if it's used at all, is expected to be for player names in the form of String values.
|
122
|
+
#
|
123
|
+
# t.add_player(8, :rating => 2800, :desc => 'Gary Kasparov (4100018)')
|
124
|
+
# t.player(8).desc # "Gary Kasparov (4100018)"
|
125
|
+
#
|
126
|
+
# == After the Tournament is Rated
|
127
|
+
#
|
128
|
+
# After the <em>rate!</em> method has been called on the ICU::RatedTournament object, the results
|
129
|
+
# of the rating calculations are available via various methods of the player objects:
|
130
|
+
#
|
131
|
+
# _new_rating_:: This is the player's new rating. For rated players it is their old rating
|
132
|
+
# plus their _rating_change_. For provisional players it is their performance
|
133
|
+
# rating including their previous games. For unrated players it is their
|
134
|
+
# tournament performance rating. New rarings are not calculated for foreign
|
135
|
+
# players so this method just returned their start _rating_.
|
136
|
+
# _rating_change_:: This is the difference between the old and new ratings for rated players,
|
137
|
+
# based on sum of expected scores in each games and the player's K-factor.
|
138
|
+
# Zero for all other types of players.
|
139
|
+
# _performance_:: This returns the tournament rating performance for rated, unrated and
|
140
|
+
# foreign players. For provisional players it returns a weighted average
|
141
|
+
# of the player's tournament performance and their previous games. For
|
142
|
+
# provisional and unrated players it is the same as _new_rating_.
|
143
|
+
# _expected_score_:: This returns the sum of expected scores over all results for all player types.
|
144
|
+
# For rated players, this number times the K-factor gives their rating change.
|
145
|
+
# It is calculated for provisional, unrated and foreign players but not actually
|
146
|
+
# used to estimate new ratings (for provisional and unrated players performance
|
147
|
+
# estimates are used instead).
|
148
|
+
#
|
149
|
+
# == Unrateable Players
|
150
|
+
#
|
151
|
+
# If a tournament contains groups of provisonal or unrated players who play games
|
152
|
+
# only amongst themselves and not against any rated or foreign opponents, they can't
|
153
|
+
# be rated. This is indicated by a value of _nil_ returned from the _new_rating_
|
154
|
+
# method.
|
155
|
+
#
|
160
156
|
class RatedPlayer
|
161
157
|
attr_reader :num, :rating, :kfactor, :games
|
162
158
|
attr_accessor :desc, :bonus
|
data/lib/icu_ratings/result.rb
CHANGED
@@ -1,98 +1,94 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module ICU
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
t
|
14
|
-
t.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
p
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
t
|
53
|
-
t.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
r
|
69
|
-
r.
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
t
|
76
|
-
[
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
_expected_score_, _rating_change_.
|
93
|
-
|
94
|
-
=end
|
95
|
-
|
4
|
+
#
|
5
|
+
# == Adding Results to Tournaments
|
6
|
+
#
|
7
|
+
# You don't create results directly with a constructor, instead, you add them to a tournament
|
8
|
+
# using the _add_result_ method, giving the round number, the player numbers and the score
|
9
|
+
# of the first player relative to the second:
|
10
|
+
#
|
11
|
+
# t = ICU::RatedTournament.new
|
12
|
+
# t.add_player(10)
|
13
|
+
# t.add_player(20)
|
14
|
+
# t.add_result(1, 10, 20, 'W')
|
15
|
+
#
|
16
|
+
# The above example expresses the result that in round 1 player 10 won against player 20. An exception is raised
|
17
|
+
# if either of the two players does not already exist in the tournament, if either player already has a game
|
18
|
+
# with another opponent in that round or if the two players already have a different result against each other
|
19
|
+
# in that round. Note that the result is added to both players: in the above example a win in round 1 against
|
20
|
+
# player 20 is added to player 10's results and a loss against player 10 in round 1 is added to player 20's results.
|
21
|
+
# It's OK (but unnecessary) to add the same result again from the other player's prespective as long as
|
22
|
+
# the score is consistent.
|
23
|
+
#
|
24
|
+
# t.add_result(1, 20, 10, 'L') # unnecessary (nothing would change) but would not cause an exception
|
25
|
+
# t.add_result(1, 20, 10, 'D') # inconsistent result - would raise an exception
|
26
|
+
#
|
27
|
+
# == Specifying the Score
|
28
|
+
#
|
29
|
+
# The method _score_ will always return a Float value (either 0.0, 0.5 or 1.0).
|
30
|
+
# When specifying a score using the _add_result_ of ICU::Tourmanent the same values
|
31
|
+
# can be used as can other, equally valid alternatives:
|
32
|
+
#
|
33
|
+
# win:: "1", "1.0", "W", "w" (String), 1 (Fixnum), 1.0 (Float)
|
34
|
+
# loss:: "0", "0.0", "L", "l" (String), 0 (Fixnum), 0.0 (Float)
|
35
|
+
# draw:: "½", "D", "d" (String), 0.5 (Float)
|
36
|
+
#
|
37
|
+
# Strings padded with whitespace also work (e.g. " 1.0 " and " W ").
|
38
|
+
#
|
39
|
+
# == Specifying the Players
|
40
|
+
#
|
41
|
+
# As described above, one way to specify the two players is via player numbers. Equally possible is player objects:
|
42
|
+
#
|
43
|
+
# t = ICU::RatedTournament.new
|
44
|
+
# p = t.add_player(10)
|
45
|
+
# q = t.add_plater(20)
|
46
|
+
# t.add_result(1, p, q, 'W')
|
47
|
+
#
|
48
|
+
# Or indeed (although this is unnecessary):
|
49
|
+
#
|
50
|
+
# t = ICU::RatedTournament.new
|
51
|
+
# t.add_player(10)
|
52
|
+
# t.add_plater(20)
|
53
|
+
# t.add_result(1, t.player(10), t.player(20), 'W')
|
54
|
+
#
|
55
|
+
# A players cannot have a results against themselves:
|
56
|
+
#
|
57
|
+
# t.add_player(2, 10, 10, 'D') # exception!
|
58
|
+
#
|
59
|
+
# == Retrieving Results
|
60
|
+
#
|
61
|
+
# Results belong to players (ICU::RatedPlayer objects) and are stored in an array accessed by the method _results_.
|
62
|
+
# Each result has a _round_ number, an _opponent_ object (also an ICU::RatedPlayer object) and a _score_ (1.0, 0.5 or 0.0):
|
63
|
+
#
|
64
|
+
# p = t.player(10)
|
65
|
+
# p.results.size # 1
|
66
|
+
# r = p.results[0]
|
67
|
+
# r.round # 1
|
68
|
+
# r.opponent.num # 20
|
69
|
+
# r.score # 1.0 (Float)
|
70
|
+
#
|
71
|
+
# The _results_ method returns results in round order, irrespective of what order they were added in:
|
72
|
+
#
|
73
|
+
# t = ICU::RatedTournament.new
|
74
|
+
# [0,1,2,3,4].each { |num| t.add_player(num) }
|
75
|
+
# [3,1].each { |rnd| t.add_result(rnd, 0, rnd, 'W') }
|
76
|
+
# [4,2].each { |rnd| t.add_result(rnd, 0, rnd, 'L') }
|
77
|
+
# t.player(0).results.map{ |r| r.round }.join(',') # "1,2,3,4"
|
78
|
+
#
|
79
|
+
# == Unrated Results
|
80
|
+
#
|
81
|
+
# Results that are not for rating, such as byes, walkovers and defaults, should not be
|
82
|
+
# added to the tournament. Instead, players can simply have no results for certain rounds.
|
83
|
+
# Indeed, it's even valid for players not to have any results at all (although, in that
|
84
|
+
# case, for those players, no new rating can be calculated from the tournament).
|
85
|
+
#
|
86
|
+
# == After the Tournament is Rated
|
87
|
+
#
|
88
|
+
# The main rating calculations are avaiable from player methods (see ICU::RatedPlayer)
|
89
|
+
# but additional details are available via methods of each player's individual results:
|
90
|
+
# _expected_score_, _rating_change_.
|
91
|
+
#
|
96
92
|
class RatedResult
|
97
93
|
# The round number.
|
98
94
|
def round
|
@@ -1,78 +1,74 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module ICU
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
ICU::RatedTournament
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
t
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
to be caught and handled in some suitable place in your code.
|
73
|
-
|
74
|
-
=end
|
75
|
-
|
4
|
+
#
|
5
|
+
# == Creating Tournaments
|
6
|
+
#
|
7
|
+
# ICU::RatedTournament objects are created directly.
|
8
|
+
#
|
9
|
+
# t = ICU::RatedTournament.new
|
10
|
+
#
|
11
|
+
# They have some optional parameters which can be set via the constructor or by calling
|
12
|
+
# the same-named setter methods. One is called _desc_ (short for description) the value
|
13
|
+
# of which can be any object but will, if utilized, typically be the name of the
|
14
|
+
# tournament as a string.
|
15
|
+
#
|
16
|
+
# t = ICU::RatedTournament.new(:desc => "Irish Championships 2010")
|
17
|
+
# puts t.desc # "Irish Championships 2010"
|
18
|
+
#
|
19
|
+
# Another optional parameter is _start_ for the start date. A Date object or a string that can be
|
20
|
+
# parsed as a string can be used to set it. The European convention is preferred for dates like
|
21
|
+
# "03/06/2013" (3rd of June, not 6th of March). Attempting to set an invalid date will raise an
|
22
|
+
# exception.
|
23
|
+
#
|
24
|
+
# t = ICU::RatedTournament.new(:start => "01/07/2010")
|
25
|
+
# puts t.start.class # Date
|
26
|
+
# puts t.start.to_s # "2010-07-01"
|
27
|
+
#
|
28
|
+
# Also, there is the option _no_bonuses_. Bonuses are a feature of the ICU rating system. If you are
|
29
|
+
# rating a non-ICU tournament (such as a FIDE tournament) where bonues are not awarded use this option.
|
30
|
+
#
|
31
|
+
# t = ICU::RatedTournament.new(:desc => 'Linares', :start => "07/02/2009", :no_bonuses => true)
|
32
|
+
#
|
33
|
+
# Note, however, that the ICU system also has its own unique way of treating provisional, unrated and
|
34
|
+
# foreign players, so the only FIDE tournaments that can be rated using this software are those that
|
35
|
+
# consist solely of rated players.
|
36
|
+
#
|
37
|
+
# == Rating Tournaments
|
38
|
+
#
|
39
|
+
# To rate a tournament, first add the players (see ICU::RatedPlayer for details):
|
40
|
+
#
|
41
|
+
# t.add_player(1, :rating => 2534, :kfactor => 16)
|
42
|
+
# # ...
|
43
|
+
#
|
44
|
+
# Then add the results (see ICU::RatedResult for details):
|
45
|
+
#
|
46
|
+
# t.add_result(1, 1, 2, 'W')
|
47
|
+
# # ...
|
48
|
+
#
|
49
|
+
# Then rate the tournament by calling the <em>rate!</em> method:
|
50
|
+
#
|
51
|
+
# t.rate!
|
52
|
+
#
|
53
|
+
# Now the results of the rating calculations can be retrieved from the players in the tournement
|
54
|
+
# or their results. For example, player 1's new rating would be:
|
55
|
+
#
|
56
|
+
# t.player(1).new_rating
|
57
|
+
#
|
58
|
+
# See ICU::RatedPlayer and ICU::RatedResult for more details.
|
59
|
+
#
|
60
|
+
# == Error Handling
|
61
|
+
#
|
62
|
+
# Some of the above methods have the potential to raise RuntimeError exceptions.
|
63
|
+
# In the case of _add_player_ and _add_result_, the use of invalid arguments
|
64
|
+
# would cause such an error. Theoretically, the <em>rate!</em> method could also throw an
|
65
|
+
# exception if the iterative algorithm it uses to estimate performance ratings
|
66
|
+
# of unrated players failed to converge. However an instance of non-convergence
|
67
|
+
# has yet to be observed in practice.
|
68
|
+
#
|
69
|
+
# Since exception throwing is how errors are signalled, you should arrange for them
|
70
|
+
# to be caught and handled in some suitable place in your code.
|
71
|
+
#
|
76
72
|
class RatedTournament
|
77
73
|
attr_accessor :desc
|
78
74
|
attr_reader :start, :no_bonuses
|
data/lib/icu_ratings/util.rb
CHANGED
@@ -3,33 +3,18 @@ require 'date' # needed for 1.9.1
|
|
3
3
|
module ICU
|
4
4
|
class Util
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
Note that the parse method of the Date class behaves differently in Ruby 1.8.7 and 1.9.1.
|
19
|
-
In 1.8.7 it assumes American dates and will raise ArgumentError on "30/03/2003".
|
20
|
-
In 1.9.1 it assumes European dates and will raise ArgumentError on "03/30/2003".
|
21
|
-
|
22
|
-
== Diffing dates
|
23
|
-
|
24
|
-
The method _age_ returns the difference of two dates:
|
25
|
-
|
26
|
-
Util.age(born, date) # age in years at the given date (Float)
|
27
|
-
Util.age(born) # age in years now (today)
|
28
|
-
|
29
|
-
Internally it uses _parsedate!_ so can throw a exception if an invalid date is supplied.
|
30
|
-
|
31
|
-
=end
|
32
|
-
|
6
|
+
# Parses strings into date objects, interpreting nn/nn/nnnn as dd/mm/yyyy. It raises an exception on error.
|
7
|
+
#
|
8
|
+
# Util.parsedate!('1955-11-09') # => Date (1955-11-09)
|
9
|
+
# Util.parsedate!('02/03/2009') # => Date (2009-03-02)
|
10
|
+
# Util.parsedate!('02/23/2009') # => Date (2009-02-23)
|
11
|
+
# Util.parsedate!('16th June 1986') # => Date (1986-06-16)
|
12
|
+
# Util.parsedate!('not a date') # exception raised
|
13
|
+
#
|
14
|
+
# Note that the parse method of the Date class behaves differently in Ruby 1.8.7 and 1.9.1.
|
15
|
+
# In 1.8.7 it assumes American dates and will raise ArgumentError on "30/03/2003".
|
16
|
+
# In 1.9.1 it assumes European dates and will raise ArgumentError on "03/30/2003".
|
17
|
+
#
|
33
18
|
def self.parsedate!(date)
|
34
19
|
return date.clone if date.is_a?(Date)
|
35
20
|
string = date.to_s.strip
|
@@ -42,6 +27,12 @@ Internally it uses _parsedate!_ so can throw a exception if an invalid date is s
|
|
42
27
|
end
|
43
28
|
end
|
44
29
|
|
30
|
+
# Returns the difference of two dates:
|
31
|
+
#
|
32
|
+
# Util.age(born, date) # age in years at the given date (Float)
|
33
|
+
# Util.age(born) # age in years now (today)
|
34
|
+
#
|
35
|
+
# Internally it uses _parsedate!_ so can throw a exception if an invalid date is supplied.
|
45
36
|
def self.age(born, date=Date.today)
|
46
37
|
born = parsedate!(born)
|
47
38
|
date = parsedate!(date)
|
data/lib/icu_ratings/version.rb
CHANGED
data/spec/tournament_spec.rb
CHANGED
@@ -160,20 +160,20 @@ module ICU
|
|
160
160
|
it "after the tournament is rated" do
|
161
161
|
@t.rate!
|
162
162
|
|
163
|
-
@t.player(1).expected_score.should
|
164
|
-
@t.player(2).expected_score.should
|
165
|
-
@t.player(3).expected_score.should
|
166
|
-
@t.player(4).expected_score.should
|
163
|
+
@t.player(1).expected_score.should be_within(0.001).of(2.249)
|
164
|
+
@t.player(2).expected_score.should be_within(0.001).of(1.760)
|
165
|
+
@t.player(3).expected_score.should be_within(0.001).of(1.240)
|
166
|
+
@t.player(4).expected_score.should be_within(0.001).of(0.751)
|
167
167
|
|
168
|
-
@t.player(1).rating_change.should
|
169
|
-
@t.player(2).rating_change.should
|
170
|
-
@t.player(3).rating_change.should
|
171
|
-
@t.player(4).rating_change.should
|
168
|
+
@t.player(1).rating_change.should be_within(0.01).of(7.51)
|
169
|
+
@t.player(2).rating_change.should be_within(0.01).of(4.81)
|
170
|
+
@t.player(3).rating_change.should be_within(0.01).of(-7.21)
|
171
|
+
@t.player(4).rating_change.should be_within(0.01).of(-30.05)
|
172
172
|
|
173
|
-
@t.player(1).new_rating.should
|
174
|
-
@t.player(2).new_rating.should
|
175
|
-
@t.player(3).new_rating.should
|
176
|
-
@t.player(4).new_rating.should
|
173
|
+
@t.player(1).new_rating.should be_within(0.1).of(2207.5)
|
174
|
+
@t.player(2).new_rating.should be_within(0.1).of(2104.8)
|
175
|
+
@t.player(3).new_rating.should be_within(0.1).of(1992.8)
|
176
|
+
@t.player(4).new_rating.should be_within(0.1).of(1870.0)
|
177
177
|
end
|
178
178
|
end
|
179
179
|
|
@@ -229,8 +229,8 @@ module ICU
|
|
229
229
|
].each do |item|
|
230
230
|
num, expected_score, new_rating = item
|
231
231
|
p = @t.player(num)
|
232
|
-
p.expected_score.should
|
233
|
-
p.new_rating.should
|
232
|
+
p.expected_score.should be_within(0.001).of(expected_score)
|
233
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
234
234
|
end
|
235
235
|
end
|
236
236
|
|
@@ -245,8 +245,8 @@ module ICU
|
|
245
245
|
].each do |item|
|
246
246
|
num, ytd_performance, tournament_performance = item
|
247
247
|
p = @t.player(num)
|
248
|
-
p.performance.should_not
|
249
|
-
p.performance.should
|
248
|
+
p.performance.should_not be_within(0.5).of(ytd_performance)
|
249
|
+
p.performance.should be_within(0.5).of(tournament_performance)
|
250
250
|
end
|
251
251
|
end
|
252
252
|
end
|
@@ -263,10 +263,10 @@ module ICU
|
|
263
263
|
end
|
264
264
|
|
265
265
|
it "should get same results as ICU rating database" do
|
266
|
-
@t.player(1).expected_score.should
|
267
|
-
@t.player(2).expected_score.should
|
268
|
-
@t.player(1).new_rating.should
|
269
|
-
@t.player(2).new_rating.should
|
266
|
+
@t.player(1).expected_score.should be_within(0.001).of(1.689)
|
267
|
+
@t.player(2).expected_score.should be_within(0.001).of(1.311)
|
268
|
+
@t.player(1).new_rating.should be_within(0.5).of(1378)
|
269
|
+
@t.player(2).new_rating.should be_within(0.5).of(1261)
|
270
270
|
end
|
271
271
|
end
|
272
272
|
|
@@ -380,12 +380,12 @@ module ICU
|
|
380
380
|
pc = @t.player(2)
|
381
381
|
|
382
382
|
af.score.should == 4.5
|
383
|
-
af.expected_score.should
|
384
|
-
af.new_rating.should
|
383
|
+
af.expected_score.should be_within(0.001).of(6.054)
|
384
|
+
af.new_rating.should be_within(0.5).of(2080)
|
385
385
|
|
386
386
|
pc.score.should == 4.0
|
387
|
-
pc.expected_score.should
|
388
|
-
pc.new_rating.should
|
387
|
+
pc.expected_score.should be_within(0.001).of(3.685)
|
388
|
+
pc.new_rating.should be_within(0.5).of(1984)
|
389
389
|
end
|
390
390
|
end
|
391
391
|
|
@@ -534,9 +534,9 @@ module ICU
|
|
534
534
|
].each do |item|
|
535
535
|
num, expected_score, new_rating = item
|
536
536
|
p = @t.player(num)
|
537
|
-
p.expected_score.should
|
538
|
-
p.new_rating.should
|
539
|
-
p.results.inject(p.rating){ |t,r| t + r.rating_change }.should
|
537
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
538
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
539
|
+
p.results.inject(p.rating){ |t,r| t + r.rating_change }.should be_within(0.5).of(new_rating)
|
540
540
|
end
|
541
541
|
end
|
542
542
|
|
@@ -547,8 +547,8 @@ module ICU
|
|
547
547
|
].each do |item|
|
548
548
|
num, expected_score, new_rating = item
|
549
549
|
p = @t.player(num)
|
550
|
-
p.expected_score.should
|
551
|
-
p.new_rating.should
|
550
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
551
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
552
552
|
end
|
553
553
|
end
|
554
554
|
|
@@ -608,8 +608,8 @@ module ICU
|
|
608
608
|
].each do |item|
|
609
609
|
num, expected_score, new_rating = item
|
610
610
|
p = @t.player(num)
|
611
|
-
p.expected_score.should
|
612
|
-
p.new_rating.should
|
611
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
612
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
613
613
|
end
|
614
614
|
end
|
615
615
|
end
|
@@ -661,8 +661,8 @@ module ICU
|
|
661
661
|
num, score, expected_score, new_rating, bonus = item
|
662
662
|
p = @t.player(num)
|
663
663
|
p.score.should == score
|
664
|
-
p.expected_score.should
|
665
|
-
p.new_rating.should
|
664
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
665
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
666
666
|
p.bonus.should == bonus
|
667
667
|
end
|
668
668
|
end
|
@@ -713,10 +713,10 @@ module ICU
|
|
713
713
|
num, score, expected_score, performance = item
|
714
714
|
p = @t.player(num)
|
715
715
|
p.score.should == score
|
716
|
-
p.expected_score.should
|
717
|
-
p.performance.should
|
716
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
717
|
+
p.performance.should be_within(0.5).of(performance)
|
718
718
|
if num == 1
|
719
|
-
p.new_rating.should
|
719
|
+
p.new_rating.should be_within(0.5).of(1836)
|
720
720
|
p.bonus.should == 71
|
721
721
|
else
|
722
722
|
p.new_rating.should == p.rating
|
@@ -773,9 +773,9 @@ module ICU
|
|
773
773
|
p = @t.player(num)
|
774
774
|
p.score.should == score
|
775
775
|
p.bonus.should == bonus
|
776
|
-
p.performance.should
|
777
|
-
p.expected_score.should
|
778
|
-
p.new_rating.should
|
776
|
+
p.performance.should be_within(0.5).of(performance)
|
777
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
778
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
779
779
|
end
|
780
780
|
end
|
781
781
|
end
|
@@ -833,9 +833,9 @@ module ICU
|
|
833
833
|
p = @t.player(num)
|
834
834
|
p.score.should == score
|
835
835
|
p.bonus.should == bonus
|
836
|
-
p.performance.should
|
837
|
-
p.expected_score.should
|
838
|
-
p.new_rating.should
|
836
|
+
p.performance.should be_within(num == 2 ? 0.6 : 0.5).of(performance)
|
837
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
838
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
839
839
|
end
|
840
840
|
end
|
841
841
|
|
@@ -846,9 +846,9 @@ module ICU
|
|
846
846
|
p = @t.player(num)
|
847
847
|
p.score.should == score
|
848
848
|
p.bonus.should == bonus
|
849
|
-
p.performance.should
|
850
|
-
p.expected_score.should
|
851
|
-
p.new_rating.should
|
849
|
+
p.performance.should be_within(num == 2 ? 0.6 : 0.5).of(performance)
|
850
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
851
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
852
852
|
end
|
853
853
|
end
|
854
854
|
|
@@ -860,9 +860,9 @@ module ICU
|
|
860
860
|
p = @t.player(num)
|
861
861
|
p.score.should == score
|
862
862
|
p.bonus.should == 0
|
863
|
-
p.performance.should_not
|
864
|
-
p.expected_score.should_not
|
865
|
-
p.new_rating.should_not
|
863
|
+
p.performance.should_not be_within(1.0).of(performance)
|
864
|
+
p.expected_score.should_not be_within(0.01).of(expected_score)
|
865
|
+
p.new_rating.should_not be_within(1.0).of(new_rating)
|
866
866
|
end
|
867
867
|
end
|
868
868
|
end
|
@@ -901,8 +901,8 @@ module ICU
|
|
901
901
|
].each do |item|
|
902
902
|
num, expected_score, new_rating = item
|
903
903
|
p = @t.player(num)
|
904
|
-
p.expected_score.should
|
905
|
-
p.new_rating.should
|
904
|
+
p.expected_score.should be_within(0.01).of(expected_score)
|
905
|
+
p.new_rating.should be_within(0.5).of(new_rating)
|
906
906
|
end
|
907
907
|
end
|
908
908
|
|
data/spec/util_spec.rb
CHANGED
@@ -50,9 +50,9 @@ module ICU
|
|
50
50
|
it "should return age in years" do
|
51
51
|
Util.age('2001-01-01', '2001-01-01').should == 0.0
|
52
52
|
Util.age('2001-01-01', '2002-01-01').should == 1.0
|
53
|
-
Util.age('2001-01-01', '2001-01-02').should
|
54
|
-
Util.age('2001-01-01', '2001-02-01').should
|
55
|
-
Util.age('1955-11-09', '2010-01-17').should
|
53
|
+
Util.age('2001-01-01', '2001-01-02').should be_within(0.01).of(1/365)
|
54
|
+
Util.age('2001-01-01', '2001-02-01').should be_within(0.01).of(1/12.0)
|
55
|
+
Util.age('1955-11-09', '2010-01-17').should be_within(0.01).of(54.2)
|
56
56
|
Util.age('2001-01-01', '2000-01-01').should == -1.0
|
57
57
|
end
|
58
58
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 1.0.
|
8
|
+
- 5
|
9
|
+
version: 1.0.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mark Orr
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-12-31 00:00:00 +00:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -27,9 +27,9 @@ dependencies:
|
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
segments:
|
29
29
|
- 2
|
30
|
+
- 3
|
30
31
|
- 0
|
31
|
-
|
32
|
-
version: 2.0.0
|
32
|
+
version: 2.3.0
|
33
33
|
type: :development
|
34
34
|
version_requirements: *id001
|
35
35
|
description: Build an object that represents a chess tournament then get it to calculate ratings of all the players.
|
@@ -82,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
82
|
version: "0"
|
83
83
|
requirements: []
|
84
84
|
|
85
|
-
rubyforge_project:
|
85
|
+
rubyforge_project: icu_ratings
|
86
86
|
rubygems_version: 1.3.7
|
87
87
|
signing_key:
|
88
88
|
specification_version: 3
|