icu_ratings 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- :patch: 6
2
+ :patch: 7
3
3
  :build:
4
4
  :major: 0
5
5
  :minor: 2
@@ -71,7 +71,7 @@ _rating_, _kfactor_ or _games_.
71
71
 
72
72
  p = t.add_player(5)
73
73
  p.type # :unrated
74
-
74
+
75
75
  == Invalid Combinations
76
76
 
77
77
  The above four types of players (_rated_, _provisional_, _unrated_, _foreign_) are the only
@@ -142,7 +142,7 @@ method.
142
142
  def expected_score
143
143
  @results.inject(0.0) { |e, r| e + (r.expected_score || 0.0) }
144
144
  end
145
-
145
+
146
146
  # After the tournament has been rated, this returns the tournament rating performance for
147
147
  # rated, unrated and foreign players. For provisional players it returns a weighted average
148
148
  # of the player's tournament performance and their previous games. For provisional and
@@ -150,7 +150,7 @@ method.
150
150
  def performance
151
151
  @performance
152
152
  end
153
-
153
+
154
154
  # Returns an array of the player's results (ICU::RatedResult) in round order.
155
155
  def results
156
156
  @results
@@ -160,12 +160,12 @@ method.
160
160
  def score
161
161
  @results.inject(0.0) { |e, r| e + r.score }
162
162
  end
163
-
163
+
164
164
  # Returns the type of player as a symbol: one of _rated_, _provisional_, _unrated_ or _foreign_.
165
165
  def type
166
166
  @type
167
167
  end
168
-
168
+
169
169
  def add_result(result) # :nodoc:
170
170
  raise "invalid result (#{result.class})" unless result.is_a? ICU::RatedResult
171
171
  raise "players cannot score results against themselves" if self == result.opponent
@@ -180,7 +180,7 @@ method.
180
180
  @results << result
181
181
  @results.sort!{ |a,b| a.round <=> b.round }
182
182
  end
183
-
183
+
184
184
  def rate! # :nodoc:
185
185
  @results.each { |r| r.rate!(self) }
186
186
  end
@@ -98,24 +98,24 @@ _expected_score_, _rating_change_.
98
98
  def round
99
99
  @round
100
100
  end
101
-
101
+
102
102
  # The player's opponent (an instance of ICU::RatedPlayer).
103
103
  def opponent
104
104
  @opponent
105
105
  end
106
-
106
+
107
107
  # The player's score in this game (1.0, 0.5 or 0.0).
108
108
  def score
109
109
  @score
110
110
  end
111
-
111
+
112
112
  # After the tournament has been rated, this returns the expected score (between 0 and 1)
113
113
  # for the player based on the rating difference with the opponent scaled by 400.
114
114
  # The standard Elo formula is used: 1/(1 + 10^(diff/400)).
115
115
  def expected_score
116
116
  @expected_score
117
117
  end
118
-
118
+
119
119
  # After the tournament has been rated, returns the change in rating due to this particular
120
120
  # result. Only for rated players (returns _nil_ for other types of players). Computed from
121
121
  # the difference between actual and expected scores multiplied by the player's K-factor.
@@ -123,7 +123,7 @@ _expected_score_, _rating_change_.
123
123
  def rating_change
124
124
  @rating_change
125
125
  end
126
-
126
+
127
127
  def rate!(player) # :nodoc:
128
128
  player_rating = player.full_rating? ? player.rating : player.performance
129
129
  opponent_rating = opponent.full_rating? ? opponent.rating : opponent.performance
@@ -10,11 +10,20 @@ ICU::RatedTournament object are created directly.
10
10
 
11
11
  t = ICU::RatedTournament.new
12
12
 
13
- They have one optional parameter called _:desc_ (short for description) the value of which can be
13
+ They have two optional parameters. One is called _desc_ (short for description) the value of which can be
14
14
  any object but will, if utilized, typically be the name of the tournament as a string.
15
15
 
16
- t = ICU::RatedTournament.new(:desc => "Irish Championships 2008")
17
- puts t.desc # "Irish Championships 2008"
16
+ t = ICU::RatedTournament.new(:desc => "Irish Championships 2010")
17
+ puts t.desc # "Irish Championships 2010"
18
+
19
+ The other optional parameter is _start_ for the start date. A Date object or a string that can be
20
+ parsed as a string can be used to set it. The European convention is preferred for dates like
21
+ "03/06/2013" (3rd of June, not 6th of March). Attempting to set an invalid date will raise an
22
+ exception.
23
+
24
+ t = ICU::RatedTournament.new(:start => "01/07/2010")
25
+ puts t.start.class # Date
26
+ puts t.start.to_s # "2010-07-01"
18
27
 
19
28
  == Rating Tournaments
20
29
 
@@ -55,6 +64,7 @@ to be caught and handled in some suitable place in your code.
55
64
 
56
65
  class RatedTournament
57
66
  attr_accessor :desc
67
+ attr_reader :start
58
68
 
59
69
  # Add a new player to the tournament. Returns the instance of ICU::RatedPlayer created.
60
70
  # See ICU::RatedPlayer for details.
@@ -94,14 +104,19 @@ to be caught and handled in some suitable place in your code.
94
104
  @player[num]
95
105
  end
96
106
 
107
+ # Set the start date. Raises exception on error.
108
+ def start=(date)
109
+ @start = ICU::Util.parsedate!(date)
110
+ end
111
+
97
112
  private
98
113
 
99
114
  # Create a new, empty (no players, no results) tournament.
100
115
  def initialize(opt={})
101
- [:desc].each { |atr| self.send("#{atr}=", opt[atr]) unless opt[atr].nil? }
116
+ [:desc, :start].each { |atr| self.send("#{atr}=", opt[atr]) unless opt[atr].nil? }
102
117
  @player = Hash.new
103
118
  end
104
-
119
+
105
120
  def performance_ratings
106
121
  @player.values.each { |p| p.init_performance }
107
122
  stable, count = false, 0
@@ -0,0 +1,35 @@
1
+ require 'date' # needed for 1.9.1
2
+
3
+ module ICU
4
+ class Util
5
+
6
+ =begin rdoc
7
+
8
+ == Parsing dates
9
+
10
+ Parse strings into date objects, interpreting nn/nn/nnnn as dd/mm/yyyy. Raises exception on error.
11
+
12
+ Util.parsedate!('1955-11-09') # => Date (1955-11-09)
13
+ Util.parsedate!('02/03/2009') # => Date (2009-03-02)
14
+ Util.parsedate!('02/23/2009') # => Date (2009-02-23)
15
+ Util.parsedate!('16th June 1986') # => Date (1986-06-16)
16
+ Util.parsedate!('not a date') # exception raised
17
+
18
+ Note that the parse method of the Date class behaves differently in Ruby 1.8.7 and 1.9.1.
19
+ In 1.8.7 it assumes American dates and will raise ArgumentError on "30/03/2003".
20
+ In 1.9.1 it assumes European dates and will raise ArgumentError on "03/30/2003".
21
+
22
+ =end
23
+
24
+ def self.parsedate!(date)
25
+ string = date.to_s.strip
26
+ raise "invalid date (#{date})" unless string.match(/[1-9]/)
27
+ string = [$3].concat($2.to_i > 12 ? [$1, $2] : [$2, $1]).join('-') if string.match(/^(\d{1,2}).(\d{1,2}).(\d{4})$/)
28
+ begin
29
+ Date.parse(string, true)
30
+ rescue
31
+ raise "invalid date (#{date})"
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/icu_ratings.rb CHANGED
@@ -1,8 +1,3 @@
1
1
  # :enddoc:
2
-
3
- icu_ratings_files = Array.new
4
- icu_ratings_files.concat %w{tournament player result}
5
-
6
2
  dir = File.dirname(__FILE__)
7
-
8
- icu_ratings_files.each { |file| require "#{dir}/icu_ratings/#{file}" }
3
+ %w{tournament player result util}.each { |f| require "#{dir}/icu_ratings/#{f}" }
data/spec/player_spec.rb CHANGED
@@ -10,7 +10,7 @@ module ICU
10
10
  @f = ICU::RatedPlayer.new(3, :rating => 2500)
11
11
  @u = ICU::RatedPlayer.new(4)
12
12
  end
13
-
13
+
14
14
  it "rated players have a rating and k-factor" do
15
15
  @r.num.should == 1
16
16
  @r.rating.should == 2000
@@ -19,7 +19,7 @@ module ICU
19
19
  @r.type.should == :rated
20
20
  @r.full_rating?.should be_true
21
21
  end
22
-
22
+
23
23
  it "provisionally rated players have a rating and number of games" do
24
24
  @p.num.should == 2
25
25
  @p.rating.should == 1500
@@ -28,7 +28,7 @@ module ICU
28
28
  @p.type.should == :provisional
29
29
  @p.full_rating?.should be_false
30
30
  end
31
-
31
+
32
32
  it "foreign players just have a rating" do
33
33
  @f.num.should == 3
34
34
  @f.rating.should == 2500
@@ -37,7 +37,7 @@ module ICU
37
37
  @f.type.should == :foreign
38
38
  @f.full_rating?.should be_true
39
39
  end
40
-
40
+
41
41
  it "unrated players just have nothing other than their number" do
42
42
  @u.num.should == 4
43
43
  @u.rating.should be_nil
@@ -46,7 +46,7 @@ module ICU
46
46
  @u.type.should == :unrated
47
47
  @u.full_rating?.should be_false
48
48
  end
49
-
49
+
50
50
  it "other combinations are invalid" do
51
51
  [
52
52
  { :games => 10 },
@@ -56,7 +56,7 @@ module ICU
56
56
  ].each { |opts| lambda { ICU::RatedPlayer.new(1, opts) }.should raise_error(/invalid.*combination/i) }
57
57
  end
58
58
  end
59
-
59
+
60
60
  context "#new - miscellaneous" do
61
61
  it "attribute values can be given by strings, even when space padded" do
62
62
  p = ICU::RatedPlayer.new(' 1 ', :kfactor => ' 10.0 ', :rating => ' 1000 ')
@@ -81,19 +81,19 @@ module ICU
81
81
  lambda { ICU::RatedPlayer.new(1, :rating => 0) }.should_not raise_error
82
82
  lambda { ICU::RatedPlayer.new(1, :rating => -1) }.should_not raise_error
83
83
  end
84
-
84
+
85
85
  it "ratings are stored as floats but can be specified with an integer" do
86
86
  ICU::RatedPlayer.new(1, :rating => 1234.5).rating.should == 1234.5
87
87
  ICU::RatedPlayer.new(1, :rating => 1234.0).rating.should == 1234
88
88
  ICU::RatedPlayer.new(1, :rating => 1234).rating.should == 1234
89
89
  end
90
-
90
+
91
91
  it "the number of games shoud not exceed 20" do
92
92
  lambda { ICU::RatedPlayer.new(1, :rating => 1000, :games => 19) }.should_not raise_error
93
93
  lambda { ICU::RatedPlayer.new(1, :rating => 1000, :games => 20) }.should raise_error
94
94
  lambda { ICU::RatedPlayer.new(1, :rating => 1000, :games => 21) }.should raise_error
95
95
  end
96
-
96
+
97
97
  it "a description, such as a name, but can be any object, is optional" do
98
98
  ICU::RatedPlayer.new(1, :desc => 'Fischer, Robert').desc.should == 'Fischer, Robert'
99
99
  ICU::RatedPlayer.new(1, :desc => 1).desc.should be_an_instance_of(Fixnum)
@@ -124,7 +124,7 @@ module ICU
124
124
  @p.results[1].should == @r2
125
125
  @p.results[2].should == @r3
126
126
  end
127
-
127
+
128
128
  it "the total score should stay consistent with results as they are added" do
129
129
  @p.score.should == 0.0
130
130
  @p.add_result(@r1)
@@ -135,18 +135,18 @@ module ICU
135
135
  @p.score.should == 1.5
136
136
  end
137
137
  end
138
-
138
+
139
139
  context "Rdoc examples" do
140
140
  before(:each) do
141
141
  @t = ICU::RatedTournament.new
142
142
  @t.add_player(1)
143
143
  end
144
-
144
+
145
145
  it "the same player number can't be added twice" do
146
146
  lambda { @t.add_player(2) }.should_not raise_error
147
147
  lambda { @t.add_player(2) }.should raise_error
148
148
  end
149
-
149
+
150
150
  it "parameters can be specified using strings, even with whitespace padding" do
151
151
  p = @t.add_player(" 0 ", :rating => " 2000.5 ", :kfactor => " 20.5 ")
152
152
  p.num.should == 0
@@ -159,11 +159,11 @@ module ICU
159
159
  p.games.should == 15
160
160
  p.games.should be_an_instance_of(Fixnum)
161
161
  end
162
-
162
+
163
163
  it "the games parameter should not exceed 20" do
164
164
  lambda { @t.add_player(2, :rating => 1500, :games => 20 ) }.should raise_error
165
165
  end
166
-
166
+
167
167
  it "adding different player types" do
168
168
  p = @t.add_player(3, :rating => 2000, :kfactor => 16)
169
169
  p.type.should == :rated
data/spec/result_spec.rb CHANGED
@@ -7,7 +7,7 @@ module ICU
7
7
  before(:all) do
8
8
  @o = ICU::RatedPlayer.new(2)
9
9
  end
10
-
10
+
11
11
  it "needs a round, opponent and score (win, loss or draw)" do
12
12
  r = ICU::RatedResult.new(1, @o, 'W')
13
13
  r.round.should == 1
@@ -20,7 +20,7 @@ module ICU
20
20
  before(:each) do
21
21
  @p = ICU::RatedPlayer.new(2)
22
22
  end
23
-
23
+
24
24
  it "round numbers must be positive" do
25
25
  lambda { ICU::RatedResult.new(0, 1, 'W') }.should raise_error(/invalid.*round number/i)
26
26
  lambda { ICU::RatedResult.new(-1, 1, 'W') }.should raise_error(/invalid.*round number/i)
@@ -43,7 +43,7 @@ module ICU
43
43
  before(:each) do
44
44
  @p = ICU::RatedPlayer.new(2)
45
45
  end
46
-
46
+
47
47
  it "should give the score from the opponent's perspective" do
48
48
  ICU::RatedResult.new(1, @p, 'W').opponents_score.should == 0.0
49
49
  ICU::RatedResult.new(1, @p, 'L').opponents_score.should == 1.0
@@ -69,7 +69,7 @@ module ICU
69
69
  (@r1 == @r5).should be_false
70
70
  end
71
71
  end
72
-
72
+
73
73
  context "Rdoc examples" do
74
74
  before(:each) do
75
75
  @t = ICU::RatedTournament.new
@@ -89,16 +89,16 @@ module ICU
89
89
  @t.player(10).results.size.should == 1
90
90
  @t.player(20).results.size.should == 1
91
91
  end
92
-
92
+
93
93
  it "adding results against other players in the same round will cause an exception" do
94
94
  lambda { @t.add_result(1, 10, 30, 'W') }.should raise_error(/inconsistent/i)
95
95
  lambda { @t.add_result(1, 10, 20, 'L') }.should raise_error(/inconsistent/i)
96
96
  end
97
-
97
+
98
98
  it "a player cannot have a result against himself/herself" do
99
99
  lambda { @t.add_result(2, 10, 10, 'D') }.should raise_error(/players.*cannot.*sel[fv]/i)
100
100
  end
101
-
101
+
102
102
  it "results are returned in score order irrespecive of the order they're added in" do
103
103
  @t.player(0).results.map{ |r| r.round }.join(',').should == "1,2,3,4"
104
104
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
- require File.dirname(__FILE__) + '/../lib/icu_ratings'
3
+ require File.dirname(__FILE__) + '/../lib/icu_ratings'
@@ -9,6 +9,22 @@ module ICU
9
9
  ICU::RatedTournament.new(:desc => 1.0).desc.should be_an_instance_of(Float)
10
10
  ICU::RatedTournament.new.desc.should be_nil
11
11
  end
12
+
13
+ it "a tournament can have an optional start date" do
14
+ ICU::RatedTournament.new(:start => '2010-01-01').start.should be_a(Date)
15
+ ICU::RatedTournament.new(:start => '03/06/2013').start.to_s.should == '2013-06-03'
16
+ ICU::RatedTournament.new(:start => Date.parse('1955-11-09')).start.to_s.should == '1955-11-09'
17
+ ICU::RatedTournament.new.start.should be_nil
18
+ lambda { ICU::RatedTournament.new(:start => 'error') }.should raise_error
19
+ end
20
+
21
+ it "should have setters for the optional arguments" do
22
+ t = ICU::RatedTournament.new
23
+ t.desc=("Championship")
24
+ t.start=("2010-07-01")
25
+ t.desc.should == "Championship"
26
+ t.start.should == Date.parse("2010-07-01")
27
+ end
12
28
  end
13
29
 
14
30
  context "#players and #player" do
@@ -79,7 +95,7 @@ module ICU
79
95
  lambda { @t.add_result(1, @p1, @p3, 'W') }.should raise_error(/inconsistent/)
80
96
  lambda { @t.add_result(1, @p3, @p2, 'W') }.should raise_error(/inconsistent/)
81
97
  end
82
-
98
+
83
99
  it "players cannot have results against themselves" do
84
100
  lambda { @t.add_result(1, @p1, @p1, 'W') }.should raise_error(/against.*themsel(f|ves)/)
85
101
  end
@@ -191,7 +207,7 @@ module ICU
191
207
  p.new_rating.should be_close(new_rating, 0.5)
192
208
  end
193
209
  end
194
-
210
+
195
211
  it "tournament performances are not the same as ICU year-to-date performances" do
196
212
  [
197
213
  [1, 1867, 1761],
@@ -306,7 +322,7 @@ module ICU
306
322
  end
307
323
  end
308
324
  end
309
-
325
+
310
326
  it "foreign players should have tournament performnce ratings" do
311
327
  [
312
328
  [3, 2505.0],
@@ -571,7 +587,7 @@ module ICU
571
587
  end
572
588
  end
573
589
  end
574
-
590
+
575
591
  context "#rate - a made-up tournament that includes a group of unrateable players" do
576
592
  # 1 Orr, Mark 1350 2 2:W 3:W 0:-
577
593
  # 2 Coughlan, Anne 251 1 1:L 0:- 3:W
@@ -610,7 +626,7 @@ module ICU
610
626
  p.new_rating.should be_close(new_rating, 0.5)
611
627
  end
612
628
  end
613
-
629
+
614
630
  it "should not rate players that have no rateable games" do
615
631
  [4, 5, 6].each do |num|
616
632
  p = @t.player(num)
data/spec/util_spec.rb ADDED
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module ICU
4
+ describe Util do
5
+ context "#parsedate!" do
6
+ it "should return instances of class Date" do
7
+ Util.parsedate!('2001-01-01').should be_a(Date)
8
+ end
9
+
10
+ it "should parse standard dates" do
11
+ Util.parsedate!('2001-01-01').to_s.should == '2001-01-01'
12
+ Util.parsedate!('1955-11-09').to_s.should == '1955-11-09'
13
+ end
14
+
15
+ it "should handle US format" do
16
+ Util.parsedate!('03/30/2009').to_s.should == '2009-03-30'
17
+ end
18
+
19
+ it "should handle European format" do
20
+ Util.parsedate!('30/03/2009').to_s.should == '2009-03-30'
21
+ end
22
+
23
+ it "should prefer European format" do
24
+ Util.parsedate!('02/03/2009').to_s.should == '2009-03-02'
25
+ end
26
+
27
+ it "should handle US style when there's no alternative" do
28
+ Util.parsedate!('02/23/2009').to_s.should == '2009-02-23'
29
+ end
30
+
31
+ it "should handle single digits" do
32
+ Util.parsedate!('9/8/2006').to_s.should == '2006-08-09'
33
+ end
34
+
35
+ it "should handle names of months" do
36
+ Util.parsedate!('9th Nov 1955').to_s.should == '1955-11-09'
37
+ Util.parsedate!('16th June 1986').to_s.should == '1986-06-16'
38
+ end
39
+
40
+ it "should raise exception on error" do
41
+ lambda { Util.parsedate!('2010-13-32') }.should raise_error(/invalid date/)
42
+ end
43
+
44
+ it "should accept Date objects as well as strings" do
45
+ Util.parsedate!(Date.parse('2013-07-01')).should == Date.parse('2013-07-01')
46
+ end
47
+ end
48
+ end
49
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icu_ratings
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Orr
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-16 00:00:00 +00:00
12
+ date: 2010-01-17 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -30,10 +30,12 @@ files:
30
30
  - lib/icu_ratings/player.rb
31
31
  - lib/icu_ratings/result.rb
32
32
  - lib/icu_ratings/tournament.rb
33
+ - lib/icu_ratings/util.rb
33
34
  - spec/player_spec.rb
34
35
  - spec/result_spec.rb
35
36
  - spec/spec_helper.rb
36
37
  - spec/tournament_spec.rb
38
+ - spec/util_spec.rb
37
39
  has_rdoc: true
38
40
  homepage: http://github.com/sanichi/icu_ratings
39
41
  licenses: []
@@ -67,3 +69,4 @@ test_files:
67
69
  - spec/result_spec.rb
68
70
  - spec/spec_helper.rb
69
71
  - spec/tournament_spec.rb
72
+ - spec/util_spec.rb