rrschedule 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|