Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 5 commits
  • 6 files changed
  • 0 comments
  • 2 contributors
22 README.md
Source Rendered
@@ -7,22 +7,22 @@ Usage
7 7 -----
8 8
9 9 Example:
10   -
  10 +
11 11 require 'rubygems'
12 12 require 'saulabs/trueskill'
13   -
  13 +
14 14 include Saulabs::TrueSkill
15   -
  15 +
16 16 # team 1 has just one player with a mean skill of 27.1, a skill-deviation of 2.13
17 17 # and an play activity of 100 %
18 18 team1 = [Rating.new(27.1, 2.13, 1.0)]
19   -
  19 +
20 20 # team 2 has two players
21 21 team2 = [Rating.new(22.0, 0.98, 0.8), Rating.new(31.1, 5.33, 0.9)]
22   -
  22 +
23 23 # team 1 finished first and team 2 second
24   - graph = FactorGraph.new([team1, team2], [1,2])
25   -
  24 + graph = FactorGraph.new(team1 => 1, team2 => 2)
  25 +
26 26 # update the Ratings
27 27 graph.update_skills
28 28
@@ -34,7 +34,7 @@ To install the TrueSkill gem, simply run
34 34 [sudo] gem install trueskill
35 35
36 36 Add the following to your script:
37   -
  37 +
38 38 require 'saulabs/trueskill'
39 39
40 40 Known issues
@@ -45,11 +45,11 @@ Known issues
45 45 Plans
46 46 -----
47 47
48   -*
  48 +*
49 49
50 50 Note on Patches/Pull Requests
51 51 -----------------------------
52   -
  52 +
53 53 * Fork the project.
54 54 * Make your feature addition or bug fix.
55 55 * Add tests for it. This is important so I don't break it in a
@@ -61,6 +61,6 @@ Note on Patches/Pull Requests
61 61 Copyright
62 62 ---------
63 63
64   -© 2010 Lars Kuhnt (<http://saulabs.net>).
  64 +© 2010 Lars Kuhnt (<http://saulabs.net>).
65 65
66 66 See LICENSE for details.
69 lib/saulabs/trueskill/factor_graph.rb
... ... @@ -1,38 +1,38 @@
1 1 # -*- encoding : utf-8 -*-
2 2 module Saulabs
3 3 module TrueSkill
4   -
  4 +
5 5 class FactorGraph
6   -
  6 +
7 7 # @return [Array<Array<TrueSkill::Rating>>]
8 8 attr_reader :teams
9   -
  9 +
10 10 # @return [Float]
11 11 attr_reader :beta
12   -
  12 +
13 13 # @return [Float]
14 14 attr_reader :beta_squared
15   -
  15 +
16 16 # @return [Float]
17 17 attr_reader :draw_probability
18   -
  18 +
19 19 # @return [Float]
20 20 attr_reader :epsilon
21   -
  21 +
22 22 # @private
23 23 attr_reader :layers
24 24
25 25 # @return [Boolean]
26 26 attr_reader :skills_additive
27   -
28   -
  27 +
  28 +
29 29 # Creates a new trueskill factor graph for calculating the new skills based on the given game parameters
30 30 #
31   - # @param [Array<Array<TrueSkill::Rating>>] teams
  31 + # @param [Array<Array<TrueSkill::Rating>>] teams
32 32 # player-ratings grouped in Arrays by teams
33   - # @param [Array<Integer>] ranks
  33 + # @param [Array<Integer>] ranks
34 34 # team rankings, example: [2,1,3] first team in teams finished 2nd, second team 1st and third team 3rd
35   - # @param [Hash] options
  35 + # @param [Hash] options
36 36 # the options hash to configure the factor graph constants beta, draw_probability and skills_additive
37 37 #
38 38 # @option options [Float] :beta (4.166667)
@@ -44,30 +44,31 @@ class FactorGraph
44 44 # true is valid for games like Halo etc, where skill is additive (2 players are better than 1),
45 45 # false for card games like Skat, Doppelkopf, Bridge where skills are not additive,
46 46 # two players dont make the team stronger, skills averaged)
47   - #
  47 + #
48 48 # @example Calculating new skills of a two team game, where one team has one player and the other two
49 49 #
50 50 # require 'rubygems'
51 51 # require 'saulabs/trueskill'
52   - #
  52 + #
53 53 # include Saulabs::TrueSkill
54   - #
  54 + #
55 55 # # team 1 has just one player with a mean skill of 27.1, a skill-deviation of 2.13
56 56 # # and a play activity of 100 %
57 57 # team1 = [Rating.new(27.1, 2.13, 1.0)]
58   - #
  58 + #
59 59 # # team 2 has two players
60 60 # team2 = [Rating.new(22.0, 0.98, 0.8), Rating.new(31.1, 5.33, 0.9)]
61   - #
  61 + #
62 62 # # team 1 finished first and team 2 second
63   - # graph = FactorGraph.new([team1, team2], [1,2])
64   - #
  63 + # graph = FactorGraph.new( team1 => 1, team2 => 2 )
  64 + #
65 65 # # update the Ratings
66 66 # graph.update_skills
67 67 #
68   - def initialize(teams, ranks, options = {})
69   - @teams = teams
70   - @ranks = ranks
  68 + def initialize(ranks_teams_hash, options = {})
  69 + @teams = ranks_teams_hash.keys
  70 + @ranks = ranks_teams_hash.values
  71 +
71 72 opts = {
72 73 :beta => 25/6.0,
73 74 :draw_probability => 0.1,
@@ -79,7 +80,7 @@ def initialize(teams, ranks, options = {})
79 80 @beta_squared = @beta**2
80 81 @epsilon = -Math.sqrt(2.0 * @beta_squared) * Gauss::Distribution.inv_cdf((1.0 - @draw_probability) / 2.0)
81 82 @skills_additive = opts[:skills_additive]
82   -
  83 +
83 84 @prior_layer = Layers::PriorToSkills.new(self, @teams)
84 85 @layers = [
85 86 @prior_layer,
@@ -87,17 +88,17 @@ def initialize(teams, ranks, options = {})
87 88 Layers::PerformancesToTeamPerformances.new(self, @skills_additive),
88 89 Layers::IteratedTeamPerformances.new(self,
89 90 Layers::TeamPerformanceDifferences.new(self),
90   - Layers::TeamDifferenceComparision.new(self, ranks)
  91 + Layers::TeamDifferenceComparision.new(self, @ranks)
91 92 )
92 93 ]
93 94 end
94   -
  95 +
95 96 def draw_margin
96 97 Gauss::Distribution.inv_cdf(0.5*(@draw_probability + 1)) * Math.sqrt(1 + 1) * @beta
97 98 end
98   -
  99 +
99 100 # Updates the skills of the players inplace
100   - #
  101 + #
101 102 # @return [Float] the probability of the games outcome
102 103 def update_skills
103 104 build_layers
@@ -109,9 +110,9 @@ def update_skills
109 110 end
110 111 ranking_probability
111 112 end
112   -
  113 +
113 114 private
114   -
  115 +
115 116 def ranking_probability
116 117 # factor_list = []
117 118 # sum_log_z, sum_log_s = 0.0
@@ -125,11 +126,11 @@ def ranking_probability
125 126 # Math.exp(sum_log_z + sum_log_s)
126 127 0.0
127 128 end
128   -
  129 +
129 130 def updated_skills
130 131 @prior_layer.output
131 132 end
132   -
  133 +
133 134 def build_layers
134 135 output = nil
135 136 @layers.each do |layer|
@@ -138,13 +139,13 @@ def build_layers
138 139 output = layer.output
139 140 end
140 141 end
141   -
  142 +
142 143 def run_schedule
143 144 schedules = @layers.map(&:prior_schedule) + @layers.reverse.map(&:posterior_schedule)
144 145 Schedules::Sequence.new(schedules.compact).visit
145 146 end
146   -
  147 +
147 148 end
148   -
  149 +
149 150 end
150 151 end
12 lib/saulabs/trueskill/layers/prior_to_skills.rb
@@ -3,15 +3,15 @@ module Saulabs
3 3 module TrueSkill
4 4 # @private
5 5 module Layers
6   -
  6 +
7 7 # @private
8 8 class PriorToSkills < Base
9   -
  9 +
10 10 def initialize(graph, teams)
11 11 super(graph)
12 12 @teams = teams
13 13 end
14   -
  14 +
15 15 def build
16 16 @teams.each do |team|
17 17 team_skills = []
@@ -23,13 +23,13 @@ def build
23 23 @output << team_skills
24 24 end
25 25 end
26   -
  26 +
27 27 def prior_schedule
28 28 Schedules::Sequence.new(@factors.map { |f| Schedules::Step.new(f, 0) })
29 29 end
30   -
  30 +
31 31 end
32   -
  32 +
33 33 end
34 34 end
35 35 end
172 spec/saulabs/trueskill/factor_graph_spec.rb
... ... @@ -1,138 +1,132 @@
1 1 # -*- encoding : utf-8 -*-
2 2 require File.expand_path('spec/spec_helper.rb')
3 3
4   -describe Saulabs::TrueSkill::FactorGraph do
5   -
  4 +describe Saulabs::TrueSkill::FactorGraph, "Unit Tests" do
  5 +
6 6 before :each do
7 7 @teams = create_teams
8   - @skill = @teams.first.first
9   - @graph = TrueSkill::FactorGraph.new(@teams, [1,2,3])
  8 + @skill = @teams[0][0]
  9 + @results = { @team1 => 1, @team2 => 2, @team3 => 3 }
  10 + @graph = TrueSkill::FactorGraph.new(@results)
10 11 end
11   -
  12 +
12 13 describe "#update_skills" do
13   -
14 14 it "should update the mean of the first player in team1 to 30.38345" do
15 15 @graph.update_skills
16 16 @skill.mean.should be_within(tolerance).of(30.38345)
17 17 end
18   -
  18 +
19 19 it "should update the deviation of the first player in team1 to 3.46421" do
20 20 @graph.update_skills
21 21 @skill.deviation.should be_within(tolerance).of(3.46421)
22 22 end
23   -
24 23 end
25   -
  24 +
26 25 describe "#draw_margin" do
27   -
28 26 it "should be -0.998291 for diff 0.740466" do
29 27 @graph.draw_margin.should be_within(tolerance).of(0.740466)
30 28 end
31   -
32 29 end
33   -
34 30 end
35 31
36   -describe Saulabs::TrueSkill::FactorGraph, "two players" do
37   -
38   - before :each do
39   - @teams = [
40   - [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)],
  32 +describe Saulabs::TrueSkill::FactorGraph, "Integration Tests" do
  33 + context "When there are two teams" do
  34 + let :team1 do # Each team needs unique instances as we modify by side effect
41 35 [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
42   - ]
43   - end
44   -
45   - describe 'win with standard rating' do
46   -
47   - before :each do
48   - TrueSkill::FactorGraph.new(@teams, [1,2]).update_skills
49   - end
50   -
51   - it "should change first players rating to [29.395832, 7.1714755]" do
52   - @teams[0][0].should eql_rating(29.395832, 7.1714755)
53   - end
54   -
55   - it "should change second players rating to [20.6041679, 7.1714755]" do
56   - @teams[1][0].should eql_rating(20.6041679, 7.1714755)
57   - end
58   -
59   - end
60   -
61   - describe 'draw with standard rating' do
62   -
63   - before :each do
64   - TrueSkill::FactorGraph.new(@teams, [1,1]).update_skills
65   - end
66   -
67   - it "should change first players rating to [25.0, 6.4575196]" do
68   - @teams[0][0].should eql_rating(25.0, 6.4575196)
69 36 end
70   -
71   - it "should change second players rating to [25.0, 6.4575196]" do
72   - @teams[1][0].should eql_rating(25.0, 6.4575196)
73   - end
74   -
75   - end
76   -
77   - describe 'draw with different ratings' do
78   -
79   - before :each do
80   - @teams[1][0] = TrueSkill::Rating.new(50.0, 12.5, 1.0, 25.0/300.0)
81   - TrueSkill::FactorGraph.new(@teams, [1,1]).update_skills
  37 +
  38 + let :team2 do # Each team needs unique instances as we modify by side effect
  39 + [TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)]
82 40 end
83   -
84   - it "should change first players rating to [31.6623, 7.1374]" do
85   - @teams[0][0].should eql_rating(31.662301, 7.1374459)
  41 +
  42 + let :teams do
  43 + [team1,team2]
86 44 end
87   -
88   - it "should change second players mean to [35.0107, 7.9101]" do
89   - @teams[1][0].should eql_rating(35.010653, 7.910077)
  45 +
  46 + let :results do
  47 + { team1 => 1, team2 => 2 }
90 48 end
91   -
92   - end
93   -
94   -end
95 49
96   -describe Saulabs::TrueSkill::FactorGraph, "1 vs 2, skills are additive, standard rating" do
  50 + let(:draw_results) do
  51 + { team1 => 1, team2 => 1 }
  52 + end
  53 + context "and exactly two players" do
97 54
  55 + describe 'team1 win with standard rating' do
98 56
99   -
100   - before :each do
101   - @teams = [
102   - [
103   - TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0),
  57 + before :each do
  58 + TrueSkill::FactorGraph.new(results).update_skills
  59 + end
104 60
105   - ],
106   - [
107   - TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0),
108   - TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)
  61 + it "should change first players rating to [29.395832, 7.1714755]" do
  62 + teams[0][0].should eql_rating(29.395832, 7.1714755)
  63 + end
109 64
  65 + it "should change second players rating to [20.6041679, 7.1714755]" do
  66 + teams[1][0].should eql_rating(20.6041679, 7.1714755)
  67 + end
110 68
111   - ]
112   - ]
  69 + end
113 70 end
114 71
115   - describe "#@skill_update" do
116   -
117   -
118   - it "should have a Boolean @skills_additive = false" do
119   - @graph = TrueSkill::FactorGraph.new(@teams, [1,1], {:skills_additive => false})
120   - @graph.skills_additive.should be_false
  72 + describe 'draw with standard rating' do
  73 +
  74 + before :each do
  75 + TrueSkill::FactorGraph.new(draw_results).update_skills
121 76 end
122 77
  78 + it "should change first players rating to [25.0, 6.4575196]" do
  79 + teams[0][0].should eql_rating(25.0, 6.4575196)
  80 + end
123 81
  82 + it "should change second players rating to [25.0, 6.4575196]" do
  83 + teams[1][0].should eql_rating(25.0, 6.4575196)
  84 + end
  85 +
  86 + end
  87 +
  88 + describe 'draw with different ratings' do
  89 + let :team2 do
  90 + [TrueSkill::Rating.new(50.0, 12.5, 1.0, 25.0/300.0)]
  91 + end
124 92
125   - it "should update the mean of the first player in team1 to 25.0 after draw" do
126   - @graph = TrueSkill::FactorGraph.new(@teams, [1,1], {:skills_additive => false})
  93 + before :each do
  94 + TrueSkill::FactorGraph.new(draw_results).update_skills
  95 + end
127 96
128   - @graph.update_skills
129   - @teams[0][0].mean.should be_within(tolerance).of(25.0)
  97 + it "should change first players rating to [31.6623, 7.1374]" do
  98 + teams[0][0].should eql_rating(31.662301, 7.1374459)
  99 + end
130 100
  101 + it "should change second players mean to [35.0107, 7.9101]" do
  102 + teams[1][0].should eql_rating(35.010653, 7.910077)
131 103 end
132   -
133 104
  105 + end
134 106
  107 + context "when it is a 1 vs 2" do
  108 + let :team2 do
  109 + [
  110 + TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0),
  111 + TrueSkill::Rating.new(25.0, 25.0/3.0, 1.0, 25.0/300.0)
  112 + ]
  113 + end
  114 +
  115 + context "and the skills are additive" do
  116 + describe "#@skill_update" do
  117 + it "should have a Boolean @skills_additive = false" do
  118 + @graph = TrueSkill::FactorGraph.new(draw_results, {:skills_additive => false})
  119 + @graph.skills_additive.should be_false
  120 + end
135 121
  122 + it "should update the mean of the first player in team1 to 25.0 after draw" do
  123 + @graph = TrueSkill::FactorGraph.new(draw_results, {:skills_additive => false})
136 124
  125 + @graph.update_skills
  126 + teams[0][0].mean.should be_within(tolerance).of(25.0)
  127 + end
  128 + end
  129 + end
  130 + end
137 131 end
138 132 end
27 spec/saulabs/trueskill/layers/prior_to_skills_spec.rb
@@ -2,39 +2,40 @@
2 2 require File.expand_path('spec/spec_helper.rb')
3 3
4 4 describe TrueSkill::Layers::PriorToSkills do
5   -
6   - before :each do
  5 +
  6 + before :each do
7 7 @teams = create_teams
8   - @graph = TrueSkill::FactorGraph.new(@teams, [1,2,3])
  8 + @results = {@team1 => 1, @team2 => 2, @team3 => 3}
  9 + @graph = TrueSkill::FactorGraph.new(@results)
9 10 @layer = TrueSkill::Layers::PriorToSkills.new(@graph, @teams)
10 11 end
11   -
  12 +
12 13 describe "#build" do
13   -
  14 +
14 15 it "should add 4 factors" do
15 16 lambda {
16 17 @layer.build
17 18 }.should change(@layer.factors, :size).by(4)
18 19 end
19   -
  20 +
20 21 it "should add 3 output variables" do
21 22 lambda {
22 23 @layer.build
23 24 }.should change(@layer.output, :size).by(3)
24 25 end
25   -
  26 +
26 27 end
27   -
  28 +
28 29 describe "#prior_schedule" do
29   -
30   - before :each do
  30 +
  31 + before :each do
31 32 @layer.build
32 33 end
33   -
  34 +
34 35 it "should return a sequence-schedule" do
35 36 @layer.prior_schedule.should be_kind_of(TrueSkill::Schedules::Sequence)
36 37 end
37   -
  38 +
38 39 end
39   -
  40 +
40 41 end
16 spec/spec_helper.rb
@@ -15,16 +15,8 @@ def tolerance
15 15 end
16 16
17 17 def create_teams
18   - [
19   - [
20   - TrueSkill::Rating.new(25, 4.1)
21   - ],
22   - [
23   - TrueSkill::Rating.new(27, 3.1),
24   - TrueSkill::Rating.new(10, 1.0)
25   - ],
26   - [
27   - TrueSkill::Rating.new(32, 0.2)
28   - ]
29   - ]
  18 + @team1 = [ TrueSkill::Rating.new(25, 4.1) ]
  19 + @team2 = [ TrueSkill::Rating.new(27, 3.1), TrueSkill::Rating.new(10, 1.0) ]
  20 + @team3 = [ TrueSkill::Rating.new(32, 0.2) ]
  21 + [@team1, @team2, @team3]
30 22 end

No commit comments for this range

Something went wrong with that request. Please try again.