elo4m 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b674afae937af9be30bd785e2599a8ae39cf5851
4
+ data.tar.gz: 15de3862089257038b5373a6eeeecc4fecae9f3e
5
+ SHA512:
6
+ metadata.gz: 2b206cf3720564da2889244f32dc94ae6f51c8eb931c6f54ac629c267c4a831c338cf0fd1f67a55357f6190471d17876e8b24239e91606c6c53be6ab4a4ec19c
7
+ data.tar.gz: 7a5b3c4b7fa98a3c0b71a50979d8f9153c72e0c153f4a5c951680dbeaa4259419a5b13c9f017fe17c475142603bafd5db5f42a13827feff1b79d998359d032ac
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in elo4m.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Daiki Taniguchi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # Elo4m
2
+
3
+ ### Elo for Multi Player Games
4
+
5
+ From [Wikipedia](http://en.wikipedia.org/wiki/Elo_rating_system):
6
+
7
+ The Elo rating system is a method for calculating the relative skill levels of
8
+ players in two-player games such as chess and Go. It is named after its creator
9
+ Arpad Elo, a Hungarian-born American physics professor.
10
+
11
+ But Elo was designed for two player games.
12
+ This is for multi player games.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'elo4m'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install elo4m
29
+
30
+ ## Usage
31
+
32
+ ```ruby
33
+ # :rating => player's rating before game.
34
+ # :rank => game result. if you win first prize, this is 1.
35
+ # :new_rating => new rating applied elo raitng.
36
+
37
+ players = [
38
+ Elo4m::Player.new(:rating, :rank),
39
+ Elo4m::Player.new(:rating, :rank)
40
+ ]
41
+ game = Elo4m::Game.new(players)
42
+ game.run
43
+ #=> [:new_rating, :new_rating]
44
+ ```
45
+
46
+ ## Example
47
+
48
+ ```ruby
49
+ players = [
50
+ Elo4m::Player.new(1613, 4),
51
+ Elo4m::Player.new(1609, 2),
52
+ Elo4m::Player.new(1477, 4),
53
+ Elo4m::Player.new(1388, 6),
54
+ Elo4m::Player.new(1586, 5),
55
+ Elo4m::Player.new(1720, 1)
56
+ ]
57
+ game = Elo4m::Game.new(players)
58
+ game.run
59
+ #=> [1601, 1646, 1499, 1350, 1533, 1762]
60
+ ```
61
+
62
+ ## Contributing
63
+
64
+ 1. Fork it ( https://github.com/kidach1/elo4m/fork )
65
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
66
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
67
+ 4. Push to the branch (`git push origin my-new-feature`)
68
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/elo4m.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'elo4m/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "elo4m"
8
+ spec.version = Elo4m::VERSION
9
+ spec.authors = ["kidach1"]
10
+ spec.email = ["daiki.taniguchi@aktsk.jp"]
11
+ spec.summary = %q{For calculating the relative skill levels of players in multi-player games.}
12
+ spec.description = %q{For calculating the relative skill levels of players in multi-player games.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_dependency "gnuplot"
25
+ end
@@ -0,0 +1,50 @@
1
+ module Elo4m
2
+ class Configuration
3
+ attr_accessor :default_k_factor
4
+ attr_accessor :default_rating
5
+
6
+ def initialize #:nodoc:
7
+ @default_rating = 1000
8
+ @default_k_factor = 15
9
+ end
10
+
11
+ # Add a K-factor rule. The first argument is the k-factor value.
12
+ # The block should return a boolean that determines if this K-factor rule applies.
13
+ # The first rule that applies is the one determining the K-factor.
14
+ #
15
+ # The block is instance_eval'ed into the player, so you can access all it's
16
+ # properties directly. The K-factor is recalculated every time a match is played.
17
+ #
18
+ # By default, the FIDE settings are used (see: +use_FIDE_settings+). To implement
19
+ # that yourself, you could write:
20
+ #
21
+ # Elo.configure do |config|
22
+ # config.k_factor(10) { pro? or pro_rating? }
23
+ # config.k_factor(25) { starter? }
24
+ # config.default_k_factor = 15
25
+ # end
26
+ #
27
+ def k_factor(factor, &rule)
28
+ k_factors << { :factor => factor, :rule => rule }
29
+ end
30
+
31
+ def applied_k_factors #:nodoc:
32
+ apply_fide_k_factors if use_FIDE_settings
33
+ k_factors
34
+ end
35
+
36
+ private
37
+
38
+ def k_factors
39
+ @k_factors ||= []
40
+ end
41
+
42
+ def apply_fide_k_factors
43
+ unless @applied_fide_k_factors
44
+ k_factor(10) { pro? or pro_rating? }
45
+ k_factor(25) { starter? }
46
+ @applied_fide_k_factors = true
47
+ end
48
+ end
49
+ end
50
+ end
data/lib/elo4m/game.rb ADDED
@@ -0,0 +1,71 @@
1
+ module Elo4m
2
+ class Game
3
+ # http://en.wikipedia.org/wiki/Elo_rating_system#Mathematical_details
4
+
5
+ include Helper
6
+ attr_accessor :player_cnt
7
+ K_FACTOR = 32
8
+
9
+ def initialize(players)
10
+ self.player_cnt = players.length
11
+ define_attr_player
12
+ players.each.with_index(1) do |player, i|
13
+ send("player#{i}=", player)
14
+ end
15
+ end
16
+
17
+ def run
18
+ self.player_cnt.times.map.with_index(1) do |_, i|
19
+ Rating.new(
20
+ result: score_sum(i),
21
+ old_rating: instance_variable_get("@player#{i}").rating,
22
+ expected: expected_sum(i),
23
+ k_factor: K_FACTOR
24
+ ).new_rating
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def define_attr_player
31
+ self.player_cnt.times.with_index(1) do |_, i|
32
+ self.class.send :define_method, "player#{i}=" do |value|
33
+ instance_variable_set("@player#{i}", value)
34
+ end
35
+ end
36
+ end
37
+
38
+ def score_sum(player_number)
39
+ my_ranking = instance_variable_get("@player#{player_number}").ranking
40
+ score = 0
41
+ self.player_cnt.times.with_index(1) do |_, i|
42
+ next if i == player_number
43
+ score += ranking2score(my_ranking, instance_variable_get("@player#{i}").ranking)
44
+ end
45
+ score
46
+ end
47
+
48
+ def ranking2score(my_ranking, other_ranking)
49
+ if my_ranking < other_ranking
50
+ 1
51
+ elsif my_ranking > other_ranking
52
+ 0
53
+ else
54
+ 0.5
55
+ end
56
+ end
57
+
58
+ def expected_sum(player_number)
59
+ my_rating = instance_variable_get("@player#{player_number}").rating
60
+ es = 0
61
+ self.player_cnt.times.with_index(1) do |_, i|
62
+ next if i == player_number
63
+ es += Rating.new(
64
+ old_rating: my_rating,
65
+ other_rating: instance_variable_get("@player#{i}").rating
66
+ ).expected_al.round(3)
67
+ end
68
+ es
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,23 @@
1
+ module Elo4m
2
+ module Helper
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ # Every object can be initialized with a hash,
8
+ # almost, but not quite, entirely unlike ActiveRecord.
9
+ def initialize(attributes = {})
10
+ attributes.each do |key, value|
11
+ instance_variable_set("@#{key}", value)
12
+ end
13
+ self.class.all << self
14
+ end
15
+
16
+ module ClassMethods
17
+ # Provides a list of all instantiated objects of the class.
18
+ def all
19
+ @all ||= []
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ module Elo4m
2
+ class Player
3
+ include Helper
4
+ attr_accessor :rating
5
+ attr_accessor :ranking
6
+
7
+ def initialize(rating, ranking)
8
+ self.rating = rating
9
+ self.ranking = ranking
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module Elo4m
2
+ class Rating
3
+ include Helper
4
+
5
+ attr_reader :other_rating
6
+ attr_reader :old_rating
7
+ attr_reader :k_factor
8
+ attr_reader :expected
9
+
10
+ def new_rating
11
+ (old_rating.to_f + change).to_i
12
+ end
13
+
14
+ # The expected score is the probably outcome of the match, depending
15
+ # on the difference in rating between the two players.
16
+ def expected_al
17
+ 1.0 / ( 1.0 + ( 10.0 ** ((other_rating.to_f - old_rating.to_f) / 400.0) ) )
18
+ end
19
+
20
+ private
21
+
22
+ def result
23
+ @result.to_f
24
+ end
25
+
26
+ def valid_result?
27
+ (0..1).include? @result
28
+ end
29
+
30
+ def change
31
+ expected = self.expected.nil? ? expected_al : self.expected
32
+ k_factor.to_f * ( result.to_f - expected )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Elo4m
2
+ VERSION = "0.0.1"
3
+ end
data/lib/elo4m.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "elo4m/version"
2
+ require "elo4m/helper"
3
+ require "elo4m/configuration"
4
+ require "elo4m/game"
5
+ require "elo4m/player"
6
+ require "elo4m/rating"
7
+
8
+ module Elo4m
9
+ def self.config
10
+ @config ||= Configuration.new
11
+ end
12
+
13
+ def self.configure(&block)
14
+ yield(config)
15
+ end
16
+ end
data/spec/elo_spec.rb ADDED
@@ -0,0 +1,98 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require "elo4m"
3
+
4
+ describe "Elo" do
5
+ after do
6
+ Elo4m.instance_eval { @config = nil }
7
+ end
8
+
9
+ =begin
10
+
11
+ Elo rating system - Wikipedia, the free encyclopedia
12
+ http://en.wikipedia.org/wiki/Elo_rating_system#Mathematical_details
13
+
14
+ R_A^' = R_A + K(S_A - E_A).
15
+ This update can be performed after each game or each tournament, or after any suitable rating period.
16
+ An example may help clarify. Suppose Player A has a rating of 1613, and plays in a five-round tournament.
17
+ He or she loses to a player rated 1609, draws with a player rated 1477, defeats a player rated 1388,
18
+ defeats a player rated 1586, and loses to a player rated 1720. The player's actual score is (0 + 0.5 + 1 + 1 + 0) = 2.5.
19
+ The expected score, calculated according to the formula above, was (0.506 + 0.686 + 0.785 + 0.539 + 0.351) = 2.867.
20
+ Therefore the player's new rating is (1613 + 32×(2.5 − 2.867)) = 1601, assuming that a K-factor of 32 is used.
21
+
22
+ =end
23
+ describe 'wikipedia-way' do
24
+ let(:players) do [
25
+ Elo4m::Player.new(1613, 4),
26
+ Elo4m::Player.new(1609, 2),
27
+ Elo4m::Player.new(1477, 4),
28
+ Elo4m::Player.new(1388, 6),
29
+ Elo4m::Player.new(1586, 6),
30
+ Elo4m::Player.new(1720, 2) ]
31
+ end
32
+
33
+ it do
34
+ game = Elo4m::Game.new(players)
35
+ expect(game.run.first).to eq(1601)
36
+ end
37
+ end
38
+
39
+ # describe 'for multi player' do
40
+ # let(:game) do
41
+ # [
42
+ # {user: 'alice', result: 0, rating: 1609},
43
+ # {user: 'alice', result: 0.5, rating: 1477},
44
+ # {user: 'alice', result: 1, rating: 1388},
45
+ # {user: 'alice', result: 1, rating: 1586},
46
+ # {user: 'alice', result: 0, rating: 1720},
47
+ # ]
48
+ # end
49
+ # let(:my_rating) { 1613 }
50
+ # let(:k_factor) { 32 }
51
+ #
52
+ # before do
53
+ # # test = Elo4m::Player.new(rating: 1500)
54
+ # # test.add_game_results(r)
55
+ # end
56
+ #
57
+ # describe 'kochi-way' do
58
+ # it do
59
+ # # ---------------------------
60
+ # # kochi way
61
+ # # ---------------------------
62
+ #
63
+ # res_kochi = game.inject(0.0) do |r, c|
64
+ # e = Elo4m::Rating.new(
65
+ # result: c[:result],
66
+ # old_rating: my_rating,
67
+ # other_rating: c[:rating],
68
+ # k_factor: k_factor
69
+ # ).new_rating
70
+ # r += e
71
+ # end / game.size
72
+ #
73
+ # expect(res_kochi).to eq(1610)
74
+ # end
75
+ # end
76
+ # describe 'wikipedia-way' do
77
+ # it do
78
+ # # ---------------------------
79
+ # # wikipedia way
80
+ # # ---------------------------
81
+ #
82
+ # result_sum = game.inject(0.0) {|r, c| r += c[:result]}
83
+ # expected_sum = game.inject(0.0) do |r, c|
84
+ # e = Elo4m::Rating.new(old_rating: my_rating, other_rating: c[:rating]).expected_al
85
+ # r += e
86
+ # end
87
+ # res_wiki = Elo4m::Rating.new(
88
+ # result: result_sum,
89
+ # old_rating: my_rating,
90
+ # expected: expected_sum,
91
+ # k_factor: k_factor
92
+ # ).new_rating
93
+ #
94
+ # expect(res_wiki).to eq(1601)
95
+ # end
96
+ # end
97
+ # end
98
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require "elo4m"
4
+ require "rspec"
data/spec/test.rb ADDED
@@ -0,0 +1,68 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require "elo4m"
3
+
4
+ def players(my_rating)
5
+ my_rank, other_ratings = rand_rank_ratings(my_rating)
6
+ other_ratings.map do |k, v|
7
+ Elo4m::Player.new(k, v)
8
+ end.unshift Elo4m::Player.new(my_rating, my_rank)
9
+ end
10
+
11
+ def rand_rank_ratings(my_rating)
12
+ i = 1
13
+ ratings = []
14
+ while ratings.flatten.length != 6
15
+ ratings = 5.times.map { rand(my_rating-30..my_rating+30) }
16
+ ratings.push my_rating
17
+ end
18
+ rank_rating = ratings.sort.reverse.each_with_object({}) do |(k, v), h|
19
+ h[k] = i
20
+ i += 1
21
+ end
22
+ # => {1522=>1, 1521=>2, 1520=>3, 1506=>4, 1500=>5, 1490=>6}
23
+
24
+ rand_rank_ratings = Hash[rank_rating.to_a.shuffle]
25
+ my_rank = rand_rank_ratings[my_rating]
26
+
27
+ rand_rank_ratings.delete(my_rating)
28
+ return my_rank, rand_rank_ratings
29
+ end
30
+
31
+ require 'gnuplot'
32
+
33
+ j = 0
34
+ def execute(my_rating, j)
35
+ return my_rating if j == 300
36
+ ps = players(my_rating)
37
+ game = Elo4m::Game.new(ps)
38
+ new_rating = game.run.first
39
+ j += 1
40
+ execute(new_rating, j)
41
+ end
42
+
43
+ res = []
44
+ 1000.times do |i|
45
+ p i
46
+ res << execute(1500, 0)
47
+ end
48
+ p res = res.sort
49
+
50
+ Gnuplot.open do |gp|
51
+ Gnuplot::Plot.new( gp ) do |plot|
52
+ plot.title 'elo'
53
+ plot.ylabel 'x'
54
+ plot.xlabel 'y'
55
+ plot.set "size ratio 1"
56
+ plot.terminal("png")
57
+ plot.output("y_eq_x2.png")
58
+ plot.set "linestyle 1 linecolor rgbcolor 'blue' linetype 1"
59
+ x = res
60
+ y = 1..1000
61
+
62
+ plot.data << Gnuplot::DataSet.new( [x, y] ) do |ds|
63
+ ds.with = "lines ls 1"
64
+ ds.linewidth = 4
65
+ ds.notitle
66
+ end
67
+ end
68
+ end
data/y_eq_x2.png ADDED
Binary file
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elo4m
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - kidach1
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: gnuplot
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: For calculating the relative skill levels of players in multi-player
70
+ games.
71
+ email:
72
+ - daiki.taniguchi@aktsk.jp
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - elo4m.gemspec
83
+ - lib/elo4m.rb
84
+ - lib/elo4m/configuration.rb
85
+ - lib/elo4m/game.rb
86
+ - lib/elo4m/helper.rb
87
+ - lib/elo4m/player.rb
88
+ - lib/elo4m/rating.rb
89
+ - lib/elo4m/version.rb
90
+ - spec/elo_spec.rb
91
+ - spec/spec_helper.rb
92
+ - spec/test.rb
93
+ - y_eq_x2.png
94
+ homepage: ''
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.4.1
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: For calculating the relative skill levels of players in multi-player games.
118
+ test_files:
119
+ - spec/elo_spec.rb
120
+ - spec/spec_helper.rb
121
+ - spec/test.rb
122
+ has_rdoc: