elo 0.0.2.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/README.rdoc +124 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/doc/classes/Elo.html +67 -0
- data/doc/classes/Elo/Configuration.html +184 -0
- data/doc/classes/Elo/EloHelper.html +124 -0
- data/doc/classes/Elo/EloHelper/ClassMethods.html +78 -0
- data/doc/classes/Elo/Game.html +193 -0
- data/doc/classes/Elo/Player.html +254 -0
- data/doc/classes/Elo/Rating.html +141 -0
- data/doc/created.rid +1 -0
- data/doc/files/README_rdoc.html +163 -0
- data/doc/files/lib/elo_rb.html +58 -0
- data/doc/fr_class_index.html +21 -0
- data/doc/fr_file_index.html +21 -0
- data/doc/fr_method_index.html +4469 -0
- data/doc/index.html +15 -0
- data/doc/rdoc-style.css +328 -0
- data/lib/elo.rb +298 -0
- data/spec/elo_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +102 -0
data/doc/index.html
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
|
2
|
+
<html lang='en' xml:lang='en' xmlns='http://www.w3.org/1999/xhtml'>
|
3
|
+
<head>
|
4
|
+
<title>elo</title>
|
5
|
+
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
|
6
|
+
</head>
|
7
|
+
<frameset border='1' bordercolor='gray' cols='20%, *' frameborder='1'>
|
8
|
+
<frameset rows='15%, 35%, 50%'>
|
9
|
+
<frame name='Files' src='fr_file_index.html' title='Files'></frame>
|
10
|
+
<frame name='Classes' src='fr_class_index.html'></frame>
|
11
|
+
<frame name='Methods' src='fr_method_index.html'></frame>
|
12
|
+
</frameset>
|
13
|
+
<frame name='docwin' src='files/README_rdoc.html'></frame>
|
14
|
+
</frameset>
|
15
|
+
</html>
|
data/doc/rdoc-style.css
ADDED
@@ -0,0 +1,328 @@
|
|
1
|
+
html, body {
|
2
|
+
height: 100%; }
|
3
|
+
|
4
|
+
body {
|
5
|
+
font-family: Lucida Grande, Verdana, Arial, Helvetica, sans-serif;
|
6
|
+
font-size: 90%;
|
7
|
+
margin: 0;
|
8
|
+
padding: 0;
|
9
|
+
background: white;
|
10
|
+
color: black; }
|
11
|
+
|
12
|
+
#wrapper {
|
13
|
+
min-height: 100%;
|
14
|
+
height: auto !important;
|
15
|
+
height: 100%;
|
16
|
+
margin: 0 auto -43px; }
|
17
|
+
|
18
|
+
#footer-push {
|
19
|
+
height: 43px; }
|
20
|
+
|
21
|
+
div.header, #footer {
|
22
|
+
background: #eee; }
|
23
|
+
|
24
|
+
#footer {
|
25
|
+
border-top: 1px solid silver;
|
26
|
+
margin-top: 12px;
|
27
|
+
padding: 0 2em;
|
28
|
+
line-height: 30px;
|
29
|
+
text-align: center;
|
30
|
+
font-variant: small-caps;
|
31
|
+
font-size: 95%; }
|
32
|
+
|
33
|
+
.clearing:after {
|
34
|
+
content: ".";
|
35
|
+
visibility: hidden;
|
36
|
+
height: 0;
|
37
|
+
display: block;
|
38
|
+
clear: both; }
|
39
|
+
* html .clearing {
|
40
|
+
height: 1px; }
|
41
|
+
.clearing *:first-child + html {
|
42
|
+
overflow: hidden; }
|
43
|
+
|
44
|
+
h1, h2, h3, h4, h5, h6 {
|
45
|
+
margin: 0;
|
46
|
+
font-weight: normal; }
|
47
|
+
|
48
|
+
a {
|
49
|
+
color: #0b3e71; }
|
50
|
+
a:hover {
|
51
|
+
background: #336699;
|
52
|
+
text-decoration: none;
|
53
|
+
color: #eef; }
|
54
|
+
|
55
|
+
#diagram img {
|
56
|
+
border: 0; }
|
57
|
+
|
58
|
+
#description a, .method .description a, .header a {
|
59
|
+
color: #336699; }
|
60
|
+
#description a:hover, .method .description a:hover, .header a:hover {
|
61
|
+
color: #eee; }
|
62
|
+
#description h1 a, #description h2 a, #description h3 a, #description h4 a, #description h5 a, #description h6 a, .method .description h1 a, .method .description h2 a, .method .description h3 a, .method .description h4 a, .method .description h5 a, .method .description h6 a, .header h1 a, .header h2 a, .header h3 a, .header h4 a, .header h5 a, .header h6 a {
|
63
|
+
color: #0b3e71; }
|
64
|
+
|
65
|
+
ol {
|
66
|
+
margin: 0;
|
67
|
+
padding: 0;
|
68
|
+
list-style: none; }
|
69
|
+
ol li {
|
70
|
+
margin-left: 0;
|
71
|
+
white-space: nowrap; }
|
72
|
+
ol li.other {
|
73
|
+
display: none; }
|
74
|
+
|
75
|
+
ol.expanded li.other {
|
76
|
+
display: list-item; }
|
77
|
+
|
78
|
+
table {
|
79
|
+
margin-bottom: 1em;
|
80
|
+
font-size: 1em;
|
81
|
+
border-collapse: collapse; }
|
82
|
+
table td, table th {
|
83
|
+
padding: .4em .8em; }
|
84
|
+
table thead {
|
85
|
+
background-color: #e8e8e8; }
|
86
|
+
table thead th {
|
87
|
+
font-variant: small-caps;
|
88
|
+
color: #666666; }
|
89
|
+
table tr {
|
90
|
+
border-bottom: 1px solid silver; }
|
91
|
+
|
92
|
+
#index a.show, div.header a.show {
|
93
|
+
text-decoration: underline;
|
94
|
+
font-style: italic;
|
95
|
+
color: #666666; }
|
96
|
+
#index a.show:after, div.header a.show:after {
|
97
|
+
content: " ..."; }
|
98
|
+
#index a.show:hover, div.header a.show:hover {
|
99
|
+
color: black;
|
100
|
+
background: #ffe; }
|
101
|
+
|
102
|
+
#index {
|
103
|
+
font: 85%/1.2 Arial, Helvetica, sans-serif; }
|
104
|
+
#index a {
|
105
|
+
text-decoration: none; }
|
106
|
+
#index h1 {
|
107
|
+
padding: .2em .5em .1em;
|
108
|
+
background: #ccc;
|
109
|
+
font: small-caps 1.2em Georgia, serif;
|
110
|
+
color: #333;
|
111
|
+
border-bottom: 1px solid gray; }
|
112
|
+
#index form {
|
113
|
+
margin: 0;
|
114
|
+
padding: 0; }
|
115
|
+
#index form input {
|
116
|
+
margin: .4em 3px 0 .4em;
|
117
|
+
width: 80%; }
|
118
|
+
#index form #search.untouched {
|
119
|
+
color: #777777; }
|
120
|
+
#index form .clear_button {
|
121
|
+
-moz-border-radius: 7px;
|
122
|
+
-webkit-border-radius: 7px;
|
123
|
+
background: #AAA;
|
124
|
+
color: white;
|
125
|
+
padding: 0 5px 1px 5px;
|
126
|
+
cursor: pointer; }
|
127
|
+
#index ol {
|
128
|
+
padding: .4em .5em; }
|
129
|
+
#index ol li {
|
130
|
+
white-space: nowrap; }
|
131
|
+
#index #index-entries li a {
|
132
|
+
padding: 1px 2px; }
|
133
|
+
#index #index-entries.classes {
|
134
|
+
font-size: 1.1em; }
|
135
|
+
#index #index-entries.classes ol {
|
136
|
+
padding: 0; }
|
137
|
+
#index #index-entries.classes span.nodoc {
|
138
|
+
display: none; }
|
139
|
+
#index #index-entries.classes span.nodoc, #index #index-entries.classes a {
|
140
|
+
font-weight: bold; }
|
141
|
+
#index #index-entries.classes .parent {
|
142
|
+
font-weight: normal; }
|
143
|
+
#index #index-entries.methods li, #index #search-results.methods li {
|
144
|
+
margin-bottom: 0.2em; }
|
145
|
+
#index #index-entries.methods li a .method_name, #index #search-results.methods li a .method_name {
|
146
|
+
margin-right: 0.25em; }
|
147
|
+
#index #index-entries.methods li a .module_name, #index #search-results.methods li a .module_name {
|
148
|
+
color: #666666; }
|
149
|
+
#index #index-entries.methods li a:hover .module_name, #index #search-results.methods li a:hover .module_name {
|
150
|
+
color: #ddd; }
|
151
|
+
|
152
|
+
#attribute-list .context-item-name {
|
153
|
+
font-weight: bold; }
|
154
|
+
|
155
|
+
div.header {
|
156
|
+
font-size: 80%;
|
157
|
+
padding: .5em 2%;
|
158
|
+
font-family: Arial, Helvetica, sans-serif;
|
159
|
+
border-bottom: 1px solid silver; }
|
160
|
+
div.header .name {
|
161
|
+
font-size: 1.6em;
|
162
|
+
font-family: Georgia, serif; }
|
163
|
+
div.header .name .type {
|
164
|
+
color: #666666;
|
165
|
+
font-size: 80%;
|
166
|
+
font-variant: small-caps; }
|
167
|
+
div.header h1.name {
|
168
|
+
font-size: 2.2em; }
|
169
|
+
div.header .paths, div.header .last-update, div.header .parent {
|
170
|
+
color: #666666; }
|
171
|
+
div.header .last-update .datetime {
|
172
|
+
color: #484848; }
|
173
|
+
div.header .parent {
|
174
|
+
margin-top: .3em; }
|
175
|
+
div.header .parent strong {
|
176
|
+
font-weight: normal;
|
177
|
+
color: #484848; }
|
178
|
+
|
179
|
+
#content {
|
180
|
+
padding: 12px 2%; }
|
181
|
+
div.class #content {
|
182
|
+
position: relative;
|
183
|
+
width: 72%; }
|
184
|
+
#content pre, #content .method .synopsis {
|
185
|
+
font: 14px Monaco, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace; }
|
186
|
+
#content pre {
|
187
|
+
color: black;
|
188
|
+
background: #eee;
|
189
|
+
border: 1px solid silver;
|
190
|
+
padding: .5em .8em;
|
191
|
+
overflow: auto; }
|
192
|
+
#content p code, #content p tt, #content li code, #content li tt, #content dl code, #content dl tt {
|
193
|
+
font: 14px Monaco, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
|
194
|
+
background: #ffffe3;
|
195
|
+
padding: 2px 3px;
|
196
|
+
line-height: 1.4; }
|
197
|
+
#content h1 code, #content h1 tt, #content h2 code, #content h2 tt, #content h3 code, #content h3 tt, #content h4 code, #content h4 tt, #content h5 code, #content h5 tt, #content h6 code, #content h6 tt {
|
198
|
+
font-size: 1.1em; }
|
199
|
+
#content #text {
|
200
|
+
position: relative; }
|
201
|
+
#content #description p {
|
202
|
+
margin-top: .5em; }
|
203
|
+
#content #description h1, #content #description h2, #content #description h3, #content #description h4, #content #description h5, #content #description h6 {
|
204
|
+
font-family: Georgia, serif; }
|
205
|
+
#content #description h1 {
|
206
|
+
font-size: 2.2em;
|
207
|
+
margin-bottom: .2em;
|
208
|
+
border-bottom: 3px double #d8d8d8;
|
209
|
+
padding-bottom: .1em; }
|
210
|
+
#content #description h2 {
|
211
|
+
font-size: 1.8em;
|
212
|
+
color: #0e3062;
|
213
|
+
margin: .8em 0 .3em 0; }
|
214
|
+
#content #description h3 {
|
215
|
+
font-size: 1.6em;
|
216
|
+
margin: .8em 0 .3em 0;
|
217
|
+
color: #666666; }
|
218
|
+
#content #description h4 {
|
219
|
+
font-size: 1.4em;
|
220
|
+
margin: .8em 0 .3em 0; }
|
221
|
+
#content #description h5 {
|
222
|
+
font-size: 1.2em;
|
223
|
+
margin: .8em 0 .3em 0;
|
224
|
+
color: #0e3062; }
|
225
|
+
#content #description h6 {
|
226
|
+
font-size: 1.0em;
|
227
|
+
margin: .8em 0 .3em 0;
|
228
|
+
color: #666666; }
|
229
|
+
#content #description ul, #content #description ol, #content .method .description ul, #content .method .description ol {
|
230
|
+
margin: .8em 0;
|
231
|
+
padding-left: 1.5em; }
|
232
|
+
#content #description ol, #content .method .description ol {
|
233
|
+
list-style: decimal; }
|
234
|
+
#content #description ol li, #content .method .description ol li {
|
235
|
+
white-space: normal; }
|
236
|
+
|
237
|
+
#method-list {
|
238
|
+
position: absolute;
|
239
|
+
top: 0px;
|
240
|
+
right: -33%;
|
241
|
+
width: 28%;
|
242
|
+
background: #eee;
|
243
|
+
border: 1px solid silver;
|
244
|
+
padding: .4em 1%;
|
245
|
+
overflow: hidden; }
|
246
|
+
#method-list h2 {
|
247
|
+
font-size: 1.3em; }
|
248
|
+
#method-list h3 {
|
249
|
+
font-variant: small-caps;
|
250
|
+
text-transform: capitalize;
|
251
|
+
font-family: Georgia, serif;
|
252
|
+
color: #666;
|
253
|
+
font-size: 1.1em; }
|
254
|
+
#method-list ol {
|
255
|
+
padding: 0 0 .5em .5em; }
|
256
|
+
|
257
|
+
#context {
|
258
|
+
border-top: 1px dashed silver;
|
259
|
+
margin-top: 1em;
|
260
|
+
margin-bottom: 1em; }
|
261
|
+
|
262
|
+
#context h2, #section h2 {
|
263
|
+
font: small-caps 1.2em Georgia, serif;
|
264
|
+
color: #444;
|
265
|
+
margin: 1em 0 .2em 0; }
|
266
|
+
|
267
|
+
#methods .method {
|
268
|
+
border: 1px solid silver;
|
269
|
+
margin-top: .5em;
|
270
|
+
background: #eee; }
|
271
|
+
#methods .method .synopsis {
|
272
|
+
color: black;
|
273
|
+
background: silver;
|
274
|
+
padding: .2em 1em; }
|
275
|
+
#methods .method .synopsis .name {
|
276
|
+
font-weight: bold; }
|
277
|
+
#methods .method .synopsis a {
|
278
|
+
text-decoration: none; }
|
279
|
+
#methods .method .description {
|
280
|
+
padding: 0 1em; }
|
281
|
+
#methods .method .description pre {
|
282
|
+
background: #f8f8f8; }
|
283
|
+
#methods .method .source {
|
284
|
+
margin: .5em 0; }
|
285
|
+
#methods .method .source-toggle {
|
286
|
+
font-size: 85%;
|
287
|
+
margin-left: 1em; }
|
288
|
+
#methods .public-class {
|
289
|
+
background: #ffffe4; }
|
290
|
+
#methods .public-instance .synopsis {
|
291
|
+
color: #eee;
|
292
|
+
background: #336699; }
|
293
|
+
|
294
|
+
#content .method .source pre {
|
295
|
+
background: #262626;
|
296
|
+
color: #ffdead;
|
297
|
+
margin: 1em;
|
298
|
+
padding: 0.5em;
|
299
|
+
border: 1px dashed #999;
|
300
|
+
overflow: auto; }
|
301
|
+
#content .method .source pre .ruby-constant {
|
302
|
+
color: #7fffd4;
|
303
|
+
background: transparent; }
|
304
|
+
#content .method .source pre .ruby-keyword {
|
305
|
+
color: #00ffff;
|
306
|
+
background: transparent; }
|
307
|
+
#content .method .source pre .ruby-ivar {
|
308
|
+
color: #eedd82;
|
309
|
+
background: transparent; }
|
310
|
+
#content .method .source pre .ruby-operator {
|
311
|
+
color: #00ffee;
|
312
|
+
background: transparent; }
|
313
|
+
#content .method .source pre .ruby-identifier {
|
314
|
+
color: #ffdead;
|
315
|
+
background: transparent; }
|
316
|
+
#content .method .source pre .ruby-node {
|
317
|
+
color: #ffa07a;
|
318
|
+
background: transparent; }
|
319
|
+
#content .method .source pre .ruby-comment {
|
320
|
+
color: #b22222;
|
321
|
+
font-weight: bold;
|
322
|
+
background: transparent; }
|
323
|
+
#content .method .source pre .ruby-regexp {
|
324
|
+
color: #ffa07a;
|
325
|
+
background: transparent; }
|
326
|
+
#content .method .source pre .ruby-value {
|
327
|
+
color: #7fffd4;
|
328
|
+
background: transparent; }
|
data/lib/elo.rb
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# The configuration of Elo is done here.
|
2
|
+
#
|
3
|
+
# See README.rdoc for general information about Elo.
|
4
|
+
module Elo
|
5
|
+
|
6
|
+
@pro_rating_boundry = 2400
|
7
|
+
@starter_boundry = 30
|
8
|
+
@default_rating = 1000
|
9
|
+
@default_k_factor = 15
|
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
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
extend Configuration
|
94
|
+
|
95
|
+
# Common methods for Elo classes.
|
96
|
+
module EloHelper
|
97
|
+
|
98
|
+
def self.included(base)
|
99
|
+
base.extend ClassMethods
|
100
|
+
end
|
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
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|