handy_capper 0.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ doc/
2
+ pkg/
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 'ruby-1.9.2-p180@handy_capper'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'minitest'
4
+ gem 'ruby-debug19', group: :development
@@ -0,0 +1,25 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ archive-tar-minitar (0.5.2)
5
+ columnize (0.3.5)
6
+ linecache19 (0.5.12)
7
+ ruby_core_source (>= 0.1.4)
8
+ minitest (2.8.1)
9
+ ruby-debug-base19 (0.11.25)
10
+ columnize (>= 0.3.1)
11
+ linecache19 (>= 0.5.11)
12
+ ruby_core_source (>= 0.1.4)
13
+ ruby-debug19 (0.11.6)
14
+ columnize (>= 0.3.1)
15
+ linecache19 (>= 0.5.11)
16
+ ruby-debug-base19 (>= 0.11.19)
17
+ ruby_core_source (0.1.5)
18
+ archive-tar-minitar (>= 0.5.2)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ minitest
25
+ ruby-debug19
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Claude Nix
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,115 @@
1
+ # HandyCapper
2
+ This is alpha software. It currently only supports PHRF Time on Distance and
3
+ PHRF Time on Time scoring.
4
+
5
+ ## Requirements
6
+ - Ruby 1.9.2 or greater
7
+ - minitest gem (if you want to run the test suite)
8
+
9
+ ## Usage
10
+
11
+ ### Calculating corrected time
12
+
13
+ ```ruby
14
+ class YourApp
15
+ include HandyCapper
16
+
17
+ class Result
18
+ # installs attr_accessors for required result attributes
19
+ include HandyCapper::Models::PreliminaryResult
20
+ end
21
+ end
22
+
23
+ result = YourApp::Result.new({
24
+ rating: 222,
25
+ start_time: '10:00:00',
26
+ finish_time: '11:30:30',
27
+ distance: 10.5
28
+ })
29
+
30
+ # If no options are passed to #phrf, the Time on Distance system will be used
31
+ result.phrf
32
+ # => #<Result ...>
33
+ result.elapsed_time
34
+ # => '01:30:30'
35
+ result.corrected_time
36
+ # => '00:51:39'
37
+ ```
38
+
39
+ #### PHRF Time on Time Scoring
40
+ The PHRF Time on Time scoring method calculates a Time Correction Factor (TCF)
41
+ which is multiplied by the elapsed time to get 'corrected time'. The TCF is
42
+ calculated thusly:
43
+
44
+ ```
45
+ A
46
+ TCF = -------------
47
+ B + Rating
48
+ ```
49
+
50
+ Adjusting the _A_ numerator will have no impact on finishing order. It's used
51
+ to generate a pretty coeffecient. It is set to 650 as a default.
52
+
53
+ Adjusting the _B_ denominator will potentially impact finishing order. It's used
54
+ to represent the conditions for a given race. It is set to 550 as a default, for
55
+ average conditions. Use 480 for heavy air or all off the wind. Use 600 for light
56
+ air or all windward work.
57
+
58
+ Corrected time is then calculated as:
59
+
60
+ ```
61
+ corrected = TCF * elapsed time
62
+ ```
63
+
64
+ For more information on PHRF Time on Time scoring, see
65
+ [http://offshore.ussailing.org/PHRF/Time-On-Time_Scoring.htm][]
66
+
67
+ ```ruby
68
+ result.phrf(formula: :tot, a: 650, b: 550)
69
+ # => #<Result ...>
70
+ result.elapsed_time
71
+ # => '01:30:30'
72
+ result.corrected_time
73
+ # => '01:16:12'
74
+ ```
75
+
76
+ ### Scoring a race
77
+ Now that we can correct times, we need to be able to sort a group of corrected
78
+ results and apply points. Currently, HandyCapper only supports scoring a
79
+ single event. Scoring a series, including applying throwouts will be supported
80
+ in a future release.
81
+
82
+ ```ruby
83
+ # get some result objects from a database or something
84
+ results = Result.where('race_id = ?', 1)
85
+ # => [ #<Result ...>, #<Result ...>]
86
+ results.score
87
+ # returns results with position and points set
88
+ # => [ #<Result ...>, #<Result ...>]
89
+ results.first.position
90
+ # => 1
91
+ results.first.points
92
+ # => 1
93
+ ```
94
+
95
+
96
+ Run the Tests
97
+ -------------
98
+ ```bash
99
+ rake test
100
+ ```
101
+
102
+ Utilities
103
+ ---------
104
+ Launch an irb session with HandyCapper loaded
105
+
106
+ ```bash
107
+ rake console
108
+ ```
109
+
110
+ Copyright
111
+ ---------
112
+ Copyright (c) 2011 Claude Nix. See [LICENSE][] for details.
113
+
114
+ [license]: https://github.com/cnix/handy_capper/blob/master/LICENSE.md
115
+ [http://offshore.ussailing.org/PHRF/Time-On-Time_Scoring.htm]: http://offshore.ussailing.org/PHRF/Time-On-Time_Scoring.htm
@@ -0,0 +1,12 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs.push "lib"
5
+ t.test_files = FileList['test/**/*_test.rb']
6
+ t.verbose = true
7
+ end
8
+
9
+ desc "Open an irb session preloaded with this library"
10
+ task :console do
11
+ sh "irb -rubygems -r ./lib/handy_capper.rb"
12
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/handy_capper/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.add_development_dependency('ruby-debug19')
6
+ s.add_development_dependency('minitest')
7
+ s.authors = ["Claude Nix"]
8
+ s.description = %q{A Ruby library for calculating corrected scores for common sailboat racing scoring systems}
9
+ s.email = ['claude@seadated.com']
10
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
11
+ s.files = `git ls-files`.split("\n")
12
+ s.homepage = 'https://github.com/cnix/handy_capper'
13
+ s.name = 'handy_capper'
14
+ s.platform = Gem::Platform::RUBY
15
+ s.require_paths = ['lib']
16
+ s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if s.respond_to? :required_rubygems_version=
17
+ s.rubyforge_project = s.name
18
+ s.summary = %q{A Ruby library for calculating corrected scores for common sailboat racing scoring systems}
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.version = HandyCapper::VERSION.dup
21
+ end
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/handy_capper/handy_capper'
2
+ require File.dirname(__FILE__) + '/models/preliminary_result'
@@ -0,0 +1,243 @@
1
+ # Public: Various methods for scoring sailing regattas.
2
+ # All methods are instance methods.
3
+ # Currently only PHRF Time on Time and PHRF Time on Distance are supported.
4
+ module HandyCapper
5
+
6
+ # Public: Applies position and points to a group of results
7
+ #
8
+ # sort - Result attribute to sort by. (default: :corrected_time)
9
+ # :one_design - sort by elapsed_time
10
+ # :corrected_time - sort by corrected_time
11
+ #
12
+ # Examples
13
+ #
14
+ # # get some result objects from a database or something
15
+ # results = Result.where('race_id = ?', 1)
16
+ # # => [ #<Result ...>, #<Result ...>]
17
+ # results.score(:corrected_time)
18
+ # # returns results with position and points set
19
+ # # => [ #<Result ...>, #<Result ...>]
20
+ # results.first.position
21
+ # # => 1
22
+ # results.first.points
23
+ # # => 1
24
+ #
25
+ # Returns receiver(Array) with position and points set for each item in the array
26
+ def score(sort = :corrected_time)
27
+ sort = (sort == :one_design) ? :elapsed_time : :corrected_time
28
+ sorted_results = self.sort_by { |h| h[sort] }
29
+
30
+ sorted_results.each_with_index do |result, index|
31
+ result.position = index + 1
32
+ calculate_points(result, sorted_results.length)
33
+ end
34
+
35
+ sorted_results
36
+ end
37
+
38
+ # Public: Corects a result with the PHRF scoring system.
39
+ # See http://http://offshore.ussailing.org/PHRF.htm
40
+ #
41
+ # options - Hash for setting a different PHRF scoring method (default: {})
42
+ # :formula - If you wish to use Time on Time, pass the Symbol :tot
43
+ # Additionally, you can set the numerator and
44
+ # denominator for the Time on Time formula by setting
45
+ # values for :a & :b
46
+ # :a - Set :a to a Fixnum to set the numerator of the formula
47
+ # :b - Set :b to a Fixnum to set the denominator of the formula
48
+ #
49
+ # Examples
50
+ #
51
+ # # Assuming a class named Result in your application
52
+ # result = Result.new({
53
+ # rating: 222,
54
+ # start_time: '10:00:00',
55
+ # finish_time: '11:30:30',
56
+ # distance: 10.5
57
+ # })
58
+ #
59
+ # result.phrf
60
+ # # => #<Result ...>
61
+ # result.elapsed_time
62
+ # # => '01:30:30'
63
+ # result.corrected_time
64
+ # # => '00:59:50'
65
+ #
66
+ # # Using default settings for Time on Time formula
67
+ # result.phrf(formula: :tot)
68
+ # # => #<Result ...>
69
+ # result.corrected_time
70
+ # # => '01:16:12'
71
+ #
72
+ # # Change the denominator to accommodate conditions
73
+ # result.phrf(formula: :tot, b: 480) # heavy air
74
+ # # => #<Result ...>
75
+ # result.corrected_time
76
+ # # => '01:23:48'
77
+ #
78
+ # Returns receiver with elapsed_time and corrected_time set
79
+ # Raises AttributeError if a required attribute is missing from the receiver
80
+ def phrf(options={})
81
+
82
+ unless rating && start_time && finish_time
83
+ raise AttributeError, "You're missing a required attribute to process this result"
84
+ end
85
+
86
+ self.elapsed_time = calculate_elapsed_time(self)
87
+
88
+ if options[:formula] == :tot
89
+ a = options[:a] || 650 # generic numerator
90
+ b = options[:b] || 550 # average conditions
91
+ ct_in_seconds = score_with_phrf_time_on_time(a,b)
92
+ else
93
+ ct_in_seconds = score_with_phrf_time_on_distance
94
+ end
95
+
96
+ self.corrected_time = convert_seconds_to_time(ct_in_seconds)
97
+ self.elapsed_time = convert_seconds_to_time(self.elapsed_time)
98
+
99
+ self
100
+ end
101
+
102
+ private
103
+
104
+ # Internal: Calculate corrected time with PHRF Time on Distance
105
+ #
106
+ # result - corrected time in seconds
107
+ #
108
+ # Examples
109
+ #
110
+ # Result = Struct.new(:elapsed_time, :rating, :distance)
111
+ # result = Result.new(5400, 222, 10.5)
112
+ # result.score_with_phrf_time_on_distance
113
+ # # => 3069
114
+ #
115
+ # Returns a Fixnum
116
+ def score_with_phrf_time_on_distance
117
+ cf = self.rating
118
+ et = self.elapsed_time
119
+ d = self.distance
120
+
121
+ (et - (d * cf)).round
122
+ end
123
+
124
+ # Internal: Calculate corrected time in seconds with PHRF Time on Time
125
+ #
126
+ # a - Numerator for TOT formula. Does not affect position.
127
+ # b - Denominator for TOT formula. This one affects position.
128
+ #
129
+ # Examples
130
+ #
131
+ # Result = Struct.new(:elapsed_time, :rating)
132
+ # result = Result.new(5400, 222)
133
+ # result.score_with_phrf_time_on_time(b: 480)
134
+ # # => 5000
135
+ # result.score_with_phrf_time_on_time(b: 600)
136
+ # # => 4270
137
+ #
138
+ # Returns a Fixnum representing corrected time in seconds
139
+ def score_with_phrf_time_on_time(a, b)
140
+ tcf = a.to_f / ( b.to_f + self.rating.to_f )
141
+ (self.elapsed_time * tcf).round
142
+ end
143
+
144
+ # Internal: Calculate delta in seconds between two time objects
145
+ #
146
+ # result - an object with a start_time and a finish_time attribute
147
+ #
148
+ # Examples
149
+ #
150
+ # Result = Struct.new(:start_time, :finish_time)
151
+ # result = Result.new("10:00:00", "11:30:00")
152
+ # calculate_elapsed_time(result)
153
+ # # => 5400
154
+ #
155
+ # Returns a Fixnum
156
+ def calculate_elapsed_time(result)
157
+ Time.parse(result.finish_time).to_i - Time.parse(result.start_time).to_i
158
+ end
159
+
160
+ # Internal: Covert seconds to a string of seconds
161
+ #
162
+ # seconds - a Fixnum representing time in seconds
163
+ #
164
+ # Examples
165
+ #
166
+ # convert_seconds_to_time(5400)
167
+ # # => '01:30:00'
168
+ #
169
+ # Returns a string
170
+ def convert_seconds_to_time(seconds)
171
+ Time.at(seconds).gmtime.strftime('%R:%S')
172
+ end
173
+
174
+ # Internal: Calculate the points for a scored result
175
+ #
176
+ # result - A Result object
177
+ # total_results - A Fixnum representing the number of results in the set
178
+ #
179
+ # Examples
180
+ #
181
+ # first_place_boat = Result.new({
182
+ # corrected_time: '01:30:41',
183
+ # position: 1,
184
+ # code: nil
185
+ # })
186
+ # calculate_points(first_place_boat, 10)
187
+ # # => #<Result ...>
188
+ # first_place_boat.points
189
+ # # => 1
190
+ #
191
+ # dnf_boat = Result.new({
192
+ # corrected_time: nil,
193
+ # position: 10,
194
+ # code: 'DNF'
195
+ # })
196
+ # calculate_points(dnf_boat, 10)
197
+ # # => #<Result ...>
198
+ # dnf_boat.points
199
+ # # => 11
200
+ #
201
+ # Returns the receiver with the points attribute set to a Fixnum
202
+ def calculate_points(result, total_results)
203
+ if result.code
204
+ calculate_points_with_penalty(result, total_results)
205
+ else
206
+ result.points = result.position
207
+ end
208
+ result
209
+ end
210
+
211
+ # Internal: Calculate points based on a penalty code
212
+ def calculate_points_with_penalty(result, total_results)
213
+ if ONE_POINT_PENALTY_CODES.include?(result.code)
214
+ result.points = total_results + 1
215
+ elsif TWENTY_PERCENT_PENALTY_CODES.include?(result.code)
216
+ penalty_points = (total_results * 0.20).round
217
+ if (penalty_points + result.position) > (total_results + 1)
218
+ result.points = total_results + 1
219
+ else
220
+ result.points = result.position + penalty_points
221
+ end
222
+ else
223
+ result.points = result.position
224
+ end
225
+ result
226
+ end
227
+
228
+ # Internal: Array of String penalty codes that apply the n + 1 penalty where
229
+ # n = the total number of entries for a fleet
230
+ ONE_POINT_PENALTY_CODES = [
231
+ 'DSQ', 'DNS', 'DNC', 'DNF',
232
+ 'OCS', 'BFD', 'DGM', 'DNE',
233
+ 'RAF'
234
+ ]
235
+
236
+ # Internal: Array of String penalty codes that apply the 20% penalty
237
+ TWENTY_PERCENT_PENALTY_CODES = [ 'ZFP', 'SCP' ]
238
+
239
+ # Internal: Error that is raised when required attributes are missing from a
240
+ # receiver for PHRF scoring
241
+ class AttributeError < StandardError; end
242
+
243
+ end
@@ -0,0 +1,4 @@
1
+ module HandyCapper
2
+ # Public: Version number for this release
3
+ VERSION = '0.1.2'.freeze unless defined?(::HandyCapper::VERSION)
4
+ end
@@ -0,0 +1,31 @@
1
+ module HandyCapper
2
+ # Public: Namespace for model-like modules
3
+ module Models
4
+ # Public: Include this module in your result class to get required attributes
5
+ # for scoring the results. Alternatively, you can alias these to whatever
6
+ # you have called them in your application
7
+ module PreliminaryResult
8
+
9
+ # Public: Array of Symbols passed attr_accessor on base class
10
+ DEFAULT_PROPERTIES = [
11
+ :rating,
12
+ :start_time,
13
+ :finish_time,
14
+ :elapsed_time,
15
+ :corrected_time,
16
+ :distance,
17
+ :penalty,
18
+ :code,
19
+ :avg_speed
20
+ ]
21
+
22
+ # Public: Installs attributes on a class where this module is included
23
+ def self.included(base)
24
+ DEFAULT_PROPERTIES.each do |p|
25
+ base.send(:attr_accessor, p)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,225 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'ruby-debug'
3
+
4
+ describe HandyCapper do
5
+ before do
6
+ Result = Struct.new(
7
+ :rating,
8
+ :start_time,
9
+ :finish_time,
10
+ :distance,
11
+ :elapsed_time,
12
+ :corrected_time,
13
+ :position,
14
+ :points,
15
+ :code
16
+ )
17
+ end
18
+
19
+ after do
20
+ # silence warnings for already initialized constant
21
+ # this could probably be done better if it wasn't a Struct. =\
22
+ Object.send(:remove_const, :Result)
23
+ end
24
+
25
+ describe "#score" do
26
+ before do
27
+ @corrected_results = []
28
+ 10.times do
29
+ result = Result.new
30
+ # TODO: Address potential race condition
31
+ time = Time.parse("#{[*0..3].sample}:#{[*0..59].sample}:#{[*0..59].sample}").strftime('%R:%S')
32
+ result.corrected_time = time
33
+ @corrected_results << result
34
+ end
35
+
36
+ @one_design_results = []
37
+ 10.times do
38
+ result = Result.new
39
+ # TODO: Address potential race condition
40
+ time = Time.parse("#{[*0..3].sample}:#{[*0..59].sample}:#{[*0..59].sample}").strftime('%R:%S')
41
+ result.elapsed_time = time
42
+ @one_design_results << result
43
+ end
44
+ end
45
+
46
+ describe "one design" do
47
+ it "should sort results by elapsed_time" do
48
+ scored_results = @one_design_results.score(:one_design)
49
+
50
+ previous = '0'
51
+ scored_results.each do |r|
52
+ this = r.elapsed_time
53
+ (this > previous).must_equal true
54
+ previous = this
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "corrected time" do
60
+ it "should sort the results by corrected_time" do
61
+ scored_results = @corrected_results.score
62
+
63
+ previous = '0'
64
+ scored_results.each do |r|
65
+ this = r.corrected_time
66
+ (this > previous).must_equal true
67
+ previous = this
68
+ end
69
+ end
70
+ end
71
+
72
+ it "should add position to the result" do
73
+ scored_results = @corrected_results.score
74
+
75
+ scored_results.each do |r|
76
+ r.position.wont_be_nil
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "#phrf" do
82
+ before do
83
+ @result = Result.new( 222, '10:00:00', '11:30:30', 10.6)
84
+ end
85
+
86
+ it "should set the elapsed time" do
87
+ @result.phrf.elapsed_time.wont_be_nil
88
+ end
89
+
90
+ it "should set the corrected time" do
91
+ @result.phrf.corrected_time.wont_be_nil
92
+ end
93
+
94
+ it "should require a rating" do
95
+ @result.rating = nil
96
+ -> { @result.phrf }.must_raise AttributeError
97
+ end
98
+
99
+ it "should require a start time" do
100
+ @result.start_time = nil
101
+ -> { @result.phrf }.must_raise AttributeError
102
+ end
103
+
104
+ it "should require a finish time" do
105
+ @result.finish_time = nil
106
+ -> { @result.phrf }.must_raise AttributeError
107
+ end
108
+
109
+ it "should default to PHRF Time On Distance" do
110
+ @result.phrf.corrected_time.must_equal '00:51:17'
111
+ end
112
+ end
113
+
114
+ describe "#score_with_phrf_time_on_distance" do
115
+ before do
116
+ # set elapsed time to seconds so we don't need to convert to test this
117
+ @result = Result.new(222, '10:00:00', '11:30:30', 10.6, 5430)
118
+ end
119
+
120
+ it "should calculate corrected time with Time on Distance formula" do
121
+ corrected_time_in_seconds = @result.send(:score_with_phrf_time_on_distance)
122
+ corrected_time_in_seconds.must_be_instance_of Fixnum
123
+ corrected_time_in_seconds.must_equal 3077
124
+ end
125
+ end
126
+
127
+ describe "#score_with_phrf_time_on_time" do
128
+ before do
129
+ @result = Result.new(222, '10:00:00', '11:30:30', nil, 5430)
130
+ end
131
+
132
+ it "should calculate corrected time with Time on Time formula" do
133
+ corrected_time_in_seconds = @result.send(:score_with_phrf_time_on_time, 650,550)
134
+ corrected_time_in_seconds.must_be_instance_of Fixnum
135
+ corrected_time_in_seconds.must_equal 4572
136
+ end
137
+
138
+ it "should allow configuration of time on time numerator" do
139
+ corrected_time_in_seconds = @result.send(:score_with_phrf_time_on_time, 550,550)
140
+ corrected_time_in_seconds.must_equal 3869
141
+ end
142
+
143
+ it "should allow configuration of time on time denominator" do
144
+ corrected_time_in_seconds = @result.send(:score_with_phrf_time_on_time, 650,480)
145
+ corrected_time_in_seconds.must_equal 5028
146
+ end
147
+ end
148
+
149
+ describe "#calculate_elapsed_time" do
150
+ before do
151
+ @result = Result.new( 222, '10:00:00', '11:30:30', 10.6)
152
+ end
153
+
154
+ it "should return a Fixnum representing time in seconds" do
155
+ seconds = calculate_elapsed_time(@result)
156
+ seconds.must_be_instance_of Fixnum
157
+ seconds.must_equal 5430
158
+ end
159
+ end
160
+
161
+ describe "#convert_seconds_to_time" do
162
+ before do
163
+ @result = Result.new( 222, '10:00:00', '11:30:30', 10.6)
164
+ end
165
+
166
+ it "should accept a Fixnum" do
167
+ -> { convert_seconds_to_time("90") }.
168
+ must_raise TypeError
169
+ end
170
+
171
+ it "should return a Time" do
172
+ time = convert_seconds_to_time(90)
173
+ time.must_be_instance_of String
174
+ time.must_equal "00:01:30"
175
+ end
176
+ end
177
+
178
+ describe "#calculate_points" do
179
+ before do
180
+ @result = Result.new
181
+ @result.position = 2
182
+ end
183
+
184
+ it "should set the points for a result" do
185
+ calculate_points(@result, 10)
186
+ @result.points.must_equal 2
187
+ end
188
+
189
+ describe "penalty scoring" do
190
+
191
+ describe "when the penalty code is a n + 1 code" do
192
+
193
+ it "should set points based on ISAF penalty code" do
194
+ ['DSQ', 'DNS', 'DNF', 'DNC', 'OCS', 'BFD', 'DGM', 'DNE', 'RAF'].each do |c|
195
+ @result.code = c
196
+ calculate_points(@result, 10)
197
+ @result.points.must_equal 11
198
+ end
199
+
200
+ end
201
+ end
202
+
203
+ describe "when the penalty code is a 20% penalty" do
204
+
205
+ it "should apply the 20% penalty" do
206
+ [ 'ZFP', 'SCP' ].each do |c|
207
+ @result.code = c
208
+ calculate_points(@result, 10)
209
+ @result.points.must_equal 4
210
+ end
211
+ end
212
+
213
+ it "should not apply a penalty greater than n + 1" do
214
+ [ 'ZFP', 'SCP' ].each do |c|
215
+ @result.code = c
216
+ @result.position = 10
217
+ calculate_points(@result, 10)
218
+ @result.points.must_equal 11
219
+ end
220
+ end
221
+
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ describe HandyCapper::Models::PreliminaryResult do
4
+ describe ".included" do
5
+ before :each do
6
+ class Result; include HandyCapper::Models::PreliminaryResult; end
7
+
8
+ DEFAULT_PROPERTIES = [
9
+ :rating,
10
+ :start_time,
11
+ :finish_time,
12
+ :elapsed_time,
13
+ :corrected_time,
14
+ :distance,
15
+ :penalty,
16
+ :code,
17
+ :avg_speed
18
+ ]
19
+ end
20
+
21
+ it "should install the default attributes when included in a class" do
22
+ result = Result.new
23
+
24
+ DEFAULT_PROPERTIES.each do |p|
25
+ result.must_respond_to p
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ gem 'minitest'
2
+ require 'minitest/spec'
3
+ require 'minitest/autorun'
4
+ require 'minitest/pride'
5
+ require File.dirname(__FILE__) + '/../lib/handy_capper'
6
+
7
+ include HandyCapper
8
+
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: handy_capper
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: "0.1"
5
+ version: 0.1.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Claude Nix
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-12-05 00:00:00 -05:00
13
+ date: 2011-12-08 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -44,8 +44,22 @@ extensions: []
44
44
 
45
45
  extra_rdoc_files: []
46
46
 
47
- files: []
48
-
47
+ files:
48
+ - .gitignore
49
+ - .rvmrc
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - LICENSE.md
53
+ - README.md
54
+ - Rakefile
55
+ - handy_capper.gemspec
56
+ - lib/handy_capper.rb
57
+ - lib/handy_capper/handy_capper.rb
58
+ - lib/handy_capper/version.rb
59
+ - lib/models/preliminary_result.rb
60
+ - test/handy_capper/handy_capper_test.rb
61
+ - test/models/preliminary_result_test.rb
62
+ - test/test_helper.rb
49
63
  has_rdoc: true
50
64
  homepage: https://github.com/cnix/handy_capper
51
65
  licenses: []
@@ -74,5 +88,7 @@ rubygems_version: 1.6.2
74
88
  signing_key:
75
89
  specification_version: 3
76
90
  summary: A Ruby library for calculating corrected scores for common sailboat racing scoring systems
77
- test_files: []
78
-
91
+ test_files:
92
+ - test/handy_capper/handy_capper_test.rb
93
+ - test/models/preliminary_result_test.rb
94
+ - test/test_helper.rb