elo_rating 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/README.md +63 -26
- data/Rakefile +4 -0
- data/lib/elo_rating/match.rb +5 -3
- data/lib/elo_rating/version.rb +1 -1
- data/lib/elo_rating.rb +1 -1
- data/spec/match_spec.rb +14 -0
- metadata +1 -3
- data/Gemfile.lock +0 -63
- data/Guardfile +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72c73870b99ccb9fa0eb8d5ada07c4104af74f33
|
4
|
+
data.tar.gz: f60fde89bad3ca68a36baf083dca5fa112da4a57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 059f1564f4fbbc9064660f6ae6be567faed7e3c0787f3340a62718923b4b9f224e69cb6bc8f5281d0834c7559490aa16370f0d45c2119d347fb37bca8eb893a3
|
7
|
+
data.tar.gz: f2c8fea968c72d13c49f00e77fb7a3934c3afe2f3c05d714ace015f7fbf5c3c113447772ef67cff9c3f92330d53d3cac4ce4dd172d0404349991c7f7a794595b
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# elo_rating
|
2
2
|
|
3
|
-
`elo_rating` helps you calculate [Elo ratings](https://en.wikipedia.org/wiki/Elo_rating_system), a rating system used primary for Chess.
|
3
|
+
`elo_rating` helps you calculate [Elo ratings](https://en.wikipedia.org/wiki/Elo_rating_system), a rating system used primary for Chess, but can be used anywhere you want to determine an absolute ordering of things by doing many comparisons of a small number of things.
|
4
4
|
|
5
5
|
It can handle multiple players in one game and allows for custom K-factor functions.
|
6
6
|
|
@@ -20,9 +20,11 @@ gem 'elo_rating'
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
Say you have two players, both
|
24
|
-
determine both player's updated ratings:
|
23
|
+
Say you have two players playing against each other in a match, both with initial ratings of 2000.
|
25
24
|
|
25
|
+
The second player wins.
|
26
|
+
|
27
|
+
To determine both player's updated ratings:
|
26
28
|
|
27
29
|
```ruby
|
28
30
|
match = EloRating::Match.new
|
@@ -31,13 +33,19 @@ match.add_player(rating: 2000, winner: true)
|
|
31
33
|
match.updated_ratings # => [1988, 2012]
|
32
34
|
```
|
33
35
|
|
34
|
-
|
36
|
+
This tells us that the first player's rating should go down 12 points and the second player's should go up 12 points.
|
37
|
+
|
38
|
+
You can chain the same function calls to achieve the same result:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
EloRating::Match.new.add_player(rating: 2000).add_player(rating: 2000, winner: true).updated_ratings # => [1988, 2012]
|
42
|
+
```
|
35
43
|
|
36
44
|
### >2 players
|
37
45
|
|
38
|
-
Most Elo rating calculators only allow for matches of just 2 players, but the
|
39
|
-
|
40
|
-
|
46
|
+
Most Elo rating calculators only allow for matches of just 2 players, but the formula can be extended to games of any number of players.
|
47
|
+
|
48
|
+
We can do this by combining the rating adjustments for each pairing of players into one big adjustment.
|
41
49
|
|
42
50
|
Say you have three players, rated 1900, 2000, and 2000. They are playing a game
|
43
51
|
like [Monopoly](https://en.wikipedia.org/wiki/Monopoly_(game)) where there is
|
@@ -52,21 +60,26 @@ match.add_player(rating: 2000)
|
|
52
60
|
match.updated_ratings # => [1931, 1985, 1985]
|
53
61
|
```
|
54
62
|
|
63
|
+
This is calculated as if the first player beat both of the other players and the other two players tied.
|
64
|
+
|
55
65
|
### Ranked games
|
56
66
|
|
57
|
-
Some games like [Mario Kart](https://en.wikipedia.org/wiki/Mario_Kart) have
|
58
|
-
|
59
|
-
1900, 2000, and 2000
|
60
|
-
|
67
|
+
Some games like [Mario Kart](https://en.wikipedia.org/wiki/Mario_Kart) have multiple, ranked winners.
|
68
|
+
|
69
|
+
Let's say you have three players like before, rated 1900, 2000, and 2000, who came in first place, second place, and third place respectively.
|
70
|
+
|
71
|
+
Instead of indicating the winner, you can specify their places:
|
61
72
|
|
62
73
|
```ruby
|
63
74
|
match = EloRating::Match.new
|
64
|
-
match.add_player(rating: 1900,
|
65
|
-
match.add_player(rating: 2000,
|
66
|
-
match.add_player(rating: 2000,
|
75
|
+
match.add_player(rating: 1900, place: 1)
|
76
|
+
match.add_player(rating: 2000, place: 2)
|
77
|
+
match.add_player(rating: 2000, place: 3)
|
67
78
|
match.updated_ratings # => [1973, 1997, 1931]
|
68
79
|
```
|
69
80
|
|
81
|
+
This is calculated as if the first player beat both of the other players and the second player beat the third.
|
82
|
+
|
70
83
|
## Elo rating functions
|
71
84
|
|
72
85
|
The functions used in the above calculations are available for use directly:
|
@@ -83,10 +96,11 @@ The player rated 1900 has a 36% chance of winning.
|
|
83
96
|
|
84
97
|
### Rating adjustment
|
85
98
|
|
86
|
-
You can use the expected score and the results of an actual match to determine
|
87
|
-
|
99
|
+
You can use the expected score and the results of an actual match to determine how an Elo rating should change.
|
100
|
+
|
101
|
+
The `EloRating.rating_adjustment` function takes an expected score and an actual score and returns how much a rating should go up or down.
|
88
102
|
|
89
|
-
Let's say we have the expected rating from above of 0.36
|
103
|
+
Let's say we have the expected rating from above of 0.36 and the first player rated 1900 won the match, making their actual score 1.
|
90
104
|
|
91
105
|
We can use this to determine how much their rating should change:
|
92
106
|
|
@@ -94,7 +108,7 @@ We can use this to determine how much their rating should change:
|
|
94
108
|
EloRating.rating_adjustment(0.36, 1) # => 15.36
|
95
109
|
```
|
96
110
|
|
97
|
-
|
111
|
+
This means their rating should now be 1915.
|
98
112
|
|
99
113
|
## K-factor
|
100
114
|
|
@@ -106,18 +120,27 @@ It defaults to 24:
|
|
106
120
|
EloRating::k_factor # => 24
|
107
121
|
```
|
108
122
|
|
109
|
-
You can change this to any number. With a lower K-factor, ratings are less volatile and change slower:
|
123
|
+
You can change this to any number. With a lower K-factor, ratings are less volatile and change slower. Compare:
|
110
124
|
|
111
125
|
```ruby
|
112
126
|
EloRating::k_factor = 10
|
113
127
|
match = EloRating::Match.new
|
114
128
|
match.add_player(rating: 2000, winner: true)
|
115
129
|
match.add_player(rating: 2000)
|
116
|
-
match.updated_ratings # => [
|
130
|
+
match.updated_ratings # => [2005, 1995]
|
131
|
+
```
|
132
|
+
|
133
|
+
to:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
EloRating::k_factor = 20
|
137
|
+
match = EloRating::Match.new
|
138
|
+
match.add_player(rating: 2000, winner: true)
|
139
|
+
match.add_player(rating: 2000)
|
140
|
+
match.updated_ratings # => [2010, 1990]
|
117
141
|
```
|
118
142
|
|
119
|
-
You can also pass a block to provide a custom function to calculate the K-factor
|
120
|
-
based on the player's rating:
|
143
|
+
You can also pass a block to provide a custom function to calculate the K-factor based on the player's rating:
|
121
144
|
|
122
145
|
```ruby
|
123
146
|
EloRating::set_k_factor do |rating|
|
@@ -132,8 +155,7 @@ EloRating::set_k_factor do |rating|
|
|
132
155
|
end
|
133
156
|
```
|
134
157
|
|
135
|
-
|
136
|
-
your custom K-factor function:
|
158
|
+
Then you can provide a rating to `EloRating.rating_adjustment` that will be used in your custom K-factor function:
|
137
159
|
|
138
160
|
```ruby
|
139
161
|
EloRating.rating_adjustment(0.75, 0) # => -24.0
|
@@ -141,13 +163,13 @@ EloRating.rating_adjustment(0.75, 0, rating: 2200) # => -18.0
|
|
141
163
|
EloRating.rating_adjustment(0.75, 0, rating: 2500) # => -12.0
|
142
164
|
```
|
143
165
|
|
144
|
-
You can also specify a K-factor for a single rating adjustment:
|
166
|
+
You can also just specify a K-factor directly for a single rating adjustment:
|
145
167
|
|
146
168
|
```ruby
|
147
169
|
EloRating.rating_adjustment(0.75, 0, k_factor: 24) # => -18.0
|
148
170
|
```
|
149
171
|
|
150
|
-
Note
|
172
|
+
*Note*: custom K-factor functions must not raise any exceptions when the rating is nil:
|
151
173
|
|
152
174
|
```ruby
|
153
175
|
EloRating::set_k_factor do |rating|
|
@@ -155,3 +177,18 @@ EloRating::set_k_factor do |rating|
|
|
155
177
|
end
|
156
178
|
# => ArgumentError: Error encountered in K-factor block when passed nil rating: undefined method `/' for nil:NilClass
|
157
179
|
```
|
180
|
+
|
181
|
+
## Thanks
|
182
|
+
|
183
|
+
Thanks to:
|
184
|
+
|
185
|
+
* Divergent Informatics for their [multiplayer Elo
|
186
|
+
calculator](http://elo.divergentinformatics.com/) used to verify calculations used in the development of this gem
|
187
|
+
* [Ian Hecker](https://github.com/iain) for the original [Elo](https://github.com/iain/elo).
|
188
|
+
|
189
|
+
## License
|
190
|
+
|
191
|
+
Copyright © 2014 Maxwell Holder.
|
192
|
+
|
193
|
+
It is free software, and may be redistributed under the terms specified in the
|
194
|
+
LICENSE file.
|
data/Rakefile
CHANGED
data/lib/elo_rating/match.rb
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
# This class represents a single game between a number of players.
|
3
3
|
class EloRating::Match
|
4
4
|
|
5
|
+
# All the players of the match.
|
5
6
|
attr_reader :players
|
6
7
|
|
7
|
-
# Creates a new match
|
8
|
+
# Creates a new match with no players.
|
8
9
|
def initialize
|
9
10
|
@players = []
|
10
11
|
end
|
11
12
|
|
12
13
|
# Adds a player to the match
|
13
|
-
#
|
14
|
+
#
|
14
15
|
# ==== Attributes
|
15
16
|
# * +rating+: the Elo rating of the player
|
16
17
|
# * +winner+ (optional): boolean, whether this player is the winner of the match
|
@@ -86,7 +87,8 @@ class EloRating::Match
|
|
86
87
|
def rating_adjustment_against(opponent)
|
87
88
|
EloRating.rating_adjustment(
|
88
89
|
expected_score_against(opponent),
|
89
|
-
actual_score_against(opponent)
|
90
|
+
actual_score_against(opponent),
|
91
|
+
rating: rating
|
90
92
|
)
|
91
93
|
end
|
92
94
|
|
data/lib/elo_rating/version.rb
CHANGED
data/lib/elo_rating.rb
CHANGED
@@ -29,7 +29,7 @@ module EloRating
|
|
29
29
|
# end
|
30
30
|
# end
|
31
31
|
#
|
32
|
-
# Raises an ArgumentError if an exception is encountered when calling the provided block with nil as the argument
|
32
|
+
# Raises an +ArgumentError+ if an exception is encountered when calling the provided block with nil as the argument
|
33
33
|
def self.set_k_factor(&k_factor)
|
34
34
|
k_factor.call(nil)
|
35
35
|
@k_factor = k_factor
|
data/spec/match_spec.rb
CHANGED
@@ -28,6 +28,20 @@ describe EloRating::Match do
|
|
28
28
|
expect(match.updated_ratings).to eql [1931, 1997, 1973]
|
29
29
|
end
|
30
30
|
end
|
31
|
+
context 'custom K-factor function' do
|
32
|
+
it 'uses the custom K-factor function' do
|
33
|
+
EloRating::set_k_factor do |rating|
|
34
|
+
rating || 0
|
35
|
+
end
|
36
|
+
|
37
|
+
match = EloRating::Match.new
|
38
|
+
match.add_player(rating: 2000)
|
39
|
+
match.add_player(rating: 2000, winner: true)
|
40
|
+
expect(match.updated_ratings).not_to eql [2000, 2000]
|
41
|
+
|
42
|
+
EloRating::k_factor = 24
|
43
|
+
end
|
44
|
+
end
|
31
45
|
context 'multiple winners specified' do
|
32
46
|
it 'raises an error' do
|
33
47
|
match = EloRating::Match.new
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elo_rating
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max Holder
|
@@ -90,8 +90,6 @@ extra_rdoc_files: []
|
|
90
90
|
files:
|
91
91
|
- ".gitignore"
|
92
92
|
- Gemfile
|
93
|
-
- Gemfile.lock
|
94
|
-
- Guardfile
|
95
93
|
- LICENSE
|
96
94
|
- README.md
|
97
95
|
- Rakefile
|
data/Gemfile.lock
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
elo_rating (0.3.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
celluloid (0.15.2)
|
10
|
-
timers (~> 1.1.0)
|
11
|
-
coderay (1.1.0)
|
12
|
-
diff-lcs (1.2.5)
|
13
|
-
ffi (1.9.3)
|
14
|
-
formatador (0.2.5)
|
15
|
-
guard (2.6.1)
|
16
|
-
formatador (>= 0.2.4)
|
17
|
-
listen (~> 2.7)
|
18
|
-
lumberjack (~> 1.0)
|
19
|
-
pry (>= 0.9.12)
|
20
|
-
thor (>= 0.18.1)
|
21
|
-
guard-rspec (4.2.10)
|
22
|
-
guard (~> 2.1)
|
23
|
-
rspec (>= 2.14, < 4.0)
|
24
|
-
listen (2.7.9)
|
25
|
-
celluloid (>= 0.15.2)
|
26
|
-
rb-fsevent (>= 0.9.3)
|
27
|
-
rb-inotify (>= 0.9)
|
28
|
-
lumberjack (1.0.7)
|
29
|
-
method_source (0.8.2)
|
30
|
-
pry (0.10.0)
|
31
|
-
coderay (~> 1.1.0)
|
32
|
-
method_source (~> 0.8.1)
|
33
|
-
slop (~> 3.4)
|
34
|
-
rake (10.3.1)
|
35
|
-
rb-fsevent (0.9.4)
|
36
|
-
rb-inotify (0.9.5)
|
37
|
-
ffi (>= 0.5.0)
|
38
|
-
rspec (3.0.0)
|
39
|
-
rspec-core (~> 3.0.0)
|
40
|
-
rspec-expectations (~> 3.0.0)
|
41
|
-
rspec-mocks (~> 3.0.0)
|
42
|
-
rspec-core (3.0.2)
|
43
|
-
rspec-support (~> 3.0.0)
|
44
|
-
rspec-expectations (3.0.2)
|
45
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
46
|
-
rspec-support (~> 3.0.0)
|
47
|
-
rspec-mocks (3.0.2)
|
48
|
-
rspec-support (~> 3.0.0)
|
49
|
-
rspec-support (3.0.2)
|
50
|
-
slop (3.5.0)
|
51
|
-
thor (0.19.1)
|
52
|
-
timers (1.1.0)
|
53
|
-
|
54
|
-
PLATFORMS
|
55
|
-
ruby
|
56
|
-
|
57
|
-
DEPENDENCIES
|
58
|
-
bundler (~> 1.6)
|
59
|
-
elo_rating!
|
60
|
-
guard
|
61
|
-
guard-rspec
|
62
|
-
rake
|
63
|
-
rspec
|
data/Guardfile
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
# A sample Guardfile
|
2
|
-
# More info at https://github.com/guard/guard#readme
|
3
|
-
|
4
|
-
guard :rspec do
|
5
|
-
watch(%r{^spec/.+_spec\.rb$})
|
6
|
-
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
-
watch('spec/spec_helper.rb') { "spec" }
|
8
|
-
|
9
|
-
# Rails example
|
10
|
-
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
11
|
-
watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
-
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
13
|
-
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
14
|
-
watch('config/routes.rb') { "spec/routing" }
|
15
|
-
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
16
|
-
|
17
|
-
# Capybara features specs
|
18
|
-
watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
|
19
|
-
|
20
|
-
# Turnip features and steps
|
21
|
-
watch(%r{^spec/acceptance/(.+)\.feature$})
|
22
|
-
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
23
|
-
end
|
24
|
-
|