icu_ratings 1.0.4 → 1.0.5
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/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
|