rrschedule 0.1.8 → 0.2.0
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.markdown +65 -0
- data/VERSION +1 -1
- data/branches_info.markdown +55 -0
- data/lib/rrschedule.rb +252 -200
- data/test/test_rrschedule.rb +162 -147
- metadata +8 -7
- data/README.rdoc +0 -125
data/README.markdown
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# RRSchedule #
|
2
|
+
|
3
|
+
RRSchedule makes it easier to generate round-robin schedules for sport leagues.
|
4
|
+
|
5
|
+
It takes into consideration the number of available playing surfaces and game times and split
|
6
|
+
games into gamedays that respect these contraints.
|
7
|
+
|
8
|
+
## Installation ##
|
9
|
+
gem install rrschedule
|
10
|
+
require 'rrschedule'
|
11
|
+
|
12
|
+
## Prepare the schedule ##
|
13
|
+
schedule=RRSchedule::Schedule.new(
|
14
|
+
#array of teams that will compete against each other. If you group teams into multiple flights (divisions),
|
15
|
+
#a separate round-robin is generated in each of them but the "physical constraints" are shared
|
16
|
+
:teams => [
|
17
|
+
%w(A1 A2 A3 A4 A5 A6 A7 A8),
|
18
|
+
%w(B1 B2 B3 B4 B5 B6 B7 B8)
|
19
|
+
],
|
20
|
+
|
21
|
+
#Setup some scheduling rules
|
22
|
+
:rules => [
|
23
|
+
RRSchedule::Rule.new(:wday => 3, :gt => ["7:00PM","9:00PM"], :ps => ["field #1", "field #2"]),
|
24
|
+
RRSchedule::Rule.new(:wday => 5, :gt => ["7:00PM"], :ps => ["field #1"])
|
25
|
+
],
|
26
|
+
|
27
|
+
#First games are played on...
|
28
|
+
:start_date => Date.parse("2010/10/13"),
|
29
|
+
|
30
|
+
#array of dates to exclude
|
31
|
+
:exclude_dates => [Date.parse("2010/11/24"),Date.parse("2010/12/15")],
|
32
|
+
|
33
|
+
#Number of times each team must play against each other (default is 1)
|
34
|
+
:cycles => 1,
|
35
|
+
|
36
|
+
#Shuffle team order before each cycle. Default is true
|
37
|
+
:shuffle => true
|
38
|
+
)
|
39
|
+
|
40
|
+
## Generate the schedule ##
|
41
|
+
schedule.generate
|
42
|
+
|
43
|
+
## Playing with the output ##
|
44
|
+
|
45
|
+
### human readable schedule ###
|
46
|
+
puts schedule.to_s
|
47
|
+
|
48
|
+
### Iterate through schedule ###
|
49
|
+
schedule.gamedays.each do |gd|
|
50
|
+
puts gd.date.strftime("%Y/%m/%d")
|
51
|
+
puts "===================="
|
52
|
+
gd.games.each do |g|
|
53
|
+
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")}"
|
54
|
+
end
|
55
|
+
puts "\n"
|
56
|
+
end
|
57
|
+
|
58
|
+
### Display each round of the round-robin(s) without any date/time or playing location info ###
|
59
|
+
puts s.rounds.collect{|r| r.to_s}
|
60
|
+
|
61
|
+
## Issues / Other ##
|
62
|
+
|
63
|
+
Hope this gem will be useful to some people!
|
64
|
+
|
65
|
+
You can read my [blog](http://www.rubyfleebie.com)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Branches summary
|
2
|
+
|
3
|
+
The objective of this file is to share the intention behind every new branches that are created for RRSchedule. Since each branch
|
4
|
+
generally represents a "development effort", I think it is useful to describe and explain them.
|
5
|
+
|
6
|
+
## Rules and multiple round-robins (branch created on 2011/01/21)
|
7
|
+
|
8
|
+
### Rules
|
9
|
+
|
10
|
+
The idea behind having "rules" is to allow more flexibility in the schedule generation.
|
11
|
+
|
12
|
+
At the moment, we can tell RRSchedule that the games are played (for example) every monday and wednesday at 7:00PM and 9:00PM on four different
|
13
|
+
playing surfaces. But what if games played on monday are held at 7:00PM while games played on wednesday are held at 7:00PM and 9:00PM?
|
14
|
+
You just cannot configure it this way at the moment. And what if on monday every playing surfaces are available while only 1 is available on wednesday?
|
15
|
+
This is why I had the idea of replacing the current "one size fits all" system with a more flexible system.
|
16
|
+
|
17
|
+
Thus, we will be able to have rules like:
|
18
|
+
|
19
|
+
Day of week | Game Time | playing surfaces |
|
20
|
+
Monday | 7:00PM | Field #1, Field #2 |
|
21
|
+
Wednesday | 7:00PM | Field #1 |
|
22
|
+
Wednesday | 9:00PM | Field #1
|
23
|
+
|
24
|
+
In the code it might look something like this:
|
25
|
+
|
26
|
+
schedule.rules << Rule.new(:wday => 1, :gt => "7:00PM", :ps => ["Field #1", "Field #2"])
|
27
|
+
schedule.rules << Rule.new(:wday => 3, :gt => "7:00PM", :ps => "Field #1")
|
28
|
+
schedule.rules << Rule.new(:wday => 3, :gt => "9:00PM", :ps => "Field #1")
|
29
|
+
|
30
|
+
schedule.generate
|
31
|
+
|
32
|
+
...
|
33
|
+
|
34
|
+
### Multiple round-robins
|
35
|
+
|
36
|
+
The other development in this branch involves the possibility to create multiple round-robins in the same method call while sharing the same
|
37
|
+
physical constraints (game times and playing surfaces)
|
38
|
+
|
39
|
+
Several sport leagues use a "division" system where teams are seeded in different groups based on their performance. Each group plays
|
40
|
+
a round-robin *inside* its own division. Suppose a league of 4 divisions containing 8 teams each. A1 will play against A2, A3 and so on but not
|
41
|
+
against B1, C3 or D6. What we need in this case is 4 different round-robins. However, these 4 round-robins share the same playing surfaces and
|
42
|
+
game times.
|
43
|
+
|
44
|
+
In other words, you will be able to do something like this:
|
45
|
+
|
46
|
+
schedule.teams = [
|
47
|
+
[A1,A2,A3,A4,A5,A6,A7,A8],
|
48
|
+
[B1,B2,B3,B4,B5,B6,B7,B8],
|
49
|
+
[C1,C2,C3,C4,C5,C6,C7,C8],
|
50
|
+
[D1,D2,D3,D4,D5,D6,D7,D8]
|
51
|
+
]
|
52
|
+
|
53
|
+
schedule.rules << Rule.new(
|
54
|
+
... #same rules apply for all 32 teams
|
55
|
+
)
|
data/lib/rrschedule.rb
CHANGED
@@ -3,155 +3,89 @@
|
|
3
3
|
############################################################################################################################
|
4
4
|
module RRSchedule
|
5
5
|
class Schedule
|
6
|
-
attr_reader :
|
7
|
-
|
8
|
-
|
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|
|
33
|
-
begin
|
34
|
-
DateTime.parse(gt)
|
35
|
-
rescue
|
36
|
-
raise "game times must be valid time representations in the string form (e.g. 3:00 PM, 11:00 AM, 18:20, etc)"
|
37
|
-
end
|
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
|
-
|
6
|
+
attr_reader :flights, :rounds, :gamedays
|
7
|
+
attr_accessor :teams, :rules, :cycles, :start_date, :exclude_dates,:shuffle
|
8
|
+
|
71
9
|
def initialize(params={})
|
72
10
|
@gamedays = []
|
73
|
-
self.teams = params[:teams]
|
74
|
-
self.
|
75
|
-
self.
|
76
|
-
self.
|
77
|
-
self.
|
78
|
-
self.
|
79
|
-
self.exclude_dates = params[:exclude_dates]
|
80
|
-
self.start_date = params[:start_date]
|
81
|
-
self.wdays = params[:wdays]
|
11
|
+
self.teams = params[:teams] if params[:teams]
|
12
|
+
self.cycles = params[:cycles] || 1
|
13
|
+
self.shuffle = params[:shuffle].nil? ? true : params[:shuffle]
|
14
|
+
self.exclude_dates = params[:exclude_dates] || []
|
15
|
+
self.start_date = params[:start_date] || Date.today
|
16
|
+
self.rules = params[:rules] || []
|
82
17
|
self
|
83
18
|
end
|
84
|
-
|
85
|
-
|
19
|
+
|
86
20
|
#This will generate the schedule based on the various parameters
|
87
|
-
#TODO: consider refactoring with a recursive algorithm
|
88
21
|
def generate(params={})
|
22
|
+
raise "You need to specify at least 1 team" if @teams.nil? || @teams.empty?
|
23
|
+
raise "You need to specify at least 1 rule" if @rules.nil? || @rules.empty?
|
24
|
+
arrange_flights
|
89
25
|
@gamedays = []
|
90
26
|
@rounds = []
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
current_round += 1
|
112
|
-
|
113
|
-
@rounds ||= []
|
114
|
-
@rounds << Round.new(
|
115
|
-
:round => current_round,
|
116
|
-
:games => games.collect { |g| Game.new(
|
117
|
-
:team_a => g[:team_a],
|
118
|
-
:team_b => g[:team_b])
|
119
|
-
})
|
120
|
-
|
121
|
-
reject_dummy = lambda {|g| g[:team_a] == :dummy || g[:team_b] == :dummy}
|
122
|
-
games.reject! {|g| reject_dummy.call(g)}
|
123
|
-
all_games.reject! {|g| reject_dummy.call(g)}
|
124
|
-
|
125
|
-
@teams = @teams.insert(1,@teams.delete_at(@teams.size-1))
|
126
|
-
|
127
|
-
#If we have completed a cycle
|
128
|
-
if @teams == initial_order
|
129
|
-
current_cycle += 1
|
130
|
-
#Shuffle the teams at each cycle
|
131
|
-
if current_cycle <= self.cycles && self.shuffle_initial_order
|
132
|
-
@teams = @teams.sort_by{rand}
|
133
|
-
initial_order = @teams.clone
|
27
|
+
|
28
|
+
@flights.each_with_index do |teams,flight_id|
|
29
|
+
current_cycle = current_round = 0
|
30
|
+
teams = teams.sort_by{rand} if @shuffle
|
31
|
+
|
32
|
+
#loop to generate the whole round-robin(s) for the current flight
|
33
|
+
begin
|
34
|
+
t = teams.clone
|
35
|
+
games = []
|
36
|
+
|
37
|
+
#process one round
|
38
|
+
while !t.empty? do
|
39
|
+
team_a = t.shift
|
40
|
+
team_b = t.reverse!.shift
|
41
|
+
t.reverse!
|
42
|
+
|
43
|
+
matchup = {:team_a => team_a, :team_b => team_b}
|
44
|
+
games << matchup
|
134
45
|
end
|
135
|
-
|
136
|
-
end until @teams == initial_order && current_cycle==self.cycles
|
46
|
+
#done processing round
|
137
47
|
|
138
|
-
|
139
|
-
self
|
140
|
-
end
|
48
|
+
current_round += 1
|
141
49
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
50
|
+
#Team rotation
|
51
|
+
teams = teams.insert(1,teams.delete_at(teams.size-1))
|
52
|
+
|
53
|
+
#add the round in memory
|
54
|
+
@rounds ||= []
|
55
|
+
@rounds[flight_id] ||= []
|
56
|
+
@rounds[flight_id] << Round.new(
|
57
|
+
:round => current_round,
|
58
|
+
:flight => flight_id,
|
59
|
+
:games => games.collect { |g|
|
60
|
+
Game.new(
|
61
|
+
:team_a => g[:team_a],
|
62
|
+
:team_b => g[:team_b]
|
63
|
+
)
|
64
|
+
}
|
65
|
+
)
|
66
|
+
#done adding round
|
67
|
+
|
68
|
+
#have we completed a full round-robin for the current flight?
|
69
|
+
if current_round == teams.size-1
|
70
|
+
current_cycle += 1
|
71
|
+
|
72
|
+
if current_cycle < self.cycles
|
73
|
+
current_round = 0
|
74
|
+
teams = teams.sort_by{rand} if @shuffle
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end until current_round == teams.size-1 && current_cycle==self.cycles
|
147
79
|
end
|
148
|
-
|
80
|
+
|
81
|
+
dispatch_games(@rounds)
|
82
|
+
self
|
149
83
|
end
|
150
|
-
|
84
|
+
|
151
85
|
#human readable schedule
|
152
86
|
def to_s
|
153
87
|
res = ""
|
154
|
-
res << "#{self.gamedays.size.to_s} gamedays\n"
|
88
|
+
res << "#{self.gamedays.size.to_s} gamedays\n"
|
155
89
|
self.gamedays.each do |gd|
|
156
90
|
res << gd.date.strftime("%Y-%m-%d") + "\n"
|
157
91
|
res << "==========\n"
|
@@ -163,92 +97,196 @@ module RRSchedule
|
|
163
97
|
res
|
164
98
|
end
|
165
99
|
|
166
|
-
#return an array of Game instances where 'team' is playing
|
167
|
-
def by_team(team)
|
168
|
-
gms=[]
|
169
|
-
self.gamedays.each do |gd|
|
170
|
-
gms << gd.games.select{|g| g.team_a == team || g.team_b == team}
|
171
|
-
end
|
172
|
-
gms.flatten
|
173
|
-
end
|
174
|
-
|
175
100
|
#returns true if the generated schedule is a valid round-robin (for testing purpose)
|
176
|
-
def round_robin?
|
101
|
+
def round_robin?(flight_id=nil)
|
177
102
|
#each round-robin round should contains n-1 games where n is the nbr of teams (:dummy included if odd)
|
178
|
-
return false if self.rounds.size != (@
|
179
|
-
|
103
|
+
return false if self.rounds[flight_id].size != (@flights[flight_id].size*self.cycles)-self.cycles
|
104
|
+
|
180
105
|
#check if each team plays the same number of games against each other
|
181
|
-
|
182
|
-
|
183
|
-
return false unless
|
106
|
+
@flights[flight_id].each do |t1|
|
107
|
+
@flights[flight_id].reject{|t| t == t1}.each do |t2|
|
108
|
+
return false unless face_to_face(t1,t2).size == self.cycles || [t1,t2].include?(:dummy)
|
184
109
|
end
|
185
110
|
end
|
186
111
|
return true
|
187
112
|
end
|
188
|
-
|
189
|
-
private
|
190
|
-
|
191
|
-
def
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def arrange_flights
|
117
|
+
#a flight is a division where teams play round-robin against each other
|
118
|
+
@flights = Marshal.load(Marshal.dump(@teams)) #deep clone
|
119
|
+
|
120
|
+
#If teams aren't in flights, we create a single flight and put all teams in it
|
121
|
+
@flights = [@flights] unless @flights.first.respond_to?(:to_ary)
|
122
|
+
|
123
|
+
@flights.each_with_index do |flight,i|
|
124
|
+
raise ":dummy is a reserved team name. Please use something else" if flight.member?(:dummy)
|
125
|
+
raise "at least 2 teams are required" if flight.size < 2
|
126
|
+
raise "teams have to be unique" if flight.uniq.size < flight.size
|
127
|
+
@flights[i] << :dummy if flight.size.odd?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
#Dispatch games according to available playing surfaces and game times
|
132
|
+
#The flat schedule contains "place holders" for the actual games. Each row contains
|
133
|
+
#a game date, a game time and a playing surface. We then process our rounds one by one
|
134
|
+
#and we put each matchup in the next available slot of the flat schedule
|
135
|
+
def dispatch_games(rounds)
|
136
|
+
flat_schedule = generate_flat_schedule
|
137
|
+
|
138
|
+
rounds_copy = Marshal.load(Marshal.dump(rounds)) #deep clone
|
139
|
+
cur_flight_index = i = 0
|
140
|
+
|
141
|
+
while !rounds_copy.flatten.empty? do
|
142
|
+
cur_round = rounds_copy[cur_flight_index].shift
|
143
|
+
|
144
|
+
#process the next round in the current flight
|
145
|
+
if cur_round
|
146
|
+
cur_round.games.each do |game|
|
147
|
+
unless [game.team_a,game.team_b].include?(:dummy)
|
148
|
+
flat_schedule[i][:team_a] = game.team_a
|
149
|
+
flat_schedule[i][:team_b] = game.team_b
|
150
|
+
i+=1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
if cur_flight_index == @flights.size-1
|
157
|
+
cur_flight_index = 0
|
158
|
+
else
|
159
|
+
cur_flight_index += 1
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
#We group our flat schedule by gameday
|
164
|
+
s=flat_schedule.group_by{|fs| fs[:gamedate]}.sort
|
165
|
+
s.each do |gamedate,gms|
|
166
|
+
games = []
|
167
|
+
gms.each do |gm|
|
168
|
+
games << Game.new(
|
169
|
+
:team_a => gm[:team_a],
|
170
|
+
:team_b => gm[:team_b],
|
171
|
+
:playing_surface => gm[:ps],
|
172
|
+
:game_time => gm [:gt]
|
173
|
+
)
|
174
|
+
end
|
175
|
+
self.gamedays << Gameday.new(:date => gamedate, :games => games)
|
176
|
+
end
|
177
|
+
self.gamedays.each { |gd| gd.games.reject! {|g| g.team_a.nil?}}
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
def generate_flat_schedule
|
182
|
+
flat_schedule = []
|
183
|
+
games_left = max_games_per_day = day_game_ctr = rule_ctr = 0
|
184
|
+
|
185
|
+
#determine first rule based on the nearest gameday
|
186
|
+
cur_rule = @rules.select{|r| r.wday >= self.start_date.wday}.first || @rules.first
|
187
|
+
cur_rule_index = @rules.index(cur_rule)
|
188
|
+
cur_date = next_game_date(self.start_date,cur_rule.wday)
|
189
|
+
|
190
|
+
@flights.each do |flight|
|
191
|
+
games_left += @cycles * (flight.include?(:dummy) ? ((flight.size-1)/2.0)*(flight.size-2) : (flight.size/2)*(flight.size-1))
|
192
|
+
max_games_per_day += (flight.include?(:dummy) ? (flight.size-2)/2.0 : (flight.size-1)/2.0).ceil
|
193
|
+
end
|
194
|
+
|
195
|
+
#process all games
|
196
|
+
while games_left > 0 do
|
197
|
+
cur_rule.gt.each do |gt|
|
198
|
+
cur_rule.ps.each do |ps|
|
199
|
+
|
200
|
+
#if there are more physical resources (playing surfaces and game times) for a given day than
|
201
|
+
#we need, we don't use them all (or else some teams would play twice on a single day)
|
202
|
+
if day_game_ctr <= max_games_per_day-1
|
203
|
+
flat_schedule << {:gamedate => cur_date, :gt => gt, :ps => ps}
|
204
|
+
games_left -= 1; day_game_ctr += 1
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
last_rule = cur_rule
|
210
|
+
last_date = cur_date
|
211
|
+
|
212
|
+
#Advance to the next rule (if we're at the last one, we go back to the first)
|
213
|
+
cur_rule_index = (cur_rule_index == @rules.size-1) ? 0 : cur_rule_index + 1
|
214
|
+
cur_rule = @rules[cur_rule_index]
|
215
|
+
|
216
|
+
#Go to the next date (except if the new rule is for the same weekday)
|
217
|
+
if cur_rule.wday != last_rule.wday || cur_rule_index == 0
|
218
|
+
cur_date = next_game_date(cur_date+=1,cur_rule.wday)
|
219
|
+
day_game_ctr = 0
|
218
220
|
end
|
219
|
-
|
220
|
-
gameday.games = gameday.games.sort_by {|g| [g.game_time,g.playing_surface]}
|
221
|
-
self.gamedays << gameday
|
222
|
-
cur_date += 1
|
223
221
|
end
|
222
|
+
flat_schedule
|
224
223
|
end
|
225
|
-
|
224
|
+
|
226
225
|
#get the next gameday
|
227
226
|
def next_game_date(dt,wday)
|
228
227
|
dt += 1 until wday == dt.wday && !self.exclude_dates.include?(dt)
|
229
228
|
dt
|
230
229
|
end
|
231
|
-
|
232
|
-
#
|
233
|
-
def
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
self.optimize ? (self.playing_surfaces.size * self.game_times.size) : self.teams.size/2
|
230
|
+
|
231
|
+
#return matchups between two teams
|
232
|
+
def face_to_face(team_a,team_b)
|
233
|
+
res=[]
|
234
|
+
self.gamedays.each do |gd|
|
235
|
+
res << gd.games.select {|g| (g.team_a == team_a && g.team_b == team_b) || (g.team_a == team_b && g.team_b == team_a)}
|
238
236
|
end
|
237
|
+
res.flatten
|
239
238
|
end
|
240
|
-
end
|
239
|
+
end
|
241
240
|
|
242
241
|
class Gameday
|
243
242
|
attr_accessor :date, :games
|
244
|
-
|
243
|
+
|
245
244
|
def initialize(params)
|
246
245
|
self.date = params[:date]
|
247
246
|
self.games = params[:games] || []
|
248
247
|
end
|
249
|
-
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
class Rule
|
252
|
+
attr_accessor :wday, :gt, :ps
|
253
|
+
|
254
|
+
|
255
|
+
def initialize(params)
|
256
|
+
self.wday = params[:wday]
|
257
|
+
self.gt = params[:gt]
|
258
|
+
self.ps = params[:ps]
|
259
|
+
end
|
260
|
+
|
261
|
+
def wday=(wday)
|
262
|
+
@wday = wday ? wday : 1
|
263
|
+
raise "Rule#wday must be between 0 and 6" unless (0..6).include?(@wday)
|
264
|
+
end
|
265
|
+
|
266
|
+
#Array of available playing surfaces. You can pass it any kind of object
|
267
|
+
def ps=(ps)
|
268
|
+
@ps = Array(ps).empty? ? ["Field #1", "Field #2"] : Array(ps)
|
269
|
+
end
|
270
|
+
|
271
|
+
#Array of game times where games are played. Must be valid DateTime objects in the string form
|
272
|
+
def gt=(gt)
|
273
|
+
@gt = Array(gt).empty? ? ["7:00 PM"] : Array(gt)
|
274
|
+
@gt.collect! do |gt|
|
275
|
+
begin
|
276
|
+
DateTime.parse(gt)
|
277
|
+
rescue
|
278
|
+
raise "game times must be valid time representations in the string form (e.g. 3:00 PM, 11:00 AM, 18:20, etc)"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def <=>(other)
|
284
|
+
self.wday == other.wday ?
|
285
|
+
DateTime.parse(self.gt.first.to_s) <=> DateTime.parse(other.gt.first.to_s) :
|
286
|
+
self.wday <=> other.wday
|
287
|
+
end
|
250
288
|
end
|
251
|
-
|
289
|
+
|
252
290
|
class Game
|
253
291
|
attr_accessor :team_a, :team_b, :playing_surface, :game_time, :game_date
|
254
292
|
alias :ta :team_a
|
@@ -256,23 +294,37 @@ module RRSchedule
|
|
256
294
|
alias :ps :playing_surface
|
257
295
|
alias :gt :game_time
|
258
296
|
alias :gd :game_date
|
259
|
-
|
297
|
+
|
260
298
|
def initialize(params={})
|
261
299
|
self.team_a = params[:team_a]
|
262
300
|
self.team_b = params[:team_b]
|
263
301
|
self.playing_surface = params[:playing_surface]
|
264
|
-
self.game_time = params[:game_time]
|
302
|
+
self.game_time = params[:game_time]
|
265
303
|
self.game_date = params[:game_date]
|
266
304
|
end
|
267
305
|
end
|
268
|
-
|
306
|
+
|
269
307
|
class Round
|
270
|
-
attr_accessor :round, :games
|
271
|
-
|
308
|
+
attr_accessor :round, :games,:flight
|
309
|
+
|
272
310
|
def initialize(params={})
|
273
311
|
self.round = params[:round]
|
312
|
+
self.flight = params[:flight]
|
274
313
|
self.games = params[:games] || []
|
275
314
|
end
|
315
|
+
|
316
|
+
def to_s
|
317
|
+
str = "FLIGHT #{@flight.to_s} - Round ##{@round.to_s}\n"
|
318
|
+
str += "=====================\n"
|
319
|
+
|
320
|
+
self.games.each do |g|
|
321
|
+
if [g.team_a,g.team_b].include?(:dummy)
|
322
|
+
str+= g.team_a == :dummy ? g.team_b.to_s : g.team_a.to_s + " has a BYE\n"
|
323
|
+
else
|
324
|
+
str += g.team_a.to_s + " Vs " + g.team_b.to_s + "\n"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
str += "\n"
|
328
|
+
end
|
276
329
|
end
|
277
330
|
end
|
278
|
-
|
data/test/test_rrschedule.rb
CHANGED
@@ -1,163 +1,178 @@
|
|
1
1
|
require 'helper'
|
2
|
-
|
3
2
|
class TestRrschedule < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
assert schedule.start_date.is_a?(Date)
|
13
|
-
assert schedule.shuffle_initial_order
|
14
|
-
assert schedule.optimize
|
15
|
-
assert schedule.wdays.select{|w| (0..6).member? w} == schedule.wdays
|
16
|
-
assert schedule.exclude_dates.empty?
|
17
|
-
end
|
18
|
-
|
19
|
-
should "have a dummy team when number of teams is odd" do
|
20
|
-
schedule = RRSchedule::Schedule.new(:teams => Array(1..9))
|
21
|
-
assert schedule.teams.size == 10
|
22
|
-
assert schedule.teams.member?(:dummy), "There should always be a :dummy team when the nbr of teams is odd"
|
23
|
-
end
|
24
|
-
|
25
|
-
should "not have a dummy team when number of teams is even" do
|
26
|
-
schedule = RRSchedule::Schedule.new(:teams => Array(1..6))
|
27
|
-
assert schedule.teams.size == 6
|
28
|
-
assert !schedule.teams.member?(:dummy), "There should never be a :dummy team when the nbr of teams is even"
|
29
|
-
end
|
30
|
-
|
31
|
-
should "not have a team named :dummy in the initial array" do
|
32
|
-
assert_raise RuntimeError do
|
33
|
-
schedule = RRSchedule::Schedule.new(
|
34
|
-
:teams => Array(1..4) << :dummy
|
35
|
-
)
|
36
|
-
end
|
3
|
+
include RRSchedule
|
4
|
+
context "new instance without params" do
|
5
|
+
setup {@s= Schedule.new}
|
6
|
+
should "have default values for some options" do
|
7
|
+
assert_equal 1, @s.cycles
|
8
|
+
assert @s.shuffle
|
9
|
+
assert_equal Date.today, @s.start_date
|
10
|
+
assert_equal [], @s.exclude_dates
|
37
11
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
assert_equal
|
64
|
-
assert_equal
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
12
|
+
end
|
13
|
+
|
14
|
+
context "no teams" do
|
15
|
+
setup {@s = Schedule.new(:rules => [Rule.new(:wday => 1, :gt => ["7:00PM"], :ps => %w(one two))])}
|
16
|
+
should "raise an exception" do
|
17
|
+
exception = assert_raise(RuntimeError){@s.generate}
|
18
|
+
assert_equal "You need to specify at least 1 team", exception.message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "no flight" do
|
23
|
+
setup{@s=Schedule.new(:teams => %w(1 2 3 4 5 6), :rules => some_rules)}
|
24
|
+
should "be wrapped into a single flight in the normalized array" do
|
25
|
+
@s.generate
|
26
|
+
assert_equal [%w(1 2 3 4 5 6)], @s.flights
|
27
|
+
end
|
28
|
+
|
29
|
+
should "not modify the original array" do
|
30
|
+
assert_equal %w(1 2 3 4 5 6), @s.teams
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "odd number of teams without flight" do
|
35
|
+
setup {@s=Schedule.new(:teams => %w(1 2 3 4 5),:rules => some_rules).generate}
|
36
|
+
should "add a dummy competitor in the created flight" do
|
37
|
+
assert_equal 1, @s.flights.size
|
38
|
+
assert_equal 6, @s.flights.first.size
|
39
|
+
assert @s.flights.first.include?(:dummy)
|
40
|
+
end
|
41
|
+
|
42
|
+
should "not modify the original array" do
|
43
|
+
assert_equal 5, @s.teams.size
|
44
|
+
assert !@s.teams.include?(:dummy)
|
71
45
|
end
|
72
|
-
|
73
|
-
should "have default teams if non was specified" do
|
74
|
-
schedule = RRSchedule::Schedule.new
|
75
|
-
assert schedule.teams.size > 1
|
76
|
-
end
|
77
|
-
|
78
|
-
should "not have a team that is specified twice" do
|
79
|
-
assert_raise RuntimeError do
|
80
|
-
schedule = RRSchedule::Schedule.new(:teams => %w(a a b c d e f g h i))
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
46
|
end
|
85
|
-
|
86
|
-
|
47
|
+
|
48
|
+
|
49
|
+
context "extra available resources" do
|
87
50
|
setup do
|
88
|
-
@s =
|
89
|
-
:teams => %w(
|
90
|
-
:
|
91
|
-
|
92
|
-
|
51
|
+
@s = Schedule.new(
|
52
|
+
:teams => %w(a1 a2 a3 a4 a5),
|
53
|
+
:rules => [
|
54
|
+
Rule.new(
|
55
|
+
:wday => 3,
|
56
|
+
:gt => ["7:00PM", "9:00PM"],
|
57
|
+
:ps => %w(one two three four)
|
58
|
+
)
|
59
|
+
]
|
60
|
+
).generate
|
93
61
|
end
|
94
62
|
|
95
|
-
should "have
|
96
|
-
@s.wdays = [3,5]
|
97
|
-
@s.generate
|
98
|
-
|
63
|
+
should "have a maximum of (teams/2) games per day" do
|
99
64
|
@s.gamedays.each do |gd|
|
100
|
-
assert
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
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)
|
65
|
+
assert gd.games.size <= @s.teams.size/2
|
108
66
|
end
|
109
67
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
68
|
+
|
69
|
+
should "not have a team that play more than once on a single day" do
|
70
|
+
@s.gamedays.each do |gd|
|
71
|
+
day_teams = gd.games.collect{|g| [g.team_a,g.team_b]}.flatten
|
72
|
+
unique_day_teams = day_teams.uniq
|
73
|
+
assert_equal day_teams.size, unique_day_teams.size
|
120
74
|
end
|
121
75
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
context "multi flights" do
|
80
|
+
setup do
|
81
|
+
@s = Schedule.new(
|
82
|
+
:teams => [
|
83
|
+
%w(A1 A2 A3 A4 A5 A6 A7 A8),
|
84
|
+
%w(B1 B2 B3 B4 B5 B6 B7 B8),
|
85
|
+
%w(C1 C2 C3 C4 C5 C6 C7 C8),
|
86
|
+
%w(D1 D2 D3 D4 D5 D6 D7 D8)
|
87
|
+
],
|
88
|
+
|
89
|
+
:rules => [
|
90
|
+
Rule.new(
|
91
|
+
:wday => 3,
|
92
|
+
:gt => ["7:00PM", "9:00PM"],
|
93
|
+
:ps => ["one","two"]
|
94
|
+
)
|
95
|
+
],
|
96
|
+
|
97
|
+
:start_date => Date.parse("2011/01/26"),
|
98
|
+
:exclude_dates => [
|
99
|
+
Date.parse("2011/02/02")
|
100
|
+
]
|
101
|
+
).generate
|
102
|
+
end
|
103
|
+
|
104
|
+
should "generate separate round-robins" do
|
105
|
+
assert_equal 4, @s.flights.size
|
106
|
+
4.times { |i| assert @s.round_robin?(i)}
|
107
|
+
end
|
108
|
+
|
109
|
+
should "have a correct total number of games" do
|
110
|
+
assert_equal 112, @s.gamedays.collect{|gd| gd.games.size}.inject{|x,sum| x+sum}
|
111
|
+
end
|
112
|
+
|
113
|
+
should "not have games for a date that is excluded" do
|
114
|
+
assert !@s.gamedays.collect{|gd| gd.date}.include?(Date.parse("2011/02/02"))
|
115
|
+
assert @s.gamedays.collect{|gd| gd.date}.include?(Date.parse("2011/02/09"))
|
141
116
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
)
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
117
|
+
end
|
118
|
+
|
119
|
+
##### RULES #######
|
120
|
+
should "auto create array for gt and ps" do
|
121
|
+
@s = Schedule.new(
|
122
|
+
:teams => %w(a1 a2 a4 a5),
|
123
|
+
:rules => [
|
124
|
+
Rule.new(:wday => 1, :gt => "7:00PM", :ps => "The Field")
|
125
|
+
]
|
126
|
+
).generate
|
127
|
+
|
128
|
+
assert_equal [DateTime.parse("7:00PM")], @s.rules.first.gt
|
129
|
+
assert_equal ["The Field"], @s.rules.first.ps
|
130
|
+
end
|
131
|
+
|
132
|
+
context "no rules specified" do
|
133
|
+
setup {@s = Schedule.new(:teams => %w(a1 a2 a4 a5))}
|
134
|
+
should "raise an exception" do
|
135
|
+
exception = assert_raise(RuntimeError){@s.generate}
|
136
|
+
assert_equal "You need to specify at least 1 rule", exception.message
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "multiple rules on the same weekday" do
|
141
|
+
setup do
|
142
|
+
@s = Schedule.new
|
143
|
+
@s.teams = [%w(a1 a2 a3 a4 a5), %w(b1 b2 b3 b4 b5 b6 b7 b8)]
|
144
|
+
@s.rules = [
|
145
|
+
Rule.new(:wday => 4, :gt => ["7:00PM"], :ps => %w(field#1 field#2)),
|
146
|
+
Rule.new(:wday => 4, :gt => ["9:00PM"], :ps => %w(field#1 field#2 field#3))
|
147
|
+
]
|
148
|
+
@s.start_date = Date.parse("2011/01/27")
|
149
|
+
@s.generate
|
150
|
+
end
|
151
|
+
|
152
|
+
should "keep games on the same day" do
|
153
|
+
cur_date = @s.start_date
|
154
|
+
@s.gamedays.each_with_index do |gd,i|
|
155
|
+
assert_equal cur_date, gd.date
|
156
|
+
|
157
|
+
#check all days to make sure that our rules are respected. We don't check
|
158
|
+
#the last one because it might not be full (round-robin over)
|
159
|
+
if i<@s.gamedays.size-1
|
160
|
+
assert_equal 5, gd.games.size
|
161
|
+
assert_equal 1, gd.games.select{|g| g.game_time == DateTime.parse("7:00PM") && g.playing_surface == "field#1"}.size
|
162
|
+
assert_equal 1, gd.games.select{|g| g.game_time == DateTime.parse("7:00PM") && g.playing_surface == "field#2"}.size
|
163
|
+
assert_equal 1, gd.games.select{|g| g.game_time == DateTime.parse("9:00PM") && g.playing_surface == "field#1"}.size
|
164
|
+
assert_equal 1, gd.games.select{|g| g.game_time == DateTime.parse("9:00PM") && g.playing_surface == "field#2"}.size
|
165
|
+
assert_equal 1, gd.games.select{|g| g.game_time == DateTime.parse("9:00PM") && g.playing_surface == "field#3"}.size
|
166
|
+
cur_date += 7
|
167
|
+
end
|
154
168
|
end
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def some_rules
|
173
|
+
[
|
174
|
+
Rule.new(:wday => 1, :gt => "7:00PM", :ps => "one"),
|
175
|
+
Rule.new(:wday => 1, :gt => "8:00PM", :ps => %w(one two))
|
176
|
+
]
|
177
|
+
end
|
163
178
|
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: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
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:
|
18
|
+
date: 2011-01-25 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -40,14 +40,15 @@ extensions: []
|
|
40
40
|
|
41
41
|
extra_rdoc_files:
|
42
42
|
- LICENSE
|
43
|
-
- README.
|
43
|
+
- README.markdown
|
44
44
|
files:
|
45
45
|
- .document
|
46
46
|
- .gitignore
|
47
47
|
- LICENSE
|
48
|
-
- README.
|
48
|
+
- README.markdown
|
49
49
|
- Rakefile
|
50
50
|
- VERSION
|
51
|
+
- branches_info.markdown
|
51
52
|
- lib/rrschedule.rb
|
52
53
|
- test/helper.rb
|
53
54
|
- test/test_rrschedule.rb
|
data/README.rdoc
DELETED
@@ -1,125 +0,0 @@
|
|
1
|
-
= rrschedule
|
2
|
-
|
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
|
-
start date, the day(s) of the week where the games are played and some other options.
|
5
|
-
|
6
|
-
It takes into consideration physical constraints such as the number of playing surfaces availables and game times.
|
7
|
-
Each round of the round-robin is splitted into multiple gamedays that respect these constraints.
|
8
|
-
|
9
|
-
Say for example that you want to generate a round-robin schedule for your 15-teams volleyball league.
|
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. So rrschedule will put the remaining games of this round on the next gameday
|
12
|
-
and will start a new round right after.
|
13
|
-
|
14
|
-
|
15
|
-
== Demo
|
16
|
-
Online round-robin generator using RRSchedule: http://rrschedule.azanka.ca
|
17
|
-
|
18
|
-
== Installation
|
19
|
-
gem install rrschedule
|
20
|
-
require 'rrschedule'
|
21
|
-
|
22
|
-
== Prepare the schedule
|
23
|
-
teams = ["Rockets","Jetpacks","Snakes","Cobras","Wolves","Huskies","Tigers","Lions",
|
24
|
-
"Moose","Sprinklers","Pacers","Cyclops","Munchkins","Magicians","French Fries"]
|
25
|
-
|
26
|
-
schedule=RRSchedule::Schedule.new(
|
27
|
-
#array of teams that will compete against each other in the season
|
28
|
-
:teams => teams,
|
29
|
-
|
30
|
-
#list of available playing surfaces (volleyball fields, curling sheets, tennis courts, etc)
|
31
|
-
:playing_surfaces => ["A","B","C","D"],
|
32
|
-
|
33
|
-
#day(s) of the week where games are played
|
34
|
-
:wdays => [3],
|
35
|
-
|
36
|
-
#Season will start on...
|
37
|
-
:start_date => Date.parse("2010/10/13"),
|
38
|
-
|
39
|
-
#array of dates WITHOUT games
|
40
|
-
:exclude_dates => [
|
41
|
-
Date.parse("2010/11/24"),
|
42
|
-
Date.parse("2010/12/15"),
|
43
|
-
Date.parse("2010/12/22"),
|
44
|
-
Date.parse("2010/12/29")
|
45
|
-
],
|
46
|
-
|
47
|
-
#1 for Round Robin, 2 for Double Round Robin and so on. Default is 1
|
48
|
-
:cycles => 1,
|
49
|
-
|
50
|
-
#Shuffle team order before each cycle. Default is true
|
51
|
-
:shuffle_initial_order => true,
|
52
|
-
|
53
|
-
#Times of the day where the games are played
|
54
|
-
:game_times => ["10:00 AM", "1:00 PM"]
|
55
|
-
)
|
56
|
-
|
57
|
-
== Generate the schedule
|
58
|
-
schedule.generate
|
59
|
-
|
60
|
-
== Playing with the output
|
61
|
-
|
62
|
-
=== human readable schedule
|
63
|
-
puts schedule.to_s
|
64
|
-
|
65
|
-
=== Iterate through schedule
|
66
|
-
schedule.gamedays.each do |gd|
|
67
|
-
puts gd.date.strftime("%Y/%m/%d")
|
68
|
-
puts "===================="
|
69
|
-
gd.games.each do |g|
|
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")}"
|
71
|
-
end
|
72
|
-
puts "\n"
|
73
|
-
end
|
74
|
-
|
75
|
-
=== Team schedule
|
76
|
-
test_team = "Sprinklers"
|
77
|
-
games=schedule.by_team(test_team)
|
78
|
-
puts "Schedule for team ##{test_team.to_s}"
|
79
|
-
games.each do |g|
|
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")}"
|
81
|
-
end
|
82
|
-
|
83
|
-
=== Face to Face
|
84
|
-
games=schedule.face_to_face("Lions","Moose")
|
85
|
-
puts "FACE TO FACE: Lions Vs Moose"
|
86
|
-
games.each do |g|
|
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")
|
88
|
-
end
|
89
|
-
|
90
|
-
=== Each round of the roun-robin without any date/time or playing location info
|
91
|
-
#If you have an ODD number of teams you will see a "dummy" opponent in each round
|
92
|
-
schedule.rounds.each do |round|
|
93
|
-
puts "Round ##{round.round}"
|
94
|
-
round.games.each do |g|
|
95
|
-
puts g.team_a.to_s + " Vs " + g.team_b.to_s
|
96
|
-
end
|
97
|
-
puts "\n"
|
98
|
-
end
|
99
|
-
|
100
|
-
== Issues / Other
|
101
|
-
|
102
|
-
Starting from version 0.1.5, calling Schedule#gamedays will returns an array of Gameday instances.
|
103
|
-
If you upgrade to this version you will need to change your code accordingly.
|
104
|
-
|
105
|
-
#this won't work anymore
|
106
|
-
schedule.gamedays.each do |gd,games|
|
107
|
-
puts gd
|
108
|
-
|
109
|
-
games.each do |g|
|
110
|
-
end
|
111
|
-
#...
|
112
|
-
end
|
113
|
-
|
114
|
-
#do this instead
|
115
|
-
schedule.gamedays.each do |gd|
|
116
|
-
puts gd.date
|
117
|
-
gd.games.each do |g|
|
118
|
-
end
|
119
|
-
#...
|
120
|
-
end
|
121
|
-
|
122
|
-
|
123
|
-
Hope this gem will be useful to some people!
|
124
|
-
|
125
|
-
You can read my blog here: www.rubyfleebie.com
|