rrschedule 0.1.6 → 0.1.7
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 +12 -10
- data/Rakefile +2 -3
- data/VERSION +1 -1
- data/lib/rrschedule.rb +91 -35
- data/test/test_rrschedule.rb +60 -46
- metadata +7 -21
data/README.rdoc
CHANGED
@@ -12,12 +12,14 @@ impossible to complete one round in a single day. So rrschedule will put the rem
|
|
12
12
|
and will start a new round right after.
|
13
13
|
|
14
14
|
|
15
|
+
== Demo
|
16
|
+
Online round-robin generator using RRSchedule: http://rrschedule.azanka.ca
|
17
|
+
|
15
18
|
== Installation
|
16
19
|
gem install rrschedule
|
17
20
|
require 'rrschedule'
|
18
21
|
|
19
22
|
== Prepare the schedule
|
20
|
-
Time.zone = "America/New_York"
|
21
23
|
teams = ["Rockets","Jetpacks","Snakes","Cobras","Wolves","Huskies","Tigers","Lions",
|
22
24
|
"Moose","Sprinklers","Pacers","Cyclops","Munchkins","Magicians","French Fries"]
|
23
25
|
|
@@ -32,14 +34,14 @@ and will start a new round right after.
|
|
32
34
|
:wdays => [3],
|
33
35
|
|
34
36
|
#Season will start on...
|
35
|
-
:start_date =>
|
37
|
+
:start_date => Date.parse("2010/10/13"),
|
36
38
|
|
37
39
|
#array of dates WITHOUT games
|
38
40
|
:exclude_dates => [
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
Date.parse("2010/11/24"),
|
42
|
+
Date.parse("2010/12/15"),
|
43
|
+
Date.parse("2010/12/22"),
|
44
|
+
Date.parse("2010/12/29")
|
43
45
|
],
|
44
46
|
|
45
47
|
#1 for Round Robin, 2 for Double Round Robin and so on. Default is 1
|
@@ -62,10 +64,10 @@ and will start a new round right after.
|
|
62
64
|
|
63
65
|
=== Iterate through schedule
|
64
66
|
schedule.gamedays.each do |gd|
|
65
|
-
puts gd.date
|
67
|
+
puts gd.date.strftime("%Y/%m/%d")
|
66
68
|
puts "===================="
|
67
69
|
gd.games.each do |g|
|
68
|
-
puts g.team_a.to_s + " Vs " + g.team_b.to_s + " on playing surface ##{g.playing_surface} at #{g.game_time}"
|
70
|
+
puts 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")}"
|
69
71
|
end
|
70
72
|
puts "\n"
|
71
73
|
end
|
@@ -75,14 +77,14 @@ and will start a new round right after.
|
|
75
77
|
games=schedule.by_team(test_team)
|
76
78
|
puts "Schedule for team ##{test_team.to_s}"
|
77
79
|
games.each do |g|
|
78
|
-
puts "#{g.game_date.strftime("%Y-%m-%d")}: against #{g.team_a == test_team ? g.team_b.to_s : g.team_a.to_s} on playing surface ##{g.playing_surface} at #{g.game_time}"
|
80
|
+
puts "#{g.game_date.strftime("%Y-%m-%d")}: against #{g.team_a == test_team ? g.team_b.to_s : g.team_a.to_s} on playing surface ##{g.playing_surface} at #{g.game_time.strftime("%I:%M %p")}"
|
79
81
|
end
|
80
82
|
|
81
83
|
=== Face to Face
|
82
84
|
games=schedule.face_to_face("Lions","Moose")
|
83
85
|
puts "FACE TO FACE: Lions Vs Moose"
|
84
86
|
games.each do |g|
|
85
|
-
puts g.game_date.
|
87
|
+
puts g.game_date.strftime("%Y/%m/%d") + " on playing surface " + g.playing_surface.to_s + " at " + g.game_time.strftime("%I:%M %p")
|
86
88
|
end
|
87
89
|
|
88
90
|
=== Each round of the roun-robin without any date/time or playing location info
|
data/Rakefile
CHANGED
@@ -8,11 +8,10 @@ begin
|
|
8
8
|
gem.summary = %Q{Round-Robin schedule generator}
|
9
9
|
gem.description = %Q{This gem automate the process of creating a round-robin sport schedule.}
|
10
10
|
gem.email = "flamontagne@azanka.ca"
|
11
|
-
gem.homepage = "http://github.com/
|
11
|
+
gem.homepage = "http://flamontagne.github.com/rrschedule"
|
12
12
|
gem.authors = ["flamontagne"]
|
13
|
-
gem.add_dependency 'activesupport'
|
13
|
+
#gem.add_dependency 'activesupport'
|
14
14
|
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
15
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
15
|
end
|
17
16
|
Jeweler::GemcutterTasks.new
|
18
17
|
rescue LoadError
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.7
|
data/lib/rrschedule.rb
CHANGED
@@ -1,56 +1,114 @@
|
|
1
1
|
# rrschedule (Round Robin Schedule generator)
|
2
2
|
# Auhtor: François Lamontagne
|
3
3
|
############################################################################################################################
|
4
|
-
require 'active_support/all'
|
5
4
|
module RRSchedule
|
6
5
|
class Schedule
|
7
|
-
|
8
|
-
|
6
|
+
attr_reader :playing_surfaces, :game_times, :cycles, :wdays, :start_date, :exclude_dates,
|
7
|
+
:shuffle_initial_order, :optimize, :teams, :rounds, :gamedays
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
|
10
|
+
#Array of teams that will compete against each other. You can pass it any kind of object
|
11
|
+
def teams=(arr)
|
12
|
+
@teams = arr ? arr.clone : [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
|
13
|
+
raise ":dummy is a reserved team name. Please use something else" if @teams.member?(:dummy)
|
14
|
+
raise "at least 2 teams are required" if @teams.size == 1
|
15
|
+
raise "teams have to be unique" if @teams.uniq.size < @teams.size
|
16
|
+
@teams << :dummy if @teams.size.odd?
|
17
|
+
end
|
18
|
+
|
19
|
+
#Array of available playing surfaces. You can pass it any kind of object
|
20
|
+
def playing_surfaces=(ps)
|
21
|
+
@playing_surfaces = Array(ps).empty? ? ["Surface A", "Surface B"] : Array(ps)
|
22
|
+
end
|
23
|
+
|
24
|
+
#Number of times each team plays against each other
|
25
|
+
def cycles=(cycles)
|
26
|
+
@cycles = cycles || 1
|
27
|
+
end
|
28
|
+
|
29
|
+
#Array of game times where games are played. Must be valid DateTime objects in the string form
|
30
|
+
def game_times=(gt)
|
31
|
+
@game_times = Array(gt).empty? ? ["7:00 PM", "9:00 PM"] : Array(gt)
|
32
|
+
@game_times.collect! do |gt|
|
18
33
|
begin
|
19
34
|
DateTime.parse(gt)
|
20
35
|
rescue
|
21
36
|
raise "game times must be valid time representations in the string form (e.g. 3:00 PM, 11:00 AM, 18:20, etc)"
|
22
37
|
end
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#Setting this to true will fill all the available playing surfaces and game times for a given gameday no matter if
|
42
|
+
#one team has to play several games on the same gameday. Setting it to false make sure that teams won't play
|
43
|
+
#more than one game per day.
|
44
|
+
def optimize=(opt)
|
45
|
+
@optimize = opt.nil? ? true : opt
|
46
|
+
end
|
47
|
+
|
48
|
+
#Shuffle the team order at the beginning of every cycles.
|
49
|
+
def shuffle_initial_order=(shuffle)
|
50
|
+
@shuffle_initial_order = shuffle.nil? ? true : shuffle
|
51
|
+
end
|
52
|
+
|
53
|
+
#Array of dates without games
|
54
|
+
def exclude_dates=(dates)
|
55
|
+
@exclude_dates=dates || []
|
56
|
+
end
|
57
|
+
|
58
|
+
#When the season starts? Since we generate the game dates based on weekdays, you need to pass it
|
59
|
+
#a start date in the correct timezone to get accurate game dates for the whole season. Otherwise
|
60
|
+
#you might
|
61
|
+
def start_date=(date)
|
62
|
+
@start_date=date || Date.today
|
63
|
+
end
|
64
|
+
|
65
|
+
#Array of weekdays where games are played (0 is sunday)
|
66
|
+
def wdays=(wdays)
|
67
|
+
@wdays = Array(wdays).empty? ? [1] : Array(wdays)
|
68
|
+
raise "each value in wdays must be between 0 and 6" if @wdays.reject{|w| (0..6).member?(w)}.size > 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(params={})
|
72
|
+
@gamedays = []
|
73
|
+
self.teams = params[:teams]
|
74
|
+
self.playing_surfaces = params[:playing_surfaces]
|
75
|
+
self.cycles = params[:cycles]
|
76
|
+
self.game_times = params[:game_times]
|
77
|
+
self.optimize = params[:optimize]
|
78
|
+
self.shuffle_initial_order = params[:shuffle_initial_order]
|
79
|
+
self.exclude_dates = params[:exclude_dates]
|
80
|
+
self.start_date = params[:start_date]
|
81
|
+
self.wdays = params[:wdays]
|
31
82
|
self
|
32
83
|
end
|
33
84
|
|
85
|
+
|
86
|
+
#This will generate the schedule based on the various parameters
|
34
87
|
#TODO: consider refactoring with a recursive algorithm
|
35
88
|
def generate(params={})
|
89
|
+
@gamedays = []
|
90
|
+
@rounds = []
|
36
91
|
@teams = @teams.sort_by{rand} if self.shuffle_initial_order
|
37
92
|
initial_order = @teams.clone
|
38
93
|
current_cycle = current_round = 0
|
39
94
|
all_games = []
|
40
95
|
|
41
|
-
#
|
96
|
+
#Cycle loop (A cycle is completed when every teams have played one game against each other)
|
42
97
|
begin
|
43
98
|
games = []
|
44
99
|
t = @teams.clone
|
100
|
+
|
101
|
+
#Round loop
|
45
102
|
while !t.empty? do
|
46
103
|
team_a = t.shift
|
47
104
|
team_b = t.reverse!.shift
|
48
105
|
t.reverse!
|
49
|
-
|
50
|
-
|
106
|
+
|
107
|
+
matchup = {:team_a => team_a, :team_b => team_b}
|
108
|
+
games << matchup; all_games << matchup
|
51
109
|
end
|
52
110
|
|
53
|
-
current_round += 1
|
111
|
+
current_round += 1
|
54
112
|
|
55
113
|
@rounds ||= []
|
56
114
|
@rounds << Round.new(
|
@@ -127,17 +185,9 @@ module RRSchedule
|
|
127
185
|
end
|
128
186
|
return true
|
129
187
|
end
|
130
|
-
|
131
|
-
def teams=(arr)
|
132
|
-
@teams = arr.clone
|
133
|
-
raise ":dummy is a reserved team name. Please use something else" if @teams.member?(:dummy)
|
134
|
-
raise "at least 2 teams are required" if @teams.size == 1
|
135
|
-
raise "teams have to be unique" if @teams.uniq.size < @teams.size
|
136
|
-
@teams << :dummy if @teams.size.odd?
|
137
|
-
end
|
138
188
|
|
139
189
|
private
|
140
|
-
#Slice games according to playing surfaces and game times
|
190
|
+
#Slice games according to playing surfaces available and game times
|
141
191
|
def slice(games)
|
142
192
|
slices = games.each_slice(games_per_day)
|
143
193
|
wdays_stack = self.wdays.clone
|
@@ -169,17 +219,23 @@ module RRSchedule
|
|
169
219
|
|
170
220
|
gameday.games = gameday.games.sort_by {|g| [g.game_time,g.playing_surface]}
|
171
221
|
self.gamedays << gameday
|
172
|
-
cur_date +=
|
222
|
+
cur_date += (60*60*24)
|
173
223
|
end
|
174
224
|
end
|
175
225
|
|
226
|
+
#get the next gameday
|
176
227
|
def next_game_date(dt,wday)
|
177
|
-
dt +=
|
228
|
+
dt += (60*60*24) until wday == dt.wday && !self.exclude_dates.include?(dt)
|
178
229
|
dt
|
179
230
|
end
|
180
231
|
|
232
|
+
#how many games can we play per day?
|
181
233
|
def games_per_day
|
182
|
-
self.playing_surfaces.size * self.game_times.size
|
234
|
+
if self.teams.size/2 >= (self.playing_surfaces.size * self.game_times.size)
|
235
|
+
(self.playing_surfaces.size * self.game_times.size)
|
236
|
+
else
|
237
|
+
self.optimize ? (self.playing_surfaces.size * self.game_times.size) : self.teams.size/2
|
238
|
+
end
|
183
239
|
end
|
184
240
|
end
|
185
241
|
|
data/test/test_rrschedule.rb
CHANGED
@@ -9,26 +9,21 @@ class TestRrschedule < Test::Unit::TestCase
|
|
9
9
|
assert_equal 1, schedule.cycles
|
10
10
|
assert schedule.game_times.respond_to?(:to_ary)
|
11
11
|
assert schedule.playing_surfaces.respond_to?(:to_ary)
|
12
|
-
assert schedule.start_date.
|
12
|
+
assert schedule.start_date.is_a?(Date)
|
13
13
|
assert schedule.shuffle_initial_order
|
14
|
+
assert schedule.optimize
|
14
15
|
assert schedule.wdays.select{|w| (0..6).member? w} == schedule.wdays
|
15
16
|
assert schedule.exclude_dates.empty?
|
16
17
|
end
|
17
18
|
|
18
19
|
should "have a dummy team when number of teams is odd" do
|
19
|
-
schedule = RRSchedule::Schedule.new(
|
20
|
-
:teams => Array(1..9)
|
21
|
-
)
|
22
|
-
|
20
|
+
schedule = RRSchedule::Schedule.new(:teams => Array(1..9))
|
23
21
|
assert schedule.teams.size == 10
|
24
22
|
assert schedule.teams.member?(:dummy), "There should always be a :dummy team when the nbr of teams is odd"
|
25
23
|
end
|
26
24
|
|
27
25
|
should "not have a dummy team when number of teams is even" do
|
28
|
-
schedule = RRSchedule::Schedule.new(
|
29
|
-
:teams => Array(1..6)
|
30
|
-
)
|
31
|
-
|
26
|
+
schedule = RRSchedule::Schedule.new(:teams => Array(1..6))
|
32
27
|
assert schedule.teams.size == 6
|
33
28
|
assert !schedule.teams.member?(:dummy), "There should never be a :dummy team when the nbr of teams is even"
|
34
29
|
end
|
@@ -96,54 +91,73 @@ class TestRrschedule < Test::Unit::TestCase
|
|
96
91
|
:game_times => ["10:00 AM", "13:00 PM"]
|
97
92
|
)
|
98
93
|
end
|
99
|
-
|
94
|
+
|
100
95
|
should "have gamedays that respect the wdays attribute" do
|
101
96
|
@s.wdays = [3,5]
|
102
97
|
@s.generate
|
103
|
-
|
98
|
+
|
104
99
|
@s.gamedays.each do |gd|
|
105
100
|
assert [3,5].include?(gd.date.wday), "wday is #{gd.date.wday.to_s} but should be 3 or 5"
|
106
101
|
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
context "A generated schedule with an odd number of teams" do
|
111
|
-
setup do
|
112
|
-
@s = RRSchedule::Schedule.new(
|
113
|
-
:teams => %w(a b c d e f g h i j l),
|
114
|
-
:playing_surfaces => %w(one two),
|
115
|
-
:game_times => ["10:00 AM", "13:00 PM"]
|
116
|
-
).generate
|
117
|
-
end
|
102
|
+
end
|
118
103
|
|
119
|
-
|
120
|
-
|
104
|
+
context "with the option optimize set to true" do
|
105
|
+
should "have at most (playing_surfaces*game_times) games per gameday" do
|
106
|
+
@s.generate
|
107
|
+
assert @s.gamedays.first.games.size == (@s.playing_surfaces.size * @s.game_times.size)
|
108
|
+
end
|
121
109
|
end
|
122
110
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
111
|
+
context "with the option optimize set to false" do
|
112
|
+
setup do
|
113
|
+
@s.optimize = false
|
114
|
+
end
|
115
|
+
|
116
|
+
should "never have more than (number of teams / 2) games per gameday" do
|
117
|
+
@s.teams = %w(only four teams here)
|
118
|
+
@s.generate
|
119
|
+
assert @s.gamedays.first.games.size == @s.teams.size / 2
|
120
|
+
end
|
127
121
|
end
|
128
|
-
end
|
129
|
-
|
130
|
-
context "A generated schedule with an even number of teams" do
|
131
|
-
setup do
|
132
|
-
@s = RRSchedule::Schedule.new(
|
133
|
-
:teams => %w(a b c d e f g h i j l m),
|
134
|
-
:playing_surfaces => %w(one two),
|
135
|
-
:game_times => ["10:00 AM", "13:00 PM"]
|
136
|
-
).generate
|
137
|
-
end
|
138
122
|
|
139
|
-
|
140
|
-
|
123
|
+
context "with an odd number of teams" do
|
124
|
+
setup do
|
125
|
+
@s = RRSchedule::Schedule.new(
|
126
|
+
:teams => %w(a b c d e f g h i j l),
|
127
|
+
:playing_surfaces => %w(one two),
|
128
|
+
:game_times => ["10:00 AM", "13:00 PM"]
|
129
|
+
).generate
|
130
|
+
end
|
131
|
+
|
132
|
+
should "be a valid round-robin" do
|
133
|
+
assert @s.round_robin?
|
134
|
+
end
|
135
|
+
|
136
|
+
should "not have any :dummy teams in the final schedule" do
|
137
|
+
assert @s.gamedays.collect{|gd| gd.games}.flatten.select{
|
138
|
+
|g| [g.team_a,g.team_b].include?(:dummy)
|
139
|
+
}.size == 0
|
140
|
+
end
|
141
141
|
end
|
142
142
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
143
|
+
context "with an even number of teams" do
|
144
|
+
setup do
|
145
|
+
@s = RRSchedule::Schedule.new(
|
146
|
+
:teams => %w(a b c d e f g h i j l m),
|
147
|
+
:playing_surfaces => %w(one two),
|
148
|
+
:game_times => ["10:00 AM", "13:00 PM"]
|
149
|
+
).generate
|
150
|
+
end
|
151
|
+
|
152
|
+
should "be a valid round-robin" do
|
153
|
+
assert @s.round_robin?
|
154
|
+
end
|
155
|
+
|
156
|
+
should "not have any :dummy teams in the final schedule" do
|
157
|
+
assert @s.gamedays.collect{|gd| gd.games}.flatten.select{
|
158
|
+
|g| [g.team_a,g.team_b].include?(:dummy)
|
159
|
+
}.size == 0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
149
163
|
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: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 7
|
10
|
+
version: 0.1.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- flamontagne
|
@@ -15,27 +15,13 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-18 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
22
|
-
name: activesupport
|
23
|
-
prerelease: false
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
hash: 3
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
version: "0"
|
33
|
-
type: :runtime
|
34
|
-
version_requirements: *id001
|
35
21
|
- !ruby/object:Gem::Dependency
|
36
22
|
name: thoughtbot-shoulda
|
37
23
|
prerelease: false
|
38
|
-
requirement: &
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
39
25
|
none: false
|
40
26
|
requirements:
|
41
27
|
- - ">="
|
@@ -45,7 +31,7 @@ dependencies:
|
|
45
31
|
- 0
|
46
32
|
version: "0"
|
47
33
|
type: :development
|
48
|
-
version_requirements: *
|
34
|
+
version_requirements: *id001
|
49
35
|
description: This gem automate the process of creating a round-robin sport schedule.
|
50
36
|
email: flamontagne@azanka.ca
|
51
37
|
executables: []
|
@@ -66,7 +52,7 @@ files:
|
|
66
52
|
- test/helper.rb
|
67
53
|
- test/test_rrschedule.rb
|
68
54
|
has_rdoc: true
|
69
|
-
homepage: http://github.com/
|
55
|
+
homepage: http://flamontagne.github.com/rrschedule
|
70
56
|
licenses: []
|
71
57
|
|
72
58
|
post_install_message:
|