glicko2 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +4 -8
- data/lib/glicko2.rb +31 -133
- data/lib/glicko2/player.rb +189 -0
- data/lib/glicko2/rating_period.rb +66 -0
- data/lib/glicko2/version.rb +1 -1
- data/spec/minitest_helper.rb +1 -0
- data/spec/player_spec.rb +4 -0
- data/spec/rating_period_spec.rb +19 -30
- data/spec/util_spec.rb +17 -0
- metadata +7 -2
data/README.md
CHANGED
@@ -28,15 +28,11 @@ Rating = Struct.new(:rating, :rating_deviation, :volatility)
|
|
28
28
|
rating1 = Rating.new(1400, 30, 0.06)
|
29
29
|
rating2 = Rating.new(1550, 100, 0.06)
|
30
30
|
|
31
|
-
#
|
32
|
-
|
33
|
-
player2 = Glicko2::Player.from_obj(rating2)
|
31
|
+
# Rating period with all participating ratings
|
32
|
+
period = Glicko2::RatingPeriod.from_objs [rating1, rating2]
|
34
33
|
|
35
|
-
#
|
36
|
-
period
|
37
|
-
|
38
|
-
# Register a game in this rating period
|
39
|
-
period.game([player1, player2], [1,2])
|
34
|
+
# Register a game where rating1 wins against rating2
|
35
|
+
period.game([rating1, rating2], [1,2])
|
40
36
|
|
41
37
|
# Generate the next rating period with updated players
|
42
38
|
next_period = period.generate_next
|
data/lib/glicko2.rb
CHANGED
@@ -1,119 +1,43 @@
|
|
1
1
|
require "glicko2/version"
|
2
|
+
require "glicko2/player"
|
3
|
+
require "glicko2/rating_period"
|
2
4
|
|
3
5
|
module Glicko2
|
4
|
-
TOLERANCE = 0.0000001
|
5
6
|
DEFAULT_VOLATILITY = 0.06
|
6
7
|
DEFAULT_GLICKO_RATING = 1500.0
|
7
8
|
DEFAULT_GLICKO_RATING_DEVIATION = 350.0
|
8
9
|
|
9
|
-
GLICKO_GRADIENT = 173.7178
|
10
|
-
GLICKO_INTERCEPT = DEFAULT_GLICKO_RATING
|
11
|
-
|
12
10
|
VOLATILITY_CHANGE = 0.5
|
13
11
|
|
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
|
-
end
|
44
|
-
|
45
|
-
def delta(others, scores)
|
46
|
-
others.zip(scores).reduce(0) do |d, (other, score)|
|
47
|
-
d + other.g * (score - e(other))
|
48
|
-
end * variance(others)
|
49
|
-
end
|
50
|
-
|
51
|
-
def f_part1(x, others, scores)
|
52
|
-
sd_sq = sd ** 2
|
53
|
-
v = variance(others)
|
54
|
-
_x = Math.exp(x)
|
55
|
-
(_x * (delta(others, scores) ** 2 - sd_sq - v - _x)) / (2 * (sd_sq + v + _x) ** 2)
|
56
|
-
end
|
57
|
-
|
58
|
-
def f_part2(x)
|
59
|
-
(x - Math::log(volatility ** 2)) / VOLATILITY_CHANGE ** 2
|
60
|
-
end
|
61
|
-
|
62
|
-
def f(x, others, scores)
|
63
|
-
f_part1(x, others, scores) - f_part2(x)
|
64
|
-
end
|
65
|
-
|
66
|
-
def generate_next(others, scores)
|
67
|
-
if others.length < 1
|
68
|
-
sd_pre = Math.sqrt(sd ** 2 + volatility ** 2)
|
69
|
-
return self.class.new(mean, sd_pre, volatility, obj) if others.length < 1
|
70
|
-
end
|
71
|
-
_v = variance(others)
|
72
|
-
a = Math::log(volatility ** 2)
|
73
|
-
if delta(others, scores) > sd ** 2 + _v
|
74
|
-
b = Math.log(_delta - sd ** 2 - _v)
|
75
|
-
else
|
76
|
-
k = 1
|
77
|
-
k += 1 while f(a - k * VOLATILITY_CHANGE, others, scores) < 0
|
78
|
-
b = a - k * VOLATILITY_CHANGE
|
79
|
-
end
|
80
|
-
fa = f(a, others, scores)
|
81
|
-
fb = f(b, others, scores)
|
82
|
-
while (b - a).abs > TOLERANCE
|
83
|
-
c = a + (a - b) * fa / (fb - fa)
|
84
|
-
fc = f(c, others, scores)
|
85
|
-
if fc * fb < 0
|
86
|
-
a = b
|
87
|
-
fa = fb
|
88
|
-
else
|
89
|
-
fa /= 2.0
|
90
|
-
end
|
91
|
-
b = c
|
92
|
-
fb = fc
|
93
|
-
end
|
94
|
-
volatility1 = Math.exp(a / 2.0)
|
95
|
-
sd_pre = Math.sqrt(sd ** 2 + volatility1 ** 2)
|
96
|
-
sd1 = 1 / Math.sqrt(1 / sd_pre ** 2 + 1 / _v)
|
97
|
-
mean1 = mean + sd1 ** 2 * others.zip(scores).reduce(0) {|x, (other, score)| x + other.g * (score - e(other)) }
|
98
|
-
self.class.new(mean1, sd1, volatility1, obj)
|
99
|
-
end
|
100
|
-
|
101
|
-
def update_obj
|
102
|
-
@obj.rating = GLICKO_GRADIENT * mean + GLICKO_INTERCEPT
|
103
|
-
@obj.rating_deviation = GLICKO_GRADIENT * sd
|
104
|
-
@obj.volatility = volatility
|
105
|
-
end
|
106
|
-
|
107
|
-
def to_s
|
108
|
-
"#<Player mean=#{mean}, sd=#{sd}, volatility=#{volatility}, obj=#{obj}>"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
class RatingPeriod
|
113
|
-
def initialize(players)
|
114
|
-
@players = players.reduce({}) { |memo, player| memo[player] = []; memo }
|
115
|
-
end
|
116
|
-
|
12
|
+
# Collection of helper methods
|
13
|
+
class Util
|
14
|
+
GLICKO_GRADIENT = 173.7178
|
15
|
+
GLICKO_INTERCEPT = DEFAULT_GLICKO_RATING
|
16
|
+
|
17
|
+
# Convert from the original Glicko scale to Glicko2 scale
|
18
|
+
#
|
19
|
+
# @param [Numeric] r Glicko rating
|
20
|
+
# @param [Numeric] rd Glicko rating deviation
|
21
|
+
# @return [Array<Numeric>]
|
22
|
+
def self.to_glicko2(r, rd)
|
23
|
+
[(r - GLICKO_INTERCEPT) / GLICKO_GRADIENT, rd / GLICKO_GRADIENT]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert from the Glicko2 scale to the original Glicko scale
|
27
|
+
#
|
28
|
+
# @param [Numeric] m Glicko2 mean
|
29
|
+
# @param [Numeric] sd Glicko2 standard deviation
|
30
|
+
# @return [Array<Numeric>]
|
31
|
+
def self.to_glicko(m, sd)
|
32
|
+
[GLICKO_GRADIENT * m + GLICKO_INTERCEPT, GLICKO_GRADIENT * sd]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Convert from a rank, where lower numbers win against higher numbers,
|
36
|
+
# into Glicko scores where wins are `1`, draws are `0.5` and losses are `0`.
|
37
|
+
#
|
38
|
+
# @param [Integer] rank players rank
|
39
|
+
# @param [Integer] other opponents rank
|
40
|
+
# @return [Numeric] Glicko score
|
117
41
|
def self.ranks_to_score(rank, other)
|
118
42
|
if rank < other
|
119
43
|
1.0
|
@@ -123,31 +47,5 @@ module Glicko2
|
|
123
47
|
0.0
|
124
48
|
end
|
125
49
|
end
|
126
|
-
|
127
|
-
def game(game_players, ranks)
|
128
|
-
game_players.zip(ranks).each do |player, rank|
|
129
|
-
game_players.zip(ranks).each do |other, other_rank|
|
130
|
-
next if player == other
|
131
|
-
@players[player] << [other, self.class.ranks_to_score(rank, other_rank)]
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def generate_next
|
137
|
-
p = []
|
138
|
-
@players.each do |player, games|
|
139
|
-
p << player.generate_next(*games.transpose)
|
140
|
-
end
|
141
|
-
self.class.new(p)
|
142
|
-
end
|
143
|
-
|
144
|
-
def players
|
145
|
-
@players.keys
|
146
|
-
end
|
147
|
-
|
148
|
-
def to_s
|
149
|
-
"#<RatingPeriod players=#{@players.keys}"
|
150
|
-
end
|
151
50
|
end
|
152
|
-
|
153
51
|
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module Glicko2
|
2
|
+
# Calculates a new Glicko2 ranking based on a seed object and game outcomes.
|
3
|
+
#
|
4
|
+
# The example from the Glicko2 paper, where a player wins against the first
|
5
|
+
# opponent, but then looses against the next two:
|
6
|
+
#
|
7
|
+
# Rating = Struct.new(:rating, :rating_deviation, :volatility)
|
8
|
+
#
|
9
|
+
# player_seed = Rating.new(1500, 200, 0.06)
|
10
|
+
# opponent1_seed = Rating.new(1400, 30, 0.06)
|
11
|
+
# opponent2_seed = Rating.new(1550, 100, 0.06)
|
12
|
+
# opponent3_seed = Rating.new(1700, 300, 0.06)
|
13
|
+
#
|
14
|
+
# player = Glicko2::Player.from_obj(player_seed)
|
15
|
+
# opponent1 = Glicko2::Player.from_obj(opponent1_seed)
|
16
|
+
# opponent2 = Glicko2::Player.from_obj(opponent2_seed)
|
17
|
+
# opponent3 = Glicko2::Player.from_obj(opponent3_seed)
|
18
|
+
#
|
19
|
+
# new_player = player.generate_next([opponent1, opponent2, opponent3],
|
20
|
+
# [1, 0, 0])
|
21
|
+
# new_player.update_obj
|
22
|
+
#
|
23
|
+
# puts player_seed
|
24
|
+
#
|
25
|
+
class Player
|
26
|
+
TOLERANCE = 0.0000001
|
27
|
+
|
28
|
+
attr_reader :mean, :sd, :volatility, :obj
|
29
|
+
|
30
|
+
# Create a {Player} from a seed object, converting from Glicko
|
31
|
+
# ratings to Glicko2.
|
32
|
+
#
|
33
|
+
# @param [#rating,#rating_deviation,#volatility] obj seed values object
|
34
|
+
# @return [Player] constructed instance.
|
35
|
+
def self.from_obj(obj)
|
36
|
+
mean, sd = Util.to_glicko2(obj.rating, obj.rating_deviation)
|
37
|
+
new(mean, sd, obj.volatility, obj)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [Numeric] mean player mean
|
41
|
+
# @param [Numeric] sd player standard deviation
|
42
|
+
# @param [Numeric] volatility player volatility
|
43
|
+
# @param [#rating,#rating_deviation,#volatility] obj seed values object
|
44
|
+
def initialize(mean, sd, volatility, obj=nil)
|
45
|
+
@mean = mean
|
46
|
+
@sd = sd
|
47
|
+
@volatility = volatility
|
48
|
+
@obj = obj
|
49
|
+
@e = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Calculate `g(phi)` as defined in the Glicko2 paper
|
53
|
+
#
|
54
|
+
# @return [Numeric]
|
55
|
+
def g
|
56
|
+
@g ||= 1 / Math.sqrt(1 + 3 * sd ** 2 / Math::PI ** 2)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Calculate `E(mu, mu_j, phi_j)` as defined in the Glicko2 paper
|
60
|
+
#
|
61
|
+
# @param [Player] other the `j` player
|
62
|
+
# @return [Numeric]
|
63
|
+
def e(other)
|
64
|
+
@e[other] ||= 1 / (1 + Math.exp(-other.g * (mean - other.mean)))
|
65
|
+
end
|
66
|
+
|
67
|
+
# Calculate the estimated variance of the team's/player's rating based only
|
68
|
+
# on the game outcomes.
|
69
|
+
#
|
70
|
+
# @param [Array<Player>] others other participating players.
|
71
|
+
# @return [Numeric]
|
72
|
+
def variance(others)
|
73
|
+
return 0.0 if others.length < 1
|
74
|
+
others.reduce(0) do |v, other|
|
75
|
+
e_other = e(other)
|
76
|
+
v + other.g ** 2 * e_other * (1 - e_other)
|
77
|
+
end ** -1
|
78
|
+
end
|
79
|
+
|
80
|
+
# Calculate the estimated improvement in rating by comparing the
|
81
|
+
# pre-period rating to the performance rating based only on game outcomes.
|
82
|
+
#
|
83
|
+
# @param [Array<Player>] others list of opponent players
|
84
|
+
# @param [Array<Numeric>] scores list of correlating scores (`0` for a loss,
|
85
|
+
# `0.5` for a draw and `1` for a win).
|
86
|
+
# @return [Numeric]
|
87
|
+
def delta(others, scores)
|
88
|
+
others.zip(scores).reduce(0) do |d, (other, score)|
|
89
|
+
d + other.g * (score - e(other))
|
90
|
+
end * variance(others)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Calculate `f(x)` as defined in the Glicko2 paper
|
94
|
+
#
|
95
|
+
# @param [Numeric] x
|
96
|
+
# @param [Numeric] d the result of calculating {#delta}
|
97
|
+
# @param [Numeric] v the result of calculating {#variance}
|
98
|
+
# @return [Numeric]
|
99
|
+
def f(x, d, v)
|
100
|
+
f_part1(x, d, v) - f_part2(x)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calculate the new value of the volatility
|
104
|
+
#
|
105
|
+
# @param [Numeric] d the result of calculating {#delta}
|
106
|
+
# @param [Numeric] v the result of calculating {#variance}
|
107
|
+
# @return [Numeric]
|
108
|
+
def volatility1(d, v)
|
109
|
+
a = Math::log(volatility ** 2)
|
110
|
+
if d > sd ** 2 + v
|
111
|
+
b = Math.log(d - sd ** 2 - v)
|
112
|
+
else
|
113
|
+
k = 1
|
114
|
+
k += 1 while f(a - k * VOLATILITY_CHANGE, d, v) < 0
|
115
|
+
b = a - k * VOLATILITY_CHANGE
|
116
|
+
end
|
117
|
+
fa = f(a, d, v)
|
118
|
+
fb = f(b, d, v)
|
119
|
+
while (b - a).abs > TOLERANCE
|
120
|
+
c = a + (a - b) * fa / (fb - fa)
|
121
|
+
fc = f(c, d, v)
|
122
|
+
if fc * fb < 0
|
123
|
+
a = b
|
124
|
+
fa = fb
|
125
|
+
else
|
126
|
+
fa /= 2.0
|
127
|
+
end
|
128
|
+
b = c
|
129
|
+
fb = fc
|
130
|
+
end
|
131
|
+
Math.exp(a / 2.0)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Create new {Player} with updated values.
|
135
|
+
#
|
136
|
+
# This method will not modify any objects that are passed into it.
|
137
|
+
#
|
138
|
+
# @param [Array<Player>] others list of opponent players
|
139
|
+
# @param [Array<Numeric>] scores list of correlating scores (`0` for a loss,
|
140
|
+
# `0.5` for a draw and `1` for a win).
|
141
|
+
# @return [Player]
|
142
|
+
def generate_next(others, scores)
|
143
|
+
if others.length < 1
|
144
|
+
generate_next_without_games
|
145
|
+
else
|
146
|
+
generate_next_with_games(others, scores)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Update seed object with this player's values
|
151
|
+
def update_obj
|
152
|
+
@obj.rating, @obj.rating_deviation = Util.to_glicko(mean, sd)
|
153
|
+
@obj.volatility = volatility
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
"#<Player mean=#{mean}, sd=#{sd}, volatility=#{volatility}, obj=#{obj}>"
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def generate_next_without_games
|
163
|
+
sd_pre = Math.sqrt(sd ** 2 + volatility ** 2)
|
164
|
+
self.class.new(mean, sd_pre, volatility, obj)
|
165
|
+
end
|
166
|
+
|
167
|
+
def generate_next_with_games(others, scores)
|
168
|
+
_v = variance(others)
|
169
|
+
_d = delta(others, scores)
|
170
|
+
_volatility = volatility1(_d, _v)
|
171
|
+
sd_pre = Math.sqrt(sd ** 2 + _volatility ** 2)
|
172
|
+
_sd = 1 / Math.sqrt(1 / sd_pre ** 2 + 1 / _v)
|
173
|
+
_mean = mean + _sd ** 2 * others.zip(scores).reduce(0) {
|
174
|
+
|x, (other, score)| x + other.g * (score - e(other))
|
175
|
+
}
|
176
|
+
self.class.new(_mean, _sd, _volatility, obj)
|
177
|
+
end
|
178
|
+
|
179
|
+
def f_part1(x, d, v)
|
180
|
+
exp_x = Math.exp(x)
|
181
|
+
sd_sq = sd ** 2
|
182
|
+
(exp_x * (d ** 2 - sd_sq - v - exp_x)) / (2 * (sd_sq + v + exp_x) ** 2)
|
183
|
+
end
|
184
|
+
|
185
|
+
def f_part2(x)
|
186
|
+
(x - Math::log(volatility ** 2)) / VOLATILITY_CHANGE ** 2
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Glicko2
|
2
|
+
# Glicko ratings are calculated in bulk at the end of arbitrary, but fixed
|
3
|
+
# length, periods named rating periods. Where a period is fixed to be long
|
4
|
+
# enough that the average number of games that each player has played in is
|
5
|
+
# about 5 to 10 games. It could be weekly, monthly or more as required.
|
6
|
+
class RatingPeriod
|
7
|
+
attr_reader :players
|
8
|
+
|
9
|
+
# @param [Array<Player>] players
|
10
|
+
def initialize(players)
|
11
|
+
@players = players
|
12
|
+
@games = Hash.new { |h, k| h[k] = [] }
|
13
|
+
@cache = players.reduce({}) { |memo, player| memo[player.obj] = player; memo }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Create rating period from list of seed objects
|
17
|
+
#
|
18
|
+
# @param [Array<#rating,#rating_deviation,#volatility>] objs seed value objects
|
19
|
+
# @return [RatingPeriod]
|
20
|
+
def self.from_objs(objs)
|
21
|
+
new(objs.map { |obj| Player.from_obj(obj) })
|
22
|
+
end
|
23
|
+
|
24
|
+
# Register a game with this rating period
|
25
|
+
#
|
26
|
+
# @param [Array<#rating,#rating_deviation,#volatility>] game_seeds ratings participating in a game
|
27
|
+
# @param [Array<Integer>] ranks corresponding ranks
|
28
|
+
def game(game_seeds, ranks)
|
29
|
+
game_seeds.zip(ranks).each do |seed, rank|
|
30
|
+
game_seeds.zip(ranks).each do |other, other_rank|
|
31
|
+
next if seed == other
|
32
|
+
@games[player(seed)] << [player(other),
|
33
|
+
Util.ranks_to_score(rank, other_rank)]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generate a new {RatingPeriod} with a new list of updated {Player}
|
39
|
+
#
|
40
|
+
# @return [RatingPeriod]
|
41
|
+
def generate_next
|
42
|
+
p = []
|
43
|
+
@players.each do |player|
|
44
|
+
games = @games[player]
|
45
|
+
if games.length > 0
|
46
|
+
p << player.generate_next(*games.transpose)
|
47
|
+
else
|
48
|
+
p << player.generate_next([], [])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
self.class.new(p)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Fetch the player associated with a seed object
|
55
|
+
#
|
56
|
+
# @param [#rating,#rating_deviation,#volatility] obj seed object
|
57
|
+
# @return [Player]
|
58
|
+
def player(obj)
|
59
|
+
@cache[obj]
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
"#<RatingPeriod players=#{@players}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/glicko2/version.rb
CHANGED
data/spec/minitest_helper.rb
CHANGED
data/spec/player_spec.rb
CHANGED
@@ -92,6 +92,10 @@ describe Glicko2::Player do
|
|
92
92
|
p.volatility.must_equal @player.volatility
|
93
93
|
p.sd.must_be_close_to Math.sqrt(@player.sd ** 2 + @player.volatility ** 2)
|
94
94
|
end
|
95
|
+
|
96
|
+
bench_performance_linear "default" do |n|
|
97
|
+
@player.generate_next(@others * n, @scores * n)
|
98
|
+
end
|
95
99
|
end
|
96
100
|
|
97
101
|
describe "#update_obj" do
|
data/spec/rating_period_spec.rb
CHANGED
@@ -2,47 +2,36 @@ require 'minitest_helper'
|
|
2
2
|
|
3
3
|
describe Glicko2::RatingPeriod do
|
4
4
|
before do
|
5
|
-
@player =
|
6
|
-
@player1 =
|
7
|
-
@player2 =
|
8
|
-
@player3 =
|
5
|
+
@player = Rating.new(1500, 200, 0.06)
|
6
|
+
@player1 = Rating.new(1400, 30, 0.06)
|
7
|
+
@player2 = Rating.new(1550, 100, 0.06)
|
8
|
+
@player3 = Rating.new(1700, 300, 0.06)
|
9
9
|
@players = [@player, @player1, @player2, @player3]
|
10
|
-
@period = Glicko2::RatingPeriod.
|
10
|
+
@period = Glicko2::RatingPeriod.from_objs(@players)
|
11
11
|
end
|
12
12
|
|
13
|
-
describe "
|
14
|
-
it "must assign players" do
|
15
|
-
@period.players.must_include @player
|
16
|
-
@period.players.must_include @player1
|
17
|
-
@period.players.must_include @player2
|
18
|
-
@period.players.must_include @player3
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
describe ".ranks_to_score" do
|
23
|
-
it "must return 1.0 when rank is less" do
|
24
|
-
Glicko2::RatingPeriod.ranks_to_score(1, 2).must_equal 1.0
|
25
|
-
end
|
26
|
-
|
27
|
-
it "must return 0.5 when rank is equal" do
|
28
|
-
Glicko2::RatingPeriod.ranks_to_score(1, 1).must_equal 0.5
|
29
|
-
end
|
30
|
-
|
31
|
-
it "must return 0.0 when rank is more" do
|
32
|
-
Glicko2::RatingPeriod.ranks_to_score(2, 1).must_equal 0.0
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
describe "complete rating period" do
|
13
|
+
describe "#generate_next" do
|
37
14
|
it "must be close to example" do
|
38
15
|
@period.game([@player, @player1], [1, 2])
|
39
16
|
@period.game([@player, @player2], [2, 1])
|
40
17
|
@period.game([@player, @player3], [2, 1])
|
41
18
|
@period.generate_next.players.each { |p| p.update_obj }
|
42
|
-
obj = @player
|
19
|
+
obj = @player
|
43
20
|
obj.rating.must_be_close_to 1464.06, 0.01
|
44
21
|
obj.rating_deviation.must_be_close_to 151.52, 0.01
|
45
22
|
obj.volatility.must_be_close_to 0.05999, 0.00001
|
46
23
|
end
|
24
|
+
|
25
|
+
it "must process non-competing players" do
|
26
|
+
@period.game([@player, @player1], [1, 2])
|
27
|
+
@period.generate_next
|
28
|
+
end
|
29
|
+
|
30
|
+
bench_performance_linear "default" do |n|
|
31
|
+
n.times do
|
32
|
+
@period.game(@players.sample(2), [1, 2])
|
33
|
+
end
|
34
|
+
@period.generate_next
|
35
|
+
end
|
47
36
|
end
|
48
37
|
end
|
data/spec/util_spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
describe Glicko2::Util do
|
4
|
+
describe ".ranks_to_score" do
|
5
|
+
it "must return 1.0 when rank is less" do
|
6
|
+
Glicko2::Util.ranks_to_score(1, 2).must_equal 1.0
|
7
|
+
end
|
8
|
+
|
9
|
+
it "must return 0.5 when rank is equal" do
|
10
|
+
Glicko2::Util.ranks_to_score(1, 1).must_equal 0.5
|
11
|
+
end
|
12
|
+
|
13
|
+
it "must return 0.0 when rank is more" do
|
14
|
+
Glicko2::Util.ranks_to_score(2, 1).must_equal 0.0
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: glicko2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -41,10 +41,13 @@ files:
|
|
41
41
|
- Rakefile
|
42
42
|
- glicko2.gemspec
|
43
43
|
- lib/glicko2.rb
|
44
|
+
- lib/glicko2/player.rb
|
45
|
+
- lib/glicko2/rating_period.rb
|
44
46
|
- lib/glicko2/version.rb
|
45
47
|
- spec/minitest_helper.rb
|
46
48
|
- spec/player_spec.rb
|
47
49
|
- spec/rating_period_spec.rb
|
50
|
+
- spec/util_spec.rb
|
48
51
|
homepage: https://github.com/proglottis/glicko2
|
49
52
|
licenses: []
|
50
53
|
post_install_message:
|
@@ -73,3 +76,5 @@ test_files:
|
|
73
76
|
- spec/minitest_helper.rb
|
74
77
|
- spec/player_spec.rb
|
75
78
|
- spec/rating_period_spec.rb
|
79
|
+
- spec/util_spec.rb
|
80
|
+
has_rdoc:
|