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 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 => Time.zone.parse("2010/10/13"),
37
+ :start_date => Date.parse("2010/10/13"),
36
38
 
37
39
  #array of dates WITHOUT games
38
40
  :exclude_dates => [
39
- Time.zone.parse("2010/11/24"),
40
- Time.zone.parse("2010/12/15"),
41
- Time.zone.parse("2010/12/22"),
42
- Time.zone.parse("2010/12/29")
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.to_s + " on playing surface " + g.playing_surface.to_s + " at " + g.game_time.to_s
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/flamontagne/rrschedule"
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.6
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
- attr_accessor :playing_surfaces, :game_times, :cycles, :wdays, :start_date, :exclude_dates, :shuffle_initial_order
8
- attr_reader :teams, :rounds, :gamedays
6
+ attr_reader :playing_surfaces, :game_times, :cycles, :wdays, :start_date, :exclude_dates,
7
+ :shuffle_initial_order, :optimize, :teams, :rounds, :gamedays
9
8
 
10
- def initialize(params={})
11
- @gamedays = []
12
- self.teams = params[:teams] || [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
13
- self.playing_surfaces = Array(params[:playing_surfaces]).empty? ? ["Surface A", "Surface B"] : Array(params[:playing_surfaces])
14
- self.cycles = params[:cycles] || 1
15
-
16
- self.game_times = Array(params[:game_times]).empty? ? ["7:00 PM", "9:00 PM"] : Array(params[:game_times])
17
- self.game_times.collect! do |gt|
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
- self.shuffle_initial_order = params[:shuffle_initial_order].nil? ? true : params[:shuffle_initial_order]
26
- self.exclude_dates = params[:exclude_dates] || []
27
- self.start_date = params[:start_date] || Time.now.beginning_of_day
28
- self.wdays = Array(params[:wdays]).empty? ? [1] : Array(params[:wdays])
29
-
30
- raise "each value in wdays must be between 0 and 6" if self.wdays.reject{|w| (0..6).member?(w)}.size > 0
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
- #Loop start here
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
- games << {:team_a => team_a, :team_b => team_b}
50
- all_games << {:team_a => team_a, :team_b => team_b}
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 #round completed
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 += 1.day
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 += 1.days until wday == dt.wday && !self.exclude_dates.include?(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
 
@@ -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.respond_to?(:to_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
- should "be a valid round-robin" do
120
- assert @s.round_robin?
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
- should "not have any :dummy teams in the final schedule" do
124
- assert @s.gamedays.collect{|gd| gd.games}.flatten.select{
125
- |g| [g.team_a,g.team_b].include?(:dummy)
126
- }.size == 0
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
- should "be a valid round-robin" do
140
- assert @s.round_robin?
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
- should "not have any :dummy teams in the final schedule" do
144
- assert @s.gamedays.collect{|gd| gd.games}.flatten.select{
145
- |g| [g.team_a,g.team_b].include?(:dummy)
146
- }.size == 0
147
- end
148
- end
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: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 6
10
- version: 0.1.6
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-11 00:00:00 -05:00
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: &id002 !ruby/object:Gem::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: *id002
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/flamontagne/rrschedule
55
+ homepage: http://flamontagne.github.com/rrschedule
70
56
  licenses: []
71
57
 
72
58
  post_install_message: