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 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: