rrschedule 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +11 -7
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/rrschedule.rb +41 -10
- data/test/test_rrschedule.rb +118 -2
- metadata +19 -5
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= rrschedule
|
2
2
|
|
3
|
-
rrschedule makes it easier to generate
|
3
|
+
rrschedule makes it easier to generate round-robin schedules for sport leagues. To generate a schedule, it needs a team list, a season
|
4
4
|
start date, the day(s) of the week where the games are played and some other options.
|
5
5
|
|
6
6
|
It takes into consideration physical constraints such as the number of playing surfaces availables and game times.
|
@@ -8,13 +8,13 @@ Each round of the round-robin is splitted into multiple gamedays that respect th
|
|
8
8
|
|
9
9
|
Say for example that you want to generate a round-robin schedule for your 15-teams volleyball league.
|
10
10
|
If there are only 3 volleyball fields available and that games are played each monday at 6PM and 8PM, this is technically
|
11
|
-
impossible to complete one round in a single day. rrschedule will put the remaining games of this round on the next gameday
|
11
|
+
impossible to complete one round in a single day. So rrschedule will put the remaining games of this round on the next gameday
|
12
12
|
and will start a new round right after.
|
13
13
|
|
14
14
|
|
15
15
|
== Installation
|
16
16
|
gem install rrschedule
|
17
|
-
require 'rrschedule
|
17
|
+
require 'rrschedule'
|
18
18
|
|
19
19
|
== Prepare the schedule
|
20
20
|
Time.zone = "America/New_York"
|
@@ -42,10 +42,10 @@ and will start a new round right after.
|
|
42
42
|
Time.zone.parse("2010/12/29")
|
43
43
|
],
|
44
44
|
|
45
|
-
#1 for Round Robin, 2 for Double Round Robin and so on
|
45
|
+
#1 for Round Robin, 2 for Double Round Robin and so on. Default is 1
|
46
46
|
:cycles => 1,
|
47
47
|
|
48
|
-
#Shuffle team order before each cycle
|
48
|
+
#Shuffle team order before each cycle. Default is true
|
49
49
|
:shuffle_initial_order => true,
|
50
50
|
|
51
51
|
#Times of the day where the games are played
|
@@ -95,9 +95,13 @@ and will start a new round right after.
|
|
95
95
|
puts "\n"
|
96
96
|
end
|
97
97
|
|
98
|
-
== Other
|
98
|
+
== Issues / Other
|
99
|
+
|
100
|
+
Playing surfaces and game times need to be distributed evenly among all competitors. At the moment
|
101
|
+
the same teams will play on the same surfaces and at the same game times most of the time.
|
102
|
+
Note that it only happens when a full round can be completed on a single gameday. Otherwise there will
|
103
|
+
be a natural rotation and teams will play on different surfaces and at different times between gamedays.
|
99
104
|
|
100
105
|
Hope this gem will be useful to some people!
|
101
|
-
As you can see, there are no tests yet but they should come quite soon.
|
102
106
|
|
103
107
|
You can read my blog here: www.rubyfleebie.com
|
data/Rakefile
CHANGED
@@ -11,6 +11,7 @@ begin
|
|
11
11
|
gem.homepage = "http://github.com/flamontagne/rrschedule"
|
12
12
|
gem.authors = ["flamontagne"]
|
13
13
|
gem.add_dependency 'activesupport'
|
14
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
15
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
16
|
end
|
16
17
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.4
|
data/lib/rrschedule.rb
CHANGED
@@ -8,14 +8,26 @@ module RRSchedule
|
|
8
8
|
attr_reader :teams, :rounds
|
9
9
|
|
10
10
|
def initialize(params={})
|
11
|
-
self.teams = params[:teams] || [1,2,3,4,5]
|
12
|
-
self.playing_surfaces = params[:playing_surfaces]
|
11
|
+
self.teams = params[:teams] || [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
|
12
|
+
self.playing_surfaces = Array(params[:playing_surfaces]).empty? ? ["Surface A", "Surface B"] : Array(params[:playing_surfaces])
|
13
13
|
self.cycles = params[:cycles] || 1
|
14
|
-
|
14
|
+
|
15
|
+
self.game_times = Array(params[:game_times]).empty? ? ["7:00 PM", "9:00 PM"] : Array(params[:game_times])
|
16
|
+
self.game_times.collect! do |gt|
|
17
|
+
begin
|
18
|
+
DateTime.parse(gt)
|
19
|
+
rescue
|
20
|
+
raise "game times must be valid time representations in the string form (e.g. 3:00 PM, 11:00 AM, 18:20, etc)"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
15
24
|
self.shuffle_initial_order = params[:shuffle_initial_order] || true
|
16
25
|
self.exclude_dates = params[:exclude_dates] || []
|
17
26
|
self.start_date = params[:start_date] || Time.now.beginning_of_day
|
18
27
|
self.wdays = Array(params[:wdays]).empty? ? [1] : Array(params[:wdays])
|
28
|
+
|
29
|
+
raise "each value in wdays must be between 0 and 6" if self.wdays.reject{|w| (0..6).member?(w)}.size > 0
|
30
|
+
self
|
19
31
|
end
|
20
32
|
|
21
33
|
|
@@ -63,8 +75,9 @@ module RRSchedule
|
|
63
75
|
end
|
64
76
|
end
|
65
77
|
end until @teams == initial_order && current_cycle==self.cycles
|
66
|
-
|
67
|
-
slice(all_games)
|
78
|
+
#@teams.delete(:dummy)
|
79
|
+
slice(all_games)
|
80
|
+
self
|
68
81
|
end
|
69
82
|
|
70
83
|
|
@@ -83,7 +96,7 @@ module RRSchedule
|
|
83
96
|
res << gd.strftime("%Y-%m-%d") + "\n"
|
84
97
|
res << "==========\n"
|
85
98
|
games.each do |g|
|
86
|
-
res << "#{g.team_a.to_s} VS #{g.team_b.to_s} on playing surface #{g.playing_surface} at #{g.game_time}\n"
|
99
|
+
res << "#{g.team_a.to_s} VS #{g.team_b.to_s} on playing surface #{g.playing_surface} at #{g.game_time.strftime("%I:%M %p")}\n"
|
87
100
|
end
|
88
101
|
res << "\n"
|
89
102
|
end
|
@@ -102,24 +115,40 @@ module RRSchedule
|
|
102
115
|
gms.flatten
|
103
116
|
end
|
104
117
|
|
118
|
+
#TODO: returns true if the generated schedule is a valid round-robin (for testing purpose)
|
119
|
+
def round_robin?
|
120
|
+
|
121
|
+
#each round-robin round should contains n-1 games where n is the nbr of teams (:dummy included if odd)
|
122
|
+
return false if self.rounds.size != (@teams.size*self.cycles)-self.cycles
|
123
|
+
|
124
|
+
#check if each team plays the same number of games against each other
|
125
|
+
self.teams.each do |t1|
|
126
|
+
self.teams.reject{|t| t == t1}.each do |t2|
|
127
|
+
return false unless self.face_to_face(t1,t2).size == self.cycles || [t1,t2].include?(:dummy)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
return true
|
131
|
+
end
|
132
|
+
|
105
133
|
private
|
106
134
|
|
107
135
|
def teams=(arr)
|
108
136
|
@teams = arr.clone
|
109
|
-
|
137
|
+
raise ":dummy is a reserved team name. Please use something else" if @teams.member?(:dummy)
|
138
|
+
raise "at least 2 teams are required" if @teams.size == 1
|
139
|
+
@teams << :dummy if @teams.size.odd?
|
110
140
|
end
|
111
141
|
|
112
142
|
#Let's slice our games according to our physical constraints
|
113
143
|
def slice(games)
|
114
144
|
res={}
|
115
145
|
slices = games.each_slice(games_per_day)
|
116
|
-
wdays_stack = self.wdays.clone
|
117
|
-
|
146
|
+
wdays_stack = self.wdays.clone
|
118
147
|
cur_date = self.start_date
|
119
148
|
slices.each_with_index do |slice,i|
|
120
149
|
gt_stack = self.game_times.clone
|
121
150
|
ps_stack = self.playing_surfaces.clone
|
122
|
-
wdays_stack=self.wdays.clone if wdays_stack.empty?
|
151
|
+
wdays_stack = self.wdays.clone if wdays_stack.empty?
|
123
152
|
|
124
153
|
cur_wday = wdays_stack.shift
|
125
154
|
cur_date = next_game_date(cur_date,cur_wday)
|
@@ -138,6 +167,8 @@ module RRSchedule
|
|
138
167
|
gt_stack = self.game_times.clone if gt_stack.empty?
|
139
168
|
ps_stack = self.playing_surfaces.clone if ps_stack.empty?
|
140
169
|
end
|
170
|
+
|
171
|
+
res[cur_date] = res[cur_date].sort_by {|g| [g.game_time,g.playing_surface]}
|
141
172
|
cur_date += 1.day
|
142
173
|
end
|
143
174
|
@schedule = res
|
data/test/test_rrschedule.rb
CHANGED
@@ -1,7 +1,123 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestRrschedule < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
4
|
+
context "A Schedule instance" do
|
5
|
+
should "have default values for every options" do
|
6
|
+
schedule = RRSchedule::Schedule.new
|
7
|
+
|
8
|
+
assert schedule.teams.size > 2
|
9
|
+
assert_equal 1, schedule.cycles
|
10
|
+
assert schedule.game_times.respond_to?(:to_ary)
|
11
|
+
assert schedule.playing_surfaces.respond_to?(:to_ary)
|
12
|
+
assert schedule.start_date.respond_to?(:to_date)
|
13
|
+
assert schedule.shuffle_initial_order
|
14
|
+
assert schedule.wdays.select{|w| (0..6).member? w} == schedule.wdays
|
15
|
+
assert schedule.exclude_dates.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
should "have a dummy team when team number is odd" do
|
19
|
+
schedule = RRSchedule::Schedule.new(
|
20
|
+
:teams => Array(1..9)
|
21
|
+
)
|
22
|
+
|
23
|
+
assert schedule.teams.size == 10
|
24
|
+
assert schedule.teams.member?(:dummy), "There should always be a :dummy team when the nbr of teams is odd"
|
25
|
+
end
|
26
|
+
|
27
|
+
should "not have a dummy team when team number is even" do
|
28
|
+
schedule = RRSchedule::Schedule.new(
|
29
|
+
:teams => Array(1..6)
|
30
|
+
)
|
31
|
+
|
32
|
+
assert schedule.teams.size == 6
|
33
|
+
assert !schedule.teams.member?(:dummy), "There should never be a :dummy team when the nbr of teams is even"
|
34
|
+
end
|
35
|
+
|
36
|
+
should "not have a team named :dummy in the initial array" do
|
37
|
+
assert_raise RuntimeError do
|
38
|
+
schedule = RRSchedule::Schedule.new(
|
39
|
+
:teams => Array(1..4) << :dummy
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
should "not have game times that cannot convert to valid DateTime objects" do
|
45
|
+
assert_raise RuntimeError do
|
46
|
+
schedule = RRSchedule::Schedule.new(
|
47
|
+
:teams => Array(1..4),
|
48
|
+
:game_times => ["10:00 AM", "13:00", "bonjour"]
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
should "not have wdays that are not between 0 and 6" do
|
54
|
+
assert_raise RuntimeError do
|
55
|
+
schedule = RRSchedule::Schedule.new(
|
56
|
+
:wdays => [2,7]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
should "automatically convert game times and playing surface to arrays" do
|
62
|
+
schedule = RRSchedule::Schedule.new(
|
63
|
+
:teams => Array(1..4),
|
64
|
+
:game_times => "10:00 AM",
|
65
|
+
:playing_surfaces => "the only one"
|
66
|
+
)
|
67
|
+
|
68
|
+
assert_equal [DateTime.parse("10:00 AM")], schedule.game_times
|
69
|
+
assert_equal ["the only one"], schedule.playing_surfaces
|
70
|
+
end
|
71
|
+
|
72
|
+
should "have at least one team specified" do
|
73
|
+
assert_raise RuntimeError do
|
74
|
+
schedule = RRSchedule::Schedule.new(:teams => [1])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
should "have default teams if non was specified" do
|
79
|
+
schedule = RRSchedule::Schedule.new
|
80
|
+
assert schedule.teams.size > 1
|
81
|
+
end
|
6
82
|
end
|
83
|
+
|
84
|
+
context "A generated schedule with an odd number of teams" do
|
85
|
+
setup do
|
86
|
+
@s = RRSchedule::Schedule.new(
|
87
|
+
:teams => %w(a b c d e f g h i j l),
|
88
|
+
:playing_surfaces => %w(one two),
|
89
|
+
:game_times => ["10:00 AM", "13:00 PM"]
|
90
|
+
).generate
|
91
|
+
end
|
92
|
+
|
93
|
+
should "be a valid round-robin" do
|
94
|
+
assert @s.round_robin?
|
95
|
+
end
|
96
|
+
|
97
|
+
should "not have any :dummy teams in the schedule" do
|
98
|
+
assert @s.gamedays.collect{|gd,games| games}.flatten.select{
|
99
|
+
|g| [g.team_a,g.team_b].include?(:dummy)
|
100
|
+
}.size == 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "A generated schedule with an even number of teams" do
|
105
|
+
setup do
|
106
|
+
@s = RRSchedule::Schedule.new(
|
107
|
+
:teams => %w(a b c d e f g h i j l m),
|
108
|
+
:playing_surfaces => %w(one two),
|
109
|
+
:game_times => ["10:00 AM", "13:00 PM"]
|
110
|
+
).generate
|
111
|
+
end
|
112
|
+
|
113
|
+
should "be a valid round-robin" do
|
114
|
+
assert @s.round_robin?
|
115
|
+
end
|
116
|
+
|
117
|
+
should "not have any :dummy teams in the schedule" do
|
118
|
+
assert @s.gamedays.collect{|gd,games| games}.flatten.select{
|
119
|
+
|g| [g.team_a,g.team_b].include?(:dummy)
|
120
|
+
}.size == 0
|
121
|
+
end
|
122
|
+
end
|
7
123
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rrschedule
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 4
|
10
|
+
version: 0.1.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- flamontagne
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-09 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -32,6 +32,20 @@ dependencies:
|
|
32
32
|
version: "0"
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: thoughtbot-shoulda
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
35
49
|
description: This gem automate the process of creating a round-robin sport schedule.
|
36
50
|
email: flamontagne@azanka.ca
|
37
51
|
executables: []
|
@@ -86,5 +100,5 @@ signing_key:
|
|
86
100
|
specification_version: 3
|
87
101
|
summary: Round-Robin schedule generator
|
88
102
|
test_files:
|
89
|
-
- test/test_rrschedule.rb
|
90
103
|
- test/helper.rb
|
104
|
+
- test/test_rrschedule.rb
|