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.
@@ -1,162 +1,158 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module ICU
4
-
5
- =begin rdoc
6
-
7
- == Adding Players to Tournaments
8
-
9
- You don't directly create players, rather you add them to tournaments with the _add_player_ method.
10
-
11
- t = ICU::RatedTournament.new
12
- t.add_player(1)
13
-
14
- There is only one mandatory parameter - the player number - which can be any integer value
15
- except player numbers must be unique in each tournament:
16
-
17
- t.add_player(2) # fine
18
- t.add_player(2) # attempt to add a second player with the same number - exception!
19
-
20
- == Retrieving Players from Tournaments
21
-
22
- Player objects can be retrieved from ICU::RatedTournament objects with the latter's _player_ method
23
- in conjunction with the appropriate player number:
24
-
25
- p = t.player(2)
26
- p.num # 2
27
-
28
- Or the player object can be saved from the return value from _add_player_:
29
-
30
- p = t.add_player(-2)
31
- p.num # -2
32
-
33
- If the number supplied to _player_ is an invalid player number, the method returns _nil_.
34
-
35
- Different types of players are signalled by different combinations of the three optional
36
- parameters: _rating_, _kfactor_ and _games_.
37
-
38
- == Full Ratings
39
-
40
- Rated players have a full rating and a K-factor and are added by including valid values for those two parameters:
41
-
42
- p = t.add_player(3, :rating => 2000, :kfactor => 16)
43
- p.type # :rated
44
-
45
- == Provisional Ratings
46
-
47
- Players that don't yet have a full rating but do have a provisonal rating estimated on some number
48
- of games played prior to the tournament are indicated by values for the _rating_ and _games_ parameters:
49
-
50
- p = t.add_player(4, :rating => 1600, :games => 10)
51
- p.type # :provisional
52
-
53
- The value for the number of games should not exceed 19 since players with 20 or more games
54
- should have a full rating.
55
-
56
- == Fixed Ratings
57
-
58
- Players with fixed ratings just have a rating - no K-factor or number of previous games.
59
- When the tournament is rated, these players will have their tournament performance ratings
60
- calculated but the value returned by the method _new_rating_ will just be the rating they
61
- started with. Typically these are foreign players with FIDE ratings who are not members of
62
- the ICU and for whom ICU ratings are not desired.
63
-
64
- p = t.add_player(6, :rating => 2500)
65
- p.type # :foreign
66
-
67
- == No Rating
68
-
69
- Unrated players who do not have any rated games at all are indicated by leaving out any values for
70
- _rating_, _kfactor_ or _games_.
71
-
72
- p = t.add_player(5)
73
- p.type # :unrated
74
-
75
- == Invalid Combinations
76
-
77
- The above four types of players (_rated_, _provisional_, _unrated_, _foreign_) are the only
78
- valid ones and any attempt to add players with other combinations of the attributes
79
- _rating_, _kfactor_ and _games_ will cause an exception. For example:
80
-
81
- t.add_player(7, :rating => 2000, :kfactor => 16, :games => 10) # exception! - cannot have both kfactor and games
82
- t.add_plater(7, :kfactor => 16) # exception! - kfactor makes no sense without a rating
83
-
84
- == String Input Values
85
-
86
- Although _rating_ and _kfactor_ are held as Float values and _games_ and _num_ (the player number) as Fixnums,
87
- all these parameters can be specified using strings, even when padded with whitespace.
88
-
89
- p = t.add_player(" 0 ", :rating => " 2000.5 ", :kfactor => " 20.5 ")
90
- p.num # 0 (Fixnum)
91
- p.rating # 2000.5 (Float)
92
- p.kfactor # 20.5 (Float)
93
-
94
- == Calculation of K-factors
95
-
96
- Rather than pre-calculating the value to set for a rated player's K-factor, the RatedPlayer class can itself
97
- calculate K-factors if the releavant information is supplied. ICU K-factors depend not only on a player's
98
- rating, but also on their age and experience. Therefore, supply a hash, instead of a numerical value, for the
99
- _kfactor_ attribute with values set for date-of-birth (_dob_) and date joined (_joined_):
100
-
101
- t = Tournament.new(:start => "2010-07-10")
102
- p = t.add_player(1, :rating => 2225, :kfactor => { :dob => "1993-12-20", :joined => "2004-11-28" })
103
- p.kfactor # 16.0
104
-
105
- For this to work the tournament's optional start date must be set to enable the player's age and
106
- experience at the start of the tournament be to calculated. The ICU K-factor rules are:
107
-
108
- * 16 for players rated 2100 and over, otherwise
109
- * 40 for players aged under 21, otherwise
110
- * 32 for players who have been members for less than 8 years, otherwise
111
- * 24
112
-
113
- If you want to calculate K-factors accrding to some other, non-ICU scheme, then override the
114
- static method _kfactor_ of the RatedPlayer class and pass in a hash of whatever key-value pairs
115
- it requires as the value associated with _kfactor_ key in the _add_player_ method.
116
-
117
- == Description Parameter
118
-
119
- There is one other optional parameter, _desc_ (short for "description"). It has no effect on player
120
- type or rating calculations and it cannot be used to retrieve players from a tournament (only the
121
- player number can be used for that). Its only use is to attach additional arbitary data to players.
122
- Any object can be used and descriptions don't have to be unique. The attribute's typical use,
123
- if it's used at all, is expected to be for player names in the form of String values.
124
-
125
- t.add_player(8, :rating => 2800, :desc => 'Gary Kasparov (4100018)')
126
- t.player(8).desc # "Gary Kasparov (4100018)"
127
-
128
- == After the Tournament is Rated
129
-
130
- After the <em>rate!</em> method has been called on the ICU::RatedTournament object, the results
131
- of the rating calculations are available via various methods of the player objects:
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).
150
-
151
- == Unrateable Players
152
-
153
- If a tournament contains groups of provisonal or unrated players who play games
154
- only amongst themselves and not against any rated or foreign opponents, they can't
155
- be rated. This is indicated by a value of _nil_ returned from the _new_rating_
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
@@ -1,98 +1,94 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module ICU
4
-
5
- =begin rdoc
6
-
7
- == Adding Results to Tournaments
8
-
9
- You don't create results directly with a constructor, instead, you add them to a tournament
10
- using the _add_result_ method, giving the round number, the player numbers and the score
11
- of the first player relative to the second:
12
-
13
- t = ICU::RatedTournament.new
14
- t.add_player(10)
15
- t.add_player(20)
16
- t.add_result(1, 10, 20, 'W')
17
-
18
- The above example expresses the result that in round 1 player 10 won against player 20. An exception is raised
19
- if either of the two players does not already exist in the tournament, if either player already has a game
20
- with another opponent in that round or if the two players already have a different result against each other
21
- in that round. Note that the result is added to both players: in the above example a win in round 1 against
22
- 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.
23
- It's OK (but unnecessary) to add the same result again from the other player's prespective as long as
24
- the score is consistent.
25
-
26
- t.add_result(1, 20, 10, 'L') # unnecessary (nothing would change) but would not cause an exception
27
- t.add_result(1, 20, 10, 'D') # inconsistent result - would raise an exception
28
-
29
- == Specifying the Score
30
-
31
- The method _score_ will always return a Float value (either 0.0, 0.5 or 1.0).
32
- When specifying a score using the _add_result_ of ICU::Tourmanent the same values
33
- can be used as can other, equally valid alternatives:
34
-
35
- win:: "1", "1.0", "W", "w" (String), 1 (Fixnum), 1.0 (Float)
36
- loss:: "0", "0.0", "L", "l" (String), 0 (Fixnum), 0.0 (Float)
37
- draw:: "½", "D", "d" (String), 0.5 (Float)
38
-
39
- Strings padded with whitespace also work (e.g. " 1.0 " and " W ").
40
-
41
- == Specifying the Players
42
-
43
- As described above, one way to specify the two players is via player numbers. Equally possible is player objects:
44
-
45
- t = ICU::RatedTournament.new
46
- p = t.add_player(10)
47
- q = t.add_plater(20)
48
- t.add_result(1, p, q, 'W')
49
-
50
- Or indeed (although this is unnecessary):
51
-
52
- t = ICU::RatedTournament.new
53
- t.add_player(10)
54
- t.add_plater(20)
55
- t.add_result(1, t.player(10), t.player(20), 'W')
56
-
57
- A players cannot have a results against themselves:
58
-
59
- t.add_player(2, 10, 10, 'D') # exception!
60
-
61
- == Retrieving Results
62
-
63
- Results belong to players (ICU::RatedPlayer objects) and are stored in an array accessed by the method _results_.
64
- Each result has a _round_ number, an _opponent_ object (also an ICU::RatedPlayer object) and a _score_ (1.0, 0.5 or 0.0):
65
-
66
- p = t.player(10)
67
- p.results.size # 1
68
- r = p.results[0]
69
- r.round # 1
70
- r.opponent.num # 20
71
- r.score # 1.0 (Float)
72
-
73
- The _results_ method returns results in round order, irrespective of what order they were added in:
74
-
75
- t = ICU::RatedTournament.new
76
- [0,1,2,3,4].each { |num| t.add_player(num) }
77
- [3,1].each { |rnd| t.add_result(rnd, 0, rnd, 'W') }
78
- [4,2].each { |rnd| t.add_result(rnd, 0, rnd, 'L') }
79
- t.player(0).results.map{ |r| r.round }.join(',') # "1,2,3,4"
80
-
81
- == Unrated Results
82
-
83
- Results that are not for rating, such as byes, walkovers and defaults, should not be
84
- added to the tournament. Instead, players can simply have no results for certain rounds.
85
- Indeed, it's even valid for players not to have any results at all (although, in that
86
- case, for those players, no new rating can be calculated from the tournament).
87
-
88
- == After the Tournament is Rated
89
-
90
- The main rating calculations are avaiable from player methods (see ICU::RatedPlayer)
91
- but additional details are available via methods of each player's individual results:
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
- =begin rdoc
6
-
7
- == Creating Tournaments
8
-
9
- ICU::RatedTournament objects are created directly.
10
-
11
- t = ICU::RatedTournament.new
12
-
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.
17
-
18
- t = ICU::RatedTournament.new(:desc => "Irish Championships 2010")
19
- puts t.desc # "Irish Championships 2010"
20
-
21
- Another optional parameter is _start_ for the start date. A Date object or a string that can be
22
- parsed as a string can be used to set it. The European convention is preferred for dates like
23
- "03/06/2013" (3rd of June, not 6th of March). Attempting to set an invalid date will raise an
24
- exception.
25
-
26
- t = ICU::RatedTournament.new(:start => "01/07/2010")
27
- puts t.start.class # Date
28
- puts t.start.to_s # "2010-07-01"
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
-
39
- == Rating Tournaments
40
-
41
- To rate a tournament, first add the players (see ICU::RatedPlayer for details):
42
-
43
- t.add_player(1, :rating => 2534, :kfactor => 16)
44
- # ...
45
-
46
- Then add the results (see ICU::RatedResult for details):
47
-
48
- t.add_result(1, 1, 2, 'W')
49
- # ...
50
-
51
- Then rate the tournament by calling the <em>rate!</em> method:
52
-
53
- t.rate!
54
-
55
- Now the results of the rating calculations can be retrieved from the players in the tournement
56
- or their results. For example, player 1's new rating would be:
57
-
58
- t.player(1).new_rating
59
-
60
- See ICU::RatedPlayer and ICU::RatedResult for more details.
61
-
62
- == Error Handling
63
-
64
- Some of the above methods have the potential to raise RuntimeError exceptions.
65
- In the case of _add_player_ and _add_result_, the use of invalid arguments
66
- would cause such an error. Theoretically, the <em>rate!</em> method could also throw an
67
- exception if the iterative algorithm it uses to estimate performance ratings
68
- of unrated players failed to converge. However an instance of non-convergence
69
- has yet to be observed in practice.
70
-
71
- Since exception throwing is how errors are signalled, you should arrange for them
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
@@ -3,33 +3,18 @@ require 'date' # needed for 1.9.1
3
3
  module ICU
4
4
  class Util
5
5
 
6
- =begin rdoc
7
-
8
- == Parsing dates
9
-
10
- The method _parsedate!_ parses strings into date objects, interpreting nn/nn/nnnn as dd/mm/yyyy. It raises an exception on error.
11
-
12
- Util.parsedate!('1955-11-09') # => Date (1955-11-09)
13
- Util.parsedate!('02/03/2009') # => Date (2009-03-02)
14
- Util.parsedate!('02/23/2009') # => Date (2009-02-23)
15
- Util.parsedate!('16th June 1986') # => Date (1986-06-16)
16
- Util.parsedate!('not a date') # exception raised
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)
@@ -1,5 +1,7 @@
1
+ # :enddoc:
2
+
1
3
  module ICU
2
4
  class Ratings
3
- VERSION = "1.0.4"
5
+ VERSION = "1.0.5"
4
6
  end
5
7
  end
@@ -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 be_close(2.249, 0.001)
164
- @t.player(2).expected_score.should be_close(1.760, 0.001)
165
- @t.player(3).expected_score.should be_close(1.240, 0.001)
166
- @t.player(4).expected_score.should be_close(0.751, 0.001)
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 be_close(7.51, 0.01)
169
- @t.player(2).rating_change.should be_close(4.81, 0.01)
170
- @t.player(3).rating_change.should be_close(-7.21, 0.01)
171
- @t.player(4).rating_change.should be_close(-30.05, 0.01)
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 be_close(2207.5, 0.1)
174
- @t.player(2).new_rating.should be_close(2104.8, 0.1)
175
- @t.player(3).new_rating.should be_close(1992.8, 0.1)
176
- @t.player(4).new_rating.should be_close(1870.0, 0.1)
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 be_close(expected_score, 0.001)
233
- p.new_rating.should be_close(new_rating, 0.5)
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 be_close(ytd_performance, 0.5)
249
- p.performance.should be_close(tournament_performance, 0.5)
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 be_close(1.689, 0.001)
267
- @t.player(2).expected_score.should be_close(1.311, 0.001)
268
- @t.player(1).new_rating.should be_close(1378, 0.5)
269
- @t.player(2).new_rating.should be_close(1261, 0.5)
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 be_close(6.054, 0.001)
384
- af.new_rating.should be_close(2080, 0.5)
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 be_close(3.685, 0.001)
388
- pc.new_rating.should be_close(1984, 0.5)
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 be_close(expected_score, 0.01)
538
- p.new_rating.should be_close(new_rating, 0.5)
539
- p.results.inject(p.rating){ |t,r| t + r.rating_change }.should be_close(new_rating, 0.5)
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 be_close(expected_score, 0.01)
551
- p.new_rating.should be_close(new_rating, 0.5)
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 be_close(expected_score, 0.01)
612
- p.new_rating.should be_close(new_rating, 0.5)
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 be_close(expected_score, 0.01)
665
- p.new_rating.should be_close(new_rating, 0.5)
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 be_close(expected_score, 0.01)
717
- p.performance.should be_close(performance, 0.5)
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 be_close(1836, 0.5)
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 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)
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 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)
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 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)
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 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)
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 be_close(expected_score, 0.01)
905
- p.new_rating.should be_close(new_rating, 0.5)
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 be_close(1/365.0, 0.01)
54
- Util.age('2001-01-01', '2001-02-01').should be_close(1/12.0, 0.01)
55
- Util.age('1955-11-09', '2010-01-17').should be_close(54.2, 0.01)
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
- - 4
9
- version: 1.0.4
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-10-14 00:00:00 +01:00
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
- - 0
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