elo 0.0.2.alpha → 0.0.3.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +12 -11
- data/VERSION +1 -1
- data/elo.gemspec +73 -0
- data/lib/elo.rb +18 -290
- data/lib/elo/configuration.rb +83 -0
- data/lib/elo/game.rb +92 -0
- data/lib/elo/helper.rb +29 -0
- data/lib/elo/player.rb +110 -0
- data/lib/elo/rating.rb +59 -0
- data/spec/elo_spec.rb +143 -2
- metadata +8 -2
data/README.rdoc
CHANGED
@@ -45,16 +45,16 @@ There is more than one way to do it, choose whatever works from your other code:
|
|
45
45
|
|
46
46
|
You can get all kinds of info from a player:
|
47
47
|
|
48
|
-
bob.rating
|
49
|
-
bob.pro?
|
50
|
-
bob.starter?
|
51
|
-
bob.
|
52
|
-
bob.
|
53
|
-
bob.games
|
48
|
+
bob.rating # => 1084
|
49
|
+
bob.pro? # => false
|
50
|
+
bob.starter? # => true
|
51
|
+
bob.games_played # => 7
|
52
|
+
bob.games # => [ game1, game2, ... game7 ]
|
54
53
|
|
55
54
|
Or get a list of players:
|
56
55
|
|
57
|
-
Elo::Player.all
|
56
|
+
Elo::Player.all # => [ bob, jane ]
|
57
|
+
Elo::Game.all # => [ game1, game2, ... game7 ]
|
58
58
|
|
59
59
|
|
60
60
|
== Configuration
|
@@ -91,14 +91,15 @@ Once you reach a pro status, you're K-factor never changes, even if your rating
|
|
91
91
|
|
92
92
|
You need to provide Elo the amount of games played, their rating and their pro-status.
|
93
93
|
|
94
|
-
bob = Elo::Player.new(:games_played => 29, :rating =>
|
94
|
+
bob = Elo::Player.new(:games_played => 29, :rating => 2399, :pro => true)
|
95
|
+
bob.k_factor == 10
|
95
96
|
|
96
97
|
You can define your own K-factors by adding K-factor rules.
|
97
|
-
This code will change the K-factor to 12, for every player
|
98
|
-
and 16 for everybody else.
|
98
|
+
This code will change the K-factor to 12, for every player that played less than 10
|
99
|
+
games, and 16 for everybody else.
|
99
100
|
|
100
101
|
Elo.configure do |config|
|
101
|
-
config.k_factor(12) {
|
102
|
+
config.k_factor(12) { games_played < 10 }
|
102
103
|
config.default_k_factor = 16
|
103
104
|
config.use_FIDE_settings = false
|
104
105
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3.alpha
|
data/elo.gemspec
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{elo}
|
8
|
+
s.version = "0.0.3.alpha"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Iain Hecker"]
|
12
|
+
s.date = %q{2010-03-15}
|
13
|
+
s.description = %q{The Elo rating system is a method for calculating the relative skill levels of players in two-player games such as cess and Go.}
|
14
|
+
s.email = %q{iain@iain.nl}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
".gitignore",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"doc/classes/Elo.html",
|
25
|
+
"doc/classes/Elo/Configuration.html",
|
26
|
+
"doc/classes/Elo/EloHelper.html",
|
27
|
+
"doc/classes/Elo/EloHelper/ClassMethods.html",
|
28
|
+
"doc/classes/Elo/Game.html",
|
29
|
+
"doc/classes/Elo/Player.html",
|
30
|
+
"doc/classes/Elo/Rating.html",
|
31
|
+
"doc/created.rid",
|
32
|
+
"doc/files/README_rdoc.html",
|
33
|
+
"doc/files/lib/elo_rb.html",
|
34
|
+
"doc/fr_class_index.html",
|
35
|
+
"doc/fr_file_index.html",
|
36
|
+
"doc/fr_method_index.html",
|
37
|
+
"doc/index.html",
|
38
|
+
"doc/rdoc-style.css",
|
39
|
+
"elo.gemspec",
|
40
|
+
"lib/elo.rb",
|
41
|
+
"lib/elo/configuration.rb",
|
42
|
+
"lib/elo/game.rb",
|
43
|
+
"lib/elo/helper.rb",
|
44
|
+
"lib/elo/player.rb",
|
45
|
+
"lib/elo/rating.rb",
|
46
|
+
"spec/elo_spec.rb",
|
47
|
+
"spec/spec.opts",
|
48
|
+
"spec/spec_helper.rb"
|
49
|
+
]
|
50
|
+
s.homepage = %q{http://github.com/iain/elo}
|
51
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubygems_version = %q{1.3.6}
|
54
|
+
s.summary = %q{The Elo rating system is a method for calculating the relative skill levels of players in two-player games such as cess and Go.}
|
55
|
+
s.test_files = [
|
56
|
+
"spec/elo_spec.rb",
|
57
|
+
"spec/spec_helper.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
68
|
+
end
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
data/lib/elo.rb
CHANGED
@@ -1,298 +1,26 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'elo/helper'
|
2
|
+
require 'elo/configuration'
|
3
|
+
require 'elo/game'
|
4
|
+
require 'elo/player'
|
5
|
+
require 'elo/rating'
|
6
|
+
|
3
7
|
# See README.rdoc for general information about Elo.
|
4
8
|
module Elo
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@use_FIDE_settings = true
|
11
|
-
|
12
|
-
module Configuration
|
13
|
-
|
14
|
-
# Add a K-factor rule. The first argument is the k-factor value.
|
15
|
-
# The block should return a boolean that determines if this K-factor rule applies.
|
16
|
-
# The first rule that applies is the one determining the K-factor.
|
17
|
-
#
|
18
|
-
# The block is instance_eval'ed into the player, so you can access all it's
|
19
|
-
# properties directly. The K-factor is recalculated every time a match is played.
|
20
|
-
#
|
21
|
-
# By default, the FIDE settings are used (see: +use_FIDE_settings+). To implement
|
22
|
-
# that yourself, you could write:
|
23
|
-
#
|
24
|
-
# Elo.configure do |config|
|
25
|
-
# config.k_factor(10) { pro? or pro_rating? }
|
26
|
-
# config.k_factor(25) { starter? }
|
27
|
-
# config.default_k_factor = 15
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
def k_factor(factor, &rule)
|
31
|
-
k_factors << { :factor => factor, :rule => rule }
|
32
|
-
end
|
33
|
-
|
34
|
-
# This is the lower boundry of the rating you need to be a pro player.
|
35
|
-
# This setting is used in the FIDE k-factor rules. (default = 2400)
|
36
|
-
attr_accessor :pro_rating_boundry
|
37
|
-
|
38
|
-
# This is the lower boundry in the amount of games played to be a starting player
|
39
|
-
# This setting is used in the FIDE k-factor rules. (default = 30)
|
40
|
-
attr_accessor :starter_boundry
|
41
|
-
|
42
|
-
# The default k-factor is chosen when no k-factor rules apply.
|
43
|
-
# K-factor rules can be added by using the +k_factor+-method. (default = 15)
|
44
|
-
attr_accessor :default_k_factor
|
45
|
-
|
46
|
-
# This is the rating every player starts out with. (default = 1000)
|
47
|
-
attr_accessor :default_rating
|
48
|
-
|
49
|
-
# Use the settings that FIDE use for determening the K-factor.
|
50
|
-
# This is the case when all settings are unaltered. (default = true)
|
51
|
-
#
|
52
|
-
# In short:
|
53
|
-
#
|
54
|
-
# * K-factor is 25 when a player is a starter (less than 30 games played)
|
55
|
-
# * K-factor is 10 when a player is a pro (rating above 2400, now or in the past)
|
56
|
-
# * K-factor is 15 when a player in other cases
|
57
|
-
#
|
58
|
-
# If you want to use your own settings, either change the boundry settings,
|
59
|
-
# or set this setting to false and add you're own k-factor rules.
|
60
|
-
# K-factor rules can be added by using the +k_factor+-method.
|
61
|
-
attr_accessor :use_FIDE_settings
|
62
|
-
|
63
|
-
# Configure Elo in a block style.
|
64
|
-
#
|
65
|
-
# Elo.configure do |config|
|
66
|
-
# config.setting = value
|
67
|
-
# end
|
68
|
-
def configure(&block)
|
69
|
-
yield(self)
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
def k_factors
|
75
|
-
@k_factors ||= []
|
76
|
-
end
|
77
|
-
|
78
|
-
def applied_k_factors
|
79
|
-
apply_fide_k_factors if use_FIDE_settings
|
80
|
-
k_factors
|
81
|
-
end
|
82
|
-
|
83
|
-
def apply_fide_k_factors
|
84
|
-
unless @applied_fide_k_factors
|
85
|
-
k_factor(10) { pro? or pro_rating? }
|
86
|
-
k_factor(25) { starter? }
|
87
|
-
@applied_fide_k_factors = true
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
10
|
+
# Accessor to the configuration object, which,
|
11
|
+
# should be instantiated only once (and automatically).
|
12
|
+
def self.config
|
13
|
+
@config ||= Configuration.new
|
91
14
|
end
|
92
15
|
|
93
|
-
|
94
|
-
|
95
|
-
#
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
# Every object can be initialized with a hash, just like in ActiveRecord.
|
103
|
-
def initialize(attributes)
|
104
|
-
@attributes == attributes.keys
|
105
|
-
attributes.each do |key, value|
|
106
|
-
self.class.attr_reader key
|
107
|
-
instance_variable_set("@#{key}", value)
|
108
|
-
end
|
109
|
-
self.class.all << self
|
110
|
-
end
|
111
|
-
|
112
|
-
# Get a hash of all attributes provided
|
113
|
-
def attributes
|
114
|
-
hash = {}
|
115
|
-
@attributes.each do |attribute|
|
116
|
-
hash.update attribute => send(attribute)
|
117
|
-
end
|
118
|
-
hash
|
119
|
-
end
|
120
|
-
|
121
|
-
module ClassMethods
|
122
|
-
|
123
|
-
# Provides a list of all instantiated objects of the class.
|
124
|
-
def all
|
125
|
-
@all ||= []
|
126
|
-
end
|
127
|
-
|
128
|
-
end
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
|
-
# A player. You need at least two play a Game.
|
133
|
-
class Player
|
134
|
-
|
135
|
-
include EloHelper
|
136
|
-
|
137
|
-
# Rating
|
138
|
-
def rating
|
139
|
-
@rating ||= Elo.default_rating
|
140
|
-
end
|
141
|
-
|
142
|
-
def games_played
|
143
|
-
@games_played ||= games.size
|
144
|
-
end
|
145
|
-
|
146
|
-
def games
|
147
|
-
@games ||= []
|
148
|
-
end
|
149
|
-
|
150
|
-
def pro_rating?
|
151
|
-
rating > Elo.pro_rating_boundry
|
152
|
-
end
|
153
|
-
|
154
|
-
def starter?
|
155
|
-
games_played < Elo.starter_boundry
|
156
|
-
end
|
157
|
-
|
158
|
-
def pro?
|
159
|
-
!!@pro
|
160
|
-
end
|
161
|
-
|
162
|
-
# TODO
|
163
|
-
def save
|
164
|
-
# hook for your own model
|
165
|
-
# which I don't know yet how to do
|
166
|
-
end
|
167
|
-
|
168
|
-
def k_factor
|
169
|
-
Elo.applied_k_factors.each do |rule|
|
170
|
-
return rule[:factor] if instance_eval(&rule[:rule])
|
171
|
-
end
|
172
|
-
Elo.default_k_factor
|
173
|
-
end
|
174
|
-
|
175
|
-
def versus(other_player)
|
176
|
-
Game.new(:one => self, :two => other_player)
|
177
|
-
end
|
178
|
-
|
179
|
-
def wins_from(other_player)
|
180
|
-
versus(other_player).win
|
181
|
-
end
|
182
|
-
|
183
|
-
def plays_draw(other_player)
|
184
|
-
versus(other_player).draw
|
185
|
-
end
|
186
|
-
|
187
|
-
def loses_from(other_player)
|
188
|
-
versus(other_player).lose
|
189
|
-
end
|
190
|
-
|
191
|
-
private
|
192
|
-
|
193
|
-
# A Game tells the players informed to update their
|
194
|
-
# scores, after it knows the result (so it can calculate the rating).
|
195
|
-
#
|
196
|
-
# This method is private, because it is called automatically.
|
197
|
-
# Therefore it is not part of the public API of Elo.
|
198
|
-
def played(game)
|
199
|
-
games_played += 1
|
200
|
-
games << game
|
201
|
-
@rating = game.new_rating(self)
|
202
|
-
@pro = true if pro_rating?
|
203
|
-
save
|
204
|
-
end
|
205
|
-
|
206
|
-
|
207
|
-
end
|
208
|
-
|
209
|
-
class Game
|
210
|
-
|
211
|
-
include EloHelper
|
212
|
-
|
213
|
-
# Result is from the perspective of player one.
|
214
|
-
def result=(result)
|
215
|
-
@result = result
|
216
|
-
one.send(:played, self)
|
217
|
-
two.send(:played, self)
|
218
|
-
save
|
219
|
-
end
|
220
|
-
|
221
|
-
def win
|
222
|
-
self.result = 1.0
|
223
|
-
end
|
224
|
-
|
225
|
-
def lose
|
226
|
-
self.result = 0.0
|
227
|
-
end
|
228
|
-
|
229
|
-
def draw
|
230
|
-
self.result = 0.5
|
231
|
-
end
|
232
|
-
|
233
|
-
# TODO
|
234
|
-
def save
|
235
|
-
end
|
236
|
-
|
237
|
-
def winner=(player)
|
238
|
-
self.result = (player == :one ? 1.0 : 0.0)
|
239
|
-
end
|
240
|
-
|
241
|
-
def loser=(player)
|
242
|
-
self.result = (player == :one ? 0.0 : 1.0)
|
243
|
-
end
|
244
|
-
|
245
|
-
def new_rating(player)
|
246
|
-
ratings[player].new_rating
|
247
|
-
end
|
248
|
-
|
249
|
-
private
|
250
|
-
|
251
|
-
def ratings
|
252
|
-
@ratings ||= { one => rating_one, two => rating_two }
|
253
|
-
end
|
254
|
-
|
255
|
-
def rating_one
|
256
|
-
Rating.new(:result => result,
|
257
|
-
:old_rating => one.rating,
|
258
|
-
:other_rating => two.rating,
|
259
|
-
:k_factor => one.k_factor)
|
260
|
-
end
|
261
|
-
|
262
|
-
def rating_two
|
263
|
-
Rating.new(:result => (1.0 - result),
|
264
|
-
:old_rating => two.rating,
|
265
|
-
:other_rating => one.rating,
|
266
|
-
:k_factor => two.k_factor)
|
267
|
-
end
|
268
|
-
|
269
|
-
end
|
270
|
-
|
271
|
-
class Rating
|
272
|
-
|
273
|
-
include EloHelper
|
274
|
-
|
275
|
-
def result
|
276
|
-
raise "Invalid result: #{@result.inspect}" unless valid_result?
|
277
|
-
@result.to_f
|
278
|
-
end
|
279
|
-
|
280
|
-
def valid_result?
|
281
|
-
(0..1).include? @result
|
282
|
-
end
|
283
|
-
|
284
|
-
def expected
|
285
|
-
1.0 / ( 1.0 + ( 10.0 ** ((other_rating.to_f - old_rating.to_f) / 400.0) ) )
|
286
|
-
end
|
287
|
-
|
288
|
-
def change
|
289
|
-
k_factor.to_f * ( result.to_f - expected )
|
290
|
-
end
|
291
|
-
|
292
|
-
def new_rating
|
293
|
-
(old_rating.to_f + change).to_i
|
294
|
-
end
|
295
|
-
|
16
|
+
# Configure Elo in a block style.
|
17
|
+
# See Elo::Configuration for more details.
|
18
|
+
#
|
19
|
+
# Elo.configure do |config|
|
20
|
+
# config.attribute = :value
|
21
|
+
# end
|
22
|
+
def self.configure(&block)
|
23
|
+
yield(config)
|
296
24
|
end
|
297
25
|
|
298
26
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Elo
|
2
|
+
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
# This is the lower boundry of the rating you need to be a pro player.
|
6
|
+
# This setting is used in the FIDE k-factor rules. (default = 2400)
|
7
|
+
attr_accessor :pro_rating_boundry
|
8
|
+
|
9
|
+
# This is the lower boundry in the amount of games played to be a starting player
|
10
|
+
# This setting is used in the FIDE k-factor rules. (default = 30)
|
11
|
+
attr_accessor :starter_boundry
|
12
|
+
|
13
|
+
# The default k-factor is chosen when no k-factor rules apply.
|
14
|
+
# K-factor rules can be added by using the +k_factor+-method. (default = 15)
|
15
|
+
attr_accessor :default_k_factor
|
16
|
+
|
17
|
+
# This is the rating every player starts out with. (default = 1000)
|
18
|
+
attr_accessor :default_rating
|
19
|
+
|
20
|
+
# Use the settings that FIDE use for determening the K-factor.
|
21
|
+
# This is the case when all settings are unaltered. (default = true)
|
22
|
+
#
|
23
|
+
# In short:
|
24
|
+
#
|
25
|
+
# * K-factor is 25 when a player is a starter (less than 30 games played)
|
26
|
+
# * K-factor is 10 when a player is a pro (rating above 2400, now or in the past)
|
27
|
+
# * K-factor is 15 when a player in other cases
|
28
|
+
#
|
29
|
+
# If you want to use your own settings, either change the boundry settings,
|
30
|
+
# or set this setting to false and add you're own k-factor rules.
|
31
|
+
# K-factor rules can be added by using the +k_factor+-method.
|
32
|
+
attr_accessor :use_FIDE_settings
|
33
|
+
|
34
|
+
def initialize #:nodoc:
|
35
|
+
@pro_rating_boundry = 2400
|
36
|
+
@starter_boundry = 30
|
37
|
+
@default_rating = 1000
|
38
|
+
@default_k_factor = 15
|
39
|
+
@use_FIDE_settings = true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add a K-factor rule. The first argument is the k-factor value.
|
43
|
+
# The block should return a boolean that determines if this K-factor rule applies.
|
44
|
+
# The first rule that applies is the one determining the K-factor.
|
45
|
+
#
|
46
|
+
# The block is instance_eval'ed into the player, so you can access all it's
|
47
|
+
# properties directly. The K-factor is recalculated every time a match is played.
|
48
|
+
#
|
49
|
+
# By default, the FIDE settings are used (see: +use_FIDE_settings+). To implement
|
50
|
+
# that yourself, you could write:
|
51
|
+
#
|
52
|
+
# Elo.configure do |config|
|
53
|
+
# config.k_factor(10) { pro? or pro_rating? }
|
54
|
+
# config.k_factor(25) { starter? }
|
55
|
+
# config.default_k_factor = 15
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
def k_factor(factor, &rule)
|
59
|
+
k_factors << { :factor => factor, :rule => rule }
|
60
|
+
end
|
61
|
+
|
62
|
+
def applied_k_factors #:nodoc:
|
63
|
+
apply_fide_k_factors if use_FIDE_settings
|
64
|
+
k_factors
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def k_factors
|
70
|
+
@k_factors ||= []
|
71
|
+
end
|
72
|
+
|
73
|
+
def apply_fide_k_factors
|
74
|
+
unless @applied_fide_k_factors
|
75
|
+
k_factor(10) { pro? or pro_rating? }
|
76
|
+
k_factor(25) { starter? }
|
77
|
+
@applied_fide_k_factors = true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
data/lib/elo/game.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module Elo
|
2
|
+
|
3
|
+
# A Game is a collection of two Elo::Player objects
|
4
|
+
# and a result.
|
5
|
+
# Once the result is known, it propagates the new
|
6
|
+
# ratings to the players.
|
7
|
+
class Game
|
8
|
+
|
9
|
+
include Helper
|
10
|
+
|
11
|
+
# The result is the result of the match. It's a nubmer
|
12
|
+
# from 0 to 1 from the perspective of player +:one+.
|
13
|
+
attr_reader :result
|
14
|
+
|
15
|
+
# The first Elo::Player. The result is in perspecive of
|
16
|
+
# this player.
|
17
|
+
attr_reader :one
|
18
|
+
|
19
|
+
# The second Elo::Player.
|
20
|
+
attr_reader :two
|
21
|
+
|
22
|
+
# Every time a result is set, it tells the Elo::Player
|
23
|
+
# objects to update their scores.
|
24
|
+
def process_result(result)
|
25
|
+
@result = result
|
26
|
+
one.send(:played, self)
|
27
|
+
two.send(:played, self)
|
28
|
+
save
|
29
|
+
self
|
30
|
+
end
|
31
|
+
alias result= process_result
|
32
|
+
|
33
|
+
# Player +:one+ has won!
|
34
|
+
# This is a shortcut method for setting the score to 1
|
35
|
+
def win
|
36
|
+
process_result 1.0
|
37
|
+
end
|
38
|
+
|
39
|
+
# Player +:one+ has lost!
|
40
|
+
# This is a shortcut method for setting the score to 0
|
41
|
+
def lose
|
42
|
+
process_result 0.0
|
43
|
+
end
|
44
|
+
|
45
|
+
# It was a draw.
|
46
|
+
# This is a shortcut method for setting the score to 0.5
|
47
|
+
def draw
|
48
|
+
process_result 0.5
|
49
|
+
end
|
50
|
+
|
51
|
+
# You can override this method if you store each game
|
52
|
+
# in a database or something like that.
|
53
|
+
# This method will be called when a result is known.
|
54
|
+
def save
|
55
|
+
end
|
56
|
+
|
57
|
+
# Set the winner. Provide it with a Elo::Player.
|
58
|
+
def winner=(player)
|
59
|
+
process_result(player == one ? 1.0 : 0.0)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set the loser. Provide it with a Elo::Player.
|
63
|
+
def loser=(player)
|
64
|
+
process_result(player == one ? 0.0 : 1.0)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Access the Elo::Rating objects for both players.
|
68
|
+
def ratings
|
69
|
+
@ratings ||= { one => rating_one, two => rating_two }
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Create an Elo::Rating object for player one
|
75
|
+
def rating_one
|
76
|
+
Rating.new(:result => result,
|
77
|
+
:old_rating => one.rating,
|
78
|
+
:other_rating => two.rating,
|
79
|
+
:k_factor => one.k_factor)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Create an Elo::Rating object for player two
|
83
|
+
def rating_two
|
84
|
+
Rating.new(:result => (1.0 - result),
|
85
|
+
:old_rating => two.rating,
|
86
|
+
:other_rating => one.rating,
|
87
|
+
:k_factor => two.k_factor)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/lib/elo/helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Elo
|
2
|
+
|
3
|
+
module Helper
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
# Every object can be initialized with a hash,
|
10
|
+
# almost, but not quite, entirely unlike ActiveRecord.
|
11
|
+
def initialize(attributes = {})
|
12
|
+
attributes.each do |key, value|
|
13
|
+
instance_variable_set("@#{key}", value)
|
14
|
+
end
|
15
|
+
self.class.all << self
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
# Provides a list of all instantiated objects of the class.
|
21
|
+
def all
|
22
|
+
@all ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/elo/player.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Elo
|
2
|
+
|
3
|
+
# A player. You need at least two play a Game.
|
4
|
+
class Player
|
5
|
+
|
6
|
+
include Helper
|
7
|
+
|
8
|
+
# The rating you provided, or the default rating from configuration
|
9
|
+
def rating
|
10
|
+
@rating ||= Elo.config.default_rating
|
11
|
+
end
|
12
|
+
|
13
|
+
# The number of games played is needed for calculating the K-factor.
|
14
|
+
def games_played
|
15
|
+
@games_played ||= games.size
|
16
|
+
end
|
17
|
+
|
18
|
+
# A list of games played by the player.
|
19
|
+
def games
|
20
|
+
@games ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Is the player considered a pro, because his/her rating crossed
|
24
|
+
# the threshold configured? This is needed for calculating the K-factor.
|
25
|
+
def pro_rating?
|
26
|
+
rating >= Elo.config.pro_rating_boundry
|
27
|
+
end
|
28
|
+
|
29
|
+
# Is the player just starting? Provide the boundry for
|
30
|
+
# the amount of games played in the configuration.
|
31
|
+
# This is needed for calculating the K-factor.
|
32
|
+
def starter?
|
33
|
+
games_played < Elo.config.starter_boundry
|
34
|
+
end
|
35
|
+
|
36
|
+
# FIDE regulations specify that once you reach a pro status
|
37
|
+
# (see +pro_rating?+), you are considered a pro for life.
|
38
|
+
#
|
39
|
+
# You might need to specify it manually, when depending on
|
40
|
+
# external persistence of players.
|
41
|
+
#
|
42
|
+
# Elo::Player.new(:pro => true)
|
43
|
+
def pro?
|
44
|
+
!!@pro
|
45
|
+
end
|
46
|
+
|
47
|
+
# You can override this method if you store each game
|
48
|
+
# in a database or something like that.
|
49
|
+
# This method will be called when a result is known.
|
50
|
+
def save
|
51
|
+
end
|
52
|
+
|
53
|
+
# Calculates the K-factor for the player.
|
54
|
+
# Elo allows you specify custom Rules (see Elo::Configuration).
|
55
|
+
#
|
56
|
+
# You can set it manually, if you wish:
|
57
|
+
#
|
58
|
+
# Elo::Player.new(:k_factor => 10)
|
59
|
+
#
|
60
|
+
# This stops this player from using the K-factor rules.
|
61
|
+
def k_factor
|
62
|
+
return @k_factor if @k_factor
|
63
|
+
Elo.config.applied_k_factors.each do |rule|
|
64
|
+
return rule[:factor] if instance_eval(&rule[:rule])
|
65
|
+
end
|
66
|
+
Elo.config.default_k_factor
|
67
|
+
end
|
68
|
+
|
69
|
+
# Start a game with another player. At this point, no
|
70
|
+
# result is known and nothing really happens.
|
71
|
+
def versus(other_player)
|
72
|
+
Game.new(:one => self, :two => other_player)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Start a game with another player and set the score
|
76
|
+
# immediately.
|
77
|
+
def wins_from(other_player)
|
78
|
+
versus(other_player).win
|
79
|
+
end
|
80
|
+
|
81
|
+
# Start a game with another player and set the score
|
82
|
+
# immediately.
|
83
|
+
def plays_draw(other_player)
|
84
|
+
versus(other_player).draw
|
85
|
+
end
|
86
|
+
|
87
|
+
# Start a game with another player and set the score
|
88
|
+
# immediately.
|
89
|
+
def loses_from(other_player)
|
90
|
+
versus(other_player).lose
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# A Game tells the players informed to update their
|
96
|
+
# scores, after it knows the result (so it can calculate the rating).
|
97
|
+
#
|
98
|
+
# This method is private, because it is called automatically.
|
99
|
+
# Therefore it is not part of the public API of Elo.
|
100
|
+
def played(game)
|
101
|
+
@games_played = games_played + 1
|
102
|
+
games << game
|
103
|
+
@rating = game.ratings[self].new_rating
|
104
|
+
@pro = true if pro_rating?
|
105
|
+
save
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/lib/elo/rating.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Elo
|
2
|
+
|
3
|
+
# This class calculates the rating between two players,
|
4
|
+
# but only from one persons perspective. You need two Rating-instances
|
5
|
+
# to calculate ratings for both players. Luckily, Elo::Game handles
|
6
|
+
# this for you automatically.
|
7
|
+
class Rating
|
8
|
+
|
9
|
+
include Helper
|
10
|
+
|
11
|
+
# The rating of the player you DON"T wish to calculate.
|
12
|
+
attr_reader :other_rating
|
13
|
+
|
14
|
+
# The rating of the player you wish to calculate.
|
15
|
+
attr_reader :old_rating
|
16
|
+
|
17
|
+
# The k-factor you wish to use for this calculation.
|
18
|
+
attr_reader :k_factor
|
19
|
+
|
20
|
+
# The new rating is... wait for it... the new rating!
|
21
|
+
def new_rating
|
22
|
+
(old_rating.to_f + change).to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# The result of the match. 1 means that the player won, 0 means that the
|
28
|
+
# player lost and 0.5 means that it was a draw.
|
29
|
+
def result
|
30
|
+
raise "Invalid result: #{@result.inspect}" unless valid_result?
|
31
|
+
@result.to_f
|
32
|
+
end
|
33
|
+
|
34
|
+
# Only values between 0 and 1 are considered to be valid scores.
|
35
|
+
def valid_result?
|
36
|
+
(0..1).include? @result
|
37
|
+
end
|
38
|
+
|
39
|
+
# The expected score is the probably outcome of the match, depending
|
40
|
+
# on the difference in rating between the two players.
|
41
|
+
#
|
42
|
+
# For more information visit
|
43
|
+
# {Wikipedia}[http://en.wikipedia.org/wiki/Elo_rating_system#Mathematical_details]
|
44
|
+
def expected
|
45
|
+
1.0 / ( 1.0 + ( 10.0 ** ((other_rating.to_f - old_rating.to_f) / 400.0) ) )
|
46
|
+
end
|
47
|
+
|
48
|
+
# The change is the points you earn or lose.
|
49
|
+
#
|
50
|
+
# For more information visit
|
51
|
+
# {Wikipedia}[http://en.wikipedia.org/wiki/Elo_rating_system#Mathematical_details]
|
52
|
+
def change
|
53
|
+
k_factor.to_f * ( result.to_f - expected )
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/spec/elo_spec.rb
CHANGED
@@ -1,7 +1,148 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
describe "Elo" do
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
after do
|
6
|
+
Elo.instance_eval { @config = nil }
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should work as advertised" do
|
10
|
+
|
11
|
+
bob = Elo::Player.new
|
12
|
+
jane = Elo::Player.new(:rating => 1500)
|
13
|
+
|
14
|
+
game1 = bob.wins_from(jane)
|
15
|
+
game2 = bob.loses_from(jane)
|
16
|
+
game3 = bob.plays_draw(jane)
|
17
|
+
|
18
|
+
game4 = bob.versus(jane)
|
19
|
+
game4.winner = jane
|
20
|
+
|
21
|
+
game5 = bob.versus(jane)
|
22
|
+
game5.loser = jane
|
23
|
+
|
24
|
+
game6 = bob.versus(jane)
|
25
|
+
game6.draw
|
26
|
+
|
27
|
+
game7 = bob.versus(jane)
|
28
|
+
game7.result = 1
|
29
|
+
|
30
|
+
bob.rating.should == 1084
|
31
|
+
jane.rating.should == 1409
|
32
|
+
bob.should_not be_pro
|
33
|
+
bob.should be_starter
|
34
|
+
bob.games_played.should == 7
|
35
|
+
bob.games.should == [ game1, game2, game3, game4, game5, game6, game7 ]
|
36
|
+
|
37
|
+
Elo::Player.all.should == [ bob, jane ]
|
38
|
+
Elo::Game.all.should == [ game1, game2, game3, game4, game5, game6, game7 ]
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "Configuration" do
|
43
|
+
|
44
|
+
it "default_rating" do
|
45
|
+
Elo.config.default_rating.should == 1000
|
46
|
+
Elo::Player.new.rating.should == 1000
|
47
|
+
|
48
|
+
Elo.config.default_rating = 1337
|
49
|
+
|
50
|
+
Elo.config.default_rating.should == 1337
|
51
|
+
Elo::Player.new.rating.should == 1337
|
52
|
+
end
|
53
|
+
|
54
|
+
it "starter_boundry" do
|
55
|
+
Elo.config.starter_boundry.should == 30
|
56
|
+
Elo::Player.new(:games_played => 20).should be_starter
|
57
|
+
|
58
|
+
Elo.config.starter_boundry = 15
|
59
|
+
|
60
|
+
Elo.config.starter_boundry.should == 15
|
61
|
+
Elo::Player.new(:games_played => 20).should_not be_starter
|
62
|
+
end
|
63
|
+
|
64
|
+
it "default_k_factor and FIDE settings" do
|
65
|
+
Elo.config.use_FIDE_settings.should == true
|
66
|
+
Elo.config.default_k_factor.should == 15
|
67
|
+
|
68
|
+
Elo.config.default_k_factor = 20
|
69
|
+
Elo.config.use_FIDE_settings = false
|
70
|
+
|
71
|
+
Elo.config.default_k_factor.should == 20
|
72
|
+
Elo.config.use_FIDE_settings.should == false
|
73
|
+
Elo::Player.new.k_factor.should == 20
|
74
|
+
end
|
75
|
+
|
76
|
+
it "pro_rating_boundry" do
|
77
|
+
Elo.config.pro_rating_boundry.should == 2400
|
78
|
+
|
79
|
+
Elo.config.pro_rating_boundry = 1337
|
80
|
+
|
81
|
+
Elo.config.pro_rating_boundry.should == 1337
|
82
|
+
Elo::Player.new(:rating => 1337).should be_pro_rating
|
83
|
+
end
|
84
|
+
|
6
85
|
end
|
86
|
+
|
87
|
+
describe "according to FIDE" do
|
88
|
+
|
89
|
+
it "starter" do
|
90
|
+
player = Elo::Player.new
|
91
|
+
player.k_factor.should == 25
|
92
|
+
player.should be_starter
|
93
|
+
player.should_not be_pro
|
94
|
+
player.should_not be_pro_rating
|
95
|
+
end
|
96
|
+
|
97
|
+
it "normal" do
|
98
|
+
player = Elo::Player.new(:rating => 2399, :games_played => 30)
|
99
|
+
player.k_factor.should == 15
|
100
|
+
player.should_not be_starter
|
101
|
+
player.should_not be_pro
|
102
|
+
player.should_not be_pro_rating
|
103
|
+
end
|
104
|
+
|
105
|
+
it "pro rating" do
|
106
|
+
player = Elo::Player.new(:rating => 2400)
|
107
|
+
player.k_factor.should == 10
|
108
|
+
player.should be_starter
|
109
|
+
player.should be_pro_rating
|
110
|
+
player.should_not be_pro
|
111
|
+
end
|
112
|
+
|
113
|
+
it "historically a pro" do
|
114
|
+
player = Elo::Player.new(:rating => 2399, :pro => true)
|
115
|
+
player.k_factor.should == 10
|
116
|
+
player.should be_starter
|
117
|
+
player.should_not be_pro_rating
|
118
|
+
player.should be_pro
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "examples for calculating rating correctly" do
|
123
|
+
|
124
|
+
# examples from http://chesselo.com/
|
125
|
+
|
126
|
+
before do
|
127
|
+
@a = Elo::Player.new(:rating => 2000, :k_factor => 10)
|
128
|
+
@b = Elo::Player.new(:rating => 1900, :k_factor => 10)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "winning" do
|
132
|
+
@a.wins_from(@b)
|
133
|
+
@a.rating.should == 2003
|
134
|
+
end
|
135
|
+
|
136
|
+
it "losing" do
|
137
|
+
@a.loses_from(@b)
|
138
|
+
@a.rating.should == 1993
|
139
|
+
end
|
140
|
+
|
141
|
+
it "draw" do
|
142
|
+
@a.plays_draw(@b)
|
143
|
+
@a.rating.should == 1998
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
7
148
|
end
|
metadata
CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- alpha
|
10
|
-
version: 0.0.
|
10
|
+
version: 0.0.3.alpha
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Iain Hecker
|
@@ -61,7 +61,13 @@ files:
|
|
61
61
|
- doc/fr_method_index.html
|
62
62
|
- doc/index.html
|
63
63
|
- doc/rdoc-style.css
|
64
|
+
- elo.gemspec
|
64
65
|
- lib/elo.rb
|
66
|
+
- lib/elo/configuration.rb
|
67
|
+
- lib/elo/game.rb
|
68
|
+
- lib/elo/helper.rb
|
69
|
+
- lib/elo/player.rb
|
70
|
+
- lib/elo/rating.rb
|
65
71
|
- spec/elo_spec.rb
|
66
72
|
- spec/spec.opts
|
67
73
|
- spec/spec_helper.rb
|