rrschedule 0.1.3 → 0.1.4
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.
- 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
|