lib/rrschedule.rb in rrschedule-0.1.6 vs lib/rrschedule.rb in rrschedule-0.1.7
- old
+ new
@@ -1,58 +1,116 @@
# rrschedule (Round Robin Schedule generator)
# Auhtor: François Lamontagne
############################################################################################################################
-require 'active_support/all'
module RRSchedule
class Schedule
- attr_accessor :playing_surfaces, :game_times, :cycles, :wdays, :start_date, :exclude_dates, :shuffle_initial_order
- attr_reader :teams, :rounds, :gamedays
+ attr_reader :playing_surfaces, :game_times, :cycles, :wdays, :start_date, :exclude_dates,
+ :shuffle_initial_order, :optimize, :teams, :rounds, :gamedays
- def initialize(params={})
- @gamedays = []
- self.teams = params[:teams] || [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
- self.playing_surfaces = Array(params[:playing_surfaces]).empty? ? ["Surface A", "Surface B"] : Array(params[:playing_surfaces])
- self.cycles = params[:cycles] || 1
-
- self.game_times = Array(params[:game_times]).empty? ? ["7:00 PM", "9:00 PM"] : Array(params[:game_times])
- self.game_times.collect! do |gt|
+
+ #Array of teams that will compete against each other. You can pass it any kind of object
+ def teams=(arr)
+ @teams = arr ? arr.clone : [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
+ raise ":dummy is a reserved team name. Please use something else" if @teams.member?(:dummy)
+ raise "at least 2 teams are required" if @teams.size == 1
+ raise "teams have to be unique" if @teams.uniq.size < @teams.size
+ @teams << :dummy if @teams.size.odd?
+ end
+
+ #Array of available playing surfaces. You can pass it any kind of object
+ def playing_surfaces=(ps)
+ @playing_surfaces = Array(ps).empty? ? ["Surface A", "Surface B"] : Array(ps)
+ end
+
+ #Number of times each team plays against each other
+ def cycles=(cycles)
+ @cycles = cycles || 1
+ end
+
+ #Array of game times where games are played. Must be valid DateTime objects in the string form
+ def game_times=(gt)
+ @game_times = Array(gt).empty? ? ["7:00 PM", "9:00 PM"] : Array(gt)
+ @game_times.collect! do |gt|
begin
DateTime.parse(gt)
rescue
raise "game times must be valid time representations in the string form (e.g. 3:00 PM, 11:00 AM, 18:20, etc)"
end
- end
-
- self.shuffle_initial_order = params[:shuffle_initial_order].nil? ? true : params[:shuffle_initial_order]
- self.exclude_dates = params[:exclude_dates] || []
- self.start_date = params[:start_date] || Time.now.beginning_of_day
- self.wdays = Array(params[:wdays]).empty? ? [1] : Array(params[:wdays])
-
- raise "each value in wdays must be between 0 and 6" if self.wdays.reject{|w| (0..6).member?(w)}.size > 0
+ end
+ end
+
+ #Setting this to true will fill all the available playing surfaces and game times for a given gameday no matter if
+ #one team has to play several games on the same gameday. Setting it to false make sure that teams won't play
+ #more than one game per day.
+ def optimize=(opt)
+ @optimize = opt.nil? ? true : opt
+ end
+
+ #Shuffle the team order at the beginning of every cycles.
+ def shuffle_initial_order=(shuffle)
+ @shuffle_initial_order = shuffle.nil? ? true : shuffle
+ end
+
+ #Array of dates without games
+ def exclude_dates=(dates)
+ @exclude_dates=dates || []
+ end
+
+ #When the season starts? Since we generate the game dates based on weekdays, you need to pass it
+ #a start date in the correct timezone to get accurate game dates for the whole season. Otherwise
+ #you might
+ def start_date=(date)
+ @start_date=date || Date.today
+ end
+
+ #Array of weekdays where games are played (0 is sunday)
+ def wdays=(wdays)
+ @wdays = Array(wdays).empty? ? [1] : Array(wdays)
+ raise "each value in wdays must be between 0 and 6" if @wdays.reject{|w| (0..6).member?(w)}.size > 0
+ end
+
+ def initialize(params={})
+ @gamedays = []
+ self.teams = params[:teams]
+ self.playing_surfaces = params[:playing_surfaces]
+ self.cycles = params[:cycles]
+ self.game_times = params[:game_times]
+ self.optimize = params[:optimize]
+ self.shuffle_initial_order = params[:shuffle_initial_order]
+ self.exclude_dates = params[:exclude_dates]
+ self.start_date = params[:start_date]
+ self.wdays = params[:wdays]
self
end
+
+ #This will generate the schedule based on the various parameters
#TODO: consider refactoring with a recursive algorithm
def generate(params={})
+ @gamedays = []
+ @rounds = []
@teams = @teams.sort_by{rand} if self.shuffle_initial_order
initial_order = @teams.clone
current_cycle = current_round = 0
all_games = []
- #Loop start here
+ #Cycle loop (A cycle is completed when every teams have played one game against each other)
begin
games = []
t = @teams.clone
+
+ #Round loop
while !t.empty? do
team_a = t.shift
team_b = t.reverse!.shift
t.reverse!
- games << {:team_a => team_a, :team_b => team_b}
- all_games << {:team_a => team_a, :team_b => team_b}
+
+ matchup = {:team_a => team_a, :team_b => team_b}
+ games << matchup; all_games << matchup
end
- current_round += 1 #round completed
+ current_round += 1
@rounds ||= []
@rounds << Round.new(
:round => current_round,
:games => games.collect { |g| Game.new(
@@ -125,21 +183,13 @@
return false unless self.face_to_face(t1,t2).size == self.cycles || [t1,t2].include?(:dummy)
end
end
return true
end
-
- def teams=(arr)
- @teams = arr.clone
- raise ":dummy is a reserved team name. Please use something else" if @teams.member?(:dummy)
- raise "at least 2 teams are required" if @teams.size == 1
- raise "teams have to be unique" if @teams.uniq.size < @teams.size
- @teams << :dummy if @teams.size.odd?
- end
private
- #Slice games according to playing surfaces and game times
+ #Slice games according to playing surfaces available and game times
def slice(games)
slices = games.each_slice(games_per_day)
wdays_stack = self.wdays.clone
cur_date = self.start_date
slices.each_with_index do |slice,i|
@@ -167,20 +217,26 @@
ps_stack = self.playing_surfaces.clone if ps_stack.empty?
end
gameday.games = gameday.games.sort_by {|g| [g.game_time,g.playing_surface]}
self.gamedays << gameday
- cur_date += 1.day
+ cur_date += (60*60*24)
end
end
+ #get the next gameday
def next_game_date(dt,wday)
- dt += 1.days until wday == dt.wday && !self.exclude_dates.include?(dt)
+ dt += (60*60*24) until wday == dt.wday && !self.exclude_dates.include?(dt)
dt
end
+ #how many games can we play per day?
def games_per_day
- self.playing_surfaces.size * self.game_times.size
+ if self.teams.size/2 >= (self.playing_surfaces.size * self.game_times.size)
+ (self.playing_surfaces.size * self.game_times.size)
+ else
+ self.optimize ? (self.playing_surfaces.size * self.game_times.size) : self.teams.size/2
+ end
end
end
class Gameday
attr_accessor :date, :games