@ -31,6 +31,7 @@ final class MatchScheduler : ModelObject, Storable {
var groupStageChunkCount : Int ?
var overrideCourtsUnavailability : Bool = false
var shouldTryToFillUpCourtsAvailable : Bool = false
var courtsAvailable : Set < Int > = Set < Int > ( )
init ( tournament : String ,
timeDifferenceLimit : Int = 5 ,
@ -42,7 +43,7 @@ final class MatchScheduler : ModelObject, Storable {
rotationDifferenceIsImportant : Bool = false ,
shouldHandleUpperRoundSlice : Bool = true ,
shouldEndRoundBeforeStartingNext : Bool = true ,
groupStageChunkCount : Int ? = nil , overrideCourtsUnavailability : Bool = false , shouldTryToFillUpCourtsAvailable : Bool = false ) {
groupStageChunkCount : Int ? = nil , overrideCourtsUnavailability : Bool = false , shouldTryToFillUpCourtsAvailable : Bool = false , courtsAvailable : Set < Int > = Set < Int > ( ) ) {
self . tournament = tournament
self . timeDifferenceLimit = timeDifferenceLimit
self . loserBracketRotationDifference = loserBracketRotationDifference
@ -56,6 +57,7 @@ final class MatchScheduler : ModelObject, Storable {
self . groupStageChunkCount = groupStageChunkCount
self . overrideCourtsUnavailability = overrideCourtsUnavailability
self . shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable
self . courtsAvailable = courtsAvailable
}
enum CodingKeys : String , CodingKey {
@ -73,6 +75,7 @@ final class MatchScheduler : ModelObject, Storable {
case _groupStageChunkCount = " groupStageChunkCount "
case _overrideCourtsUnavailability = " overrideCourtsUnavailability "
case _shouldTryToFillUpCourtsAvailable = " shouldTryToFillUpCourtsAvailable "
case _courtsAvailable = " courtsAvailable "
}
var courtsUnavailability : [ DateInterval ] ? {
@ -99,7 +102,6 @@ final class MatchScheduler : ModelObject, Storable {
if let specificGroupStage {
groupStages = [ specificGroupStage ]
}
let numberOfCourtsAvailablePerRotation : Int = tournament . courtCount
let matches = groupStages . flatMap { $0 . _matches ( ) }
matches . forEach ( {
@ -127,7 +129,7 @@ final class MatchScheduler : ModelObject, Storable {
lastDate = time
}
let groups = groupStages . filter ( { $0 . startDate = = time } )
let dispatch = groupStageDispatcher ( numberOfCourtsAvailablePerRotation : numberOfCourtsAvailablePerRotation , groupStages : groups , startingDate : lastDate )
let dispatch = groupStageDispatcher ( groupStages : groups , startingDate : lastDate )
dispatch . timedMatches . forEach { matchSchedule in
if let match = matches . first ( where : { $0 . id = = matchSchedule . matchID } ) {
@ -151,7 +153,7 @@ final class MatchScheduler : ModelObject, Storable {
Logger . error ( error )
}
let dispatch = groupStageDispatcher ( numberOfCourtsAvailablePerRotation : numberOfCourtsAvailablePerRotation , groupStages : groups , startingDate : lastDate )
let dispatch = groupStageDispatcher ( groupStages : groups , startingDate : lastDate )
dispatch . timedMatches . forEach { matchSchedule in
if let match = matches . first ( where : { $0 . id = = matchSchedule . matchID } ) {
@ -174,7 +176,7 @@ final class MatchScheduler : ModelObject, Storable {
return lastDate
}
func groupStageDispatcher ( numberOfCourtsAvailablePerRotation : Int , groupStages : [ GroupStage ] , startingDate : Date ) -> GroupStageMatchDispatcher {
func groupStageDispatcher ( groupStages : [ GroupStage ] , startingDate : Date ) -> GroupStageMatchDispatcher {
let _groupStages = groupStages
@ -214,7 +216,7 @@ final class MatchScheduler : ModelObject, Storable {
print ( " Match \( match . roundAndMatchTitle ( ) ) has teams already scheduled in rotation \( rotationIndex ) " )
}
return teamsAvailable
} ) . prefix ( numberOfCourtsAvailablePerRotation ) )
} ) . prefix ( courtsAvailable . count ) )
if rotationIndex > 0 {
rotationMatches = rotationMatches . sorted ( by : {
@ -226,7 +228,7 @@ final class MatchScheduler : ModelObject, Storable {
} )
}
( 0. . < numberOfC ourtsAvailablePerRotation ) . forEach { courtIndex in
c ourtsAvailable. forEach { courtIndex in
print ( " Checking availability for court \( courtIndex ) in rotation \( rotationIndex ) " )
if let first = rotationMatches . first ( where : { match in
let estimatedDuration = match . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
@ -427,7 +429,7 @@ final class MatchScheduler : ModelObject, Storable {
)
}
func roundDispatcher ( numberOfCourtsAvailablePerRotation : Int , flattenedMatches : [ Match ] , dispatcherStartDate : Date , initialCourts : [ Int ] ? ) -> MatchDispatcher {
func roundDispatcher ( flattenedMatches : [ Match ] , dispatcherStartDate : Date , initialCourts : [ Int ] ? ) -> MatchDispatcher {
var slots = [ TimeMatch ] ( )
var _startDate : Date ?
var rotationIndex = 0
@ -436,7 +438,7 @@ final class MatchScheduler : ModelObject, Storable {
var issueFound : Bool = false
// L o g s t a r t o f t h e f u n c t i o n
print ( " Starting roundDispatcher with \( availableMatchs . count ) matches and \( numberOfCourtsAvailablePerRotation ) courts available " )
print ( " Starting roundDispatcher with \( availableMatchs . count ) matches and \( courtsAvailable ) courts available " )
flattenedMatches . filter { $0 . startDate != nil } . sorted ( by : \ . startDate ! ) . forEach { match in
if _startDate = = nil {
@ -455,8 +457,7 @@ final class MatchScheduler : ModelObject, Storable {
}
var freeCourtPerRotation = [ Int : [ Int ] ] ( )
let availableCourt = numberOfCourtsAvailablePerRotation
var courts = initialCourts ? ? ( 0. . < availableCourt ) . map { $0 }
var courts = initialCourts ? ? Array ( courtsAvailable )
var shouldStartAtDispatcherDate = rotationIndex > 0
while ! availableMatchs . isEmpty && ! issueFound && rotationIndex < 100 {
@ -468,7 +469,7 @@ final class MatchScheduler : ModelObject, Storable {
rotationStartDate = dispatcherStartDate
shouldStartAtDispatcherDate = false
} else {
courts = rotationIndex = = 0 ? courts : ( 0. . < availableCourt ) . map { $0 }
courts = rotationIndex = = 0 ? courts : Array ( courtsAvailable )
}
courts . sort ( )
@ -520,16 +521,22 @@ final class MatchScheduler : ModelObject, Storable {
let duration = firstMatch . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : duration , courtsUnavailability : courtsUnavailability )
if courtsUnavailable . count = = numberOfCourtsAvailablePerRotation {
if Array ( Set ( courtsAvailable ) . subtracting ( Set ( courtsUnavailable ) ) ) . isEmpty {
print ( " Issue: All courts unavailable in this rotation " )
issueFound = true
if let courtsUnavailability {
let computedStartDateAndCourts = getFirstFreeCourt ( startDate : rotationStartDate , duration : duration , courts : courts , courtsUnavailability : courtsUnavailability )
rotationStartDate = computedStartDateAndCourts . earliestFreeDate
courts = computedStartDateAndCourts . availableCourts
} else {
issueFound = true
}
} else {
courts = Array ( Set ( courts ) . subtracting ( Set ( courtsUnavailable ) ) )
courts = Array ( Set ( courtsAvailable ) . subtracting ( Set ( courtsUnavailable ) ) )
}
}
// D i s p a t c h c o u r t s a n d s c h e d u l e m a t c h e s
dispatchCourts ( availableCourts : numberOfCourtsAvailablePerRotation , courts : courts , availableMatchs : & availableMatchs , slots : & slots , rotationIndex : rotationIndex , rotationStartDate : rotationStartDate , freeCourtPerRotation : & freeCourtPerRotation , courtsUnavailability : courtsUnavailability )
dispatchCourts ( courts : courts , availableMatchs : & availableMatchs , slots : & slots , rotationIndex : rotationIndex , rotationStartDate : rotationStartDate , freeCourtPerRotation : & freeCourtPerRotation , courtsUnavailability : courtsUnavailability )
rotationIndex += 1
}
@ -551,7 +558,7 @@ final class MatchScheduler : ModelObject, Storable {
return MatchDispatcher ( timedMatches : slots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , issueFound : issueFound )
}
func dispatchCourts ( availableCourts : Int , courts : [ Int ] , availableMatchs : inout [ Match ] , slots : inout [ TimeMatch ] , rotationIndex : Int , rotationStartDate : Date , freeCourtPerRotation : inout [ Int : [ Int ] ] , courtsUnavailability : [ DateInterval ] ? ) {
func dispatchCourts ( courts : [ Int ] , availableMatchs : inout [ Match ] , slots : inout [ TimeMatch ] , rotationIndex : Int , rotationStartDate : Date , freeCourtPerRotation : inout [ Int : [ Int ] ] , courtsUnavailability : [ DateInterval ] ? ) {
var matchPerRound = [ String : Int ] ( )
var minimumTargetedEndDate = rotationStartDate
@ -567,7 +574,7 @@ final class MatchScheduler : ModelObject, Storable {
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : duration , courtsUnavailability : courtsUnavailability )
if courtsUnavailable . contains ( courtPosition ) {
if courtsUnavailable . contains ( courtIndex ) {
print ( " Returning false: Court \( courtIndex ) unavailable due to schedule conflicts during \( rotationStartDate ) . " )
return false
}
@ -591,13 +598,16 @@ final class MatchScheduler : ModelObject, Storable {
let indexInRound = match . indexInRound ( )
if roundObject . parent = = nil && roundObject . index > 0 && indexInRound = = 0 , let nextMatch = match . next ( ) {
if courtPosition < courts . count - 1 && canBePlayed && roundMatchCanBePlayed ( nextMatch , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate , minimumTargetedEndDate : & minimumTargetedEndDate ) {
print ( " Returning true: Both current \( match . index ) and next match \( nextMatch . index ) can be played in rotation \( rotationIndex ) . " )
return true
} else {
print ( " Returning false: Either current match or next match cannot be played in rotation \( rotationIndex ) . " )
return false
if shouldTryToFillUpCourtsAvailable = = false {
if roundObject . parent = = nil && roundObject . index > 1 && indexInRound = = 0 , let nextMatch = match . next ( ) {
if courtPosition < courts . count - 1 && canBePlayed && roundMatchCanBePlayed ( nextMatch , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate , minimumTargetedEndDate : & minimumTargetedEndDate ) {
print ( " Returning true: Both current \( match . index ) and next match \( nextMatch . index ) can be played in rotation \( rotationIndex ) . " )
return true
} else {
print ( " Returning false: Either current match or next match cannot be played in rotation \( rotationIndex ) . " )
return false
}
}
}
@ -626,7 +636,7 @@ final class MatchScheduler : ModelObject, Storable {
}
if freeCourtPerRotation [ rotationIndex ] ? . count = = availableCourts {
if freeCourtPerRotation [ rotationIndex ] ? . count = = courtsAvailable . count {
print ( " All courts in rotation \( rotationIndex ) are free " )
}
}
@ -713,7 +723,7 @@ final class MatchScheduler : ModelObject, Storable {
print ( " initial available courts at beginning: \( courts ? ? [ ] ) " )
let roundDispatch = self . roundDispatcher ( numberOfCourtsAvailablePerRotation : tournament . courtCount , flattenedMatches : flattenedMatches , dispatcherStartDate : startDate , initialCourts : courts )
let roundDispatch = self . roundDispatcher ( flattenedMatches : flattenedMatches , dispatcherStartDate : startDate , initialCourts : courts )
roundDispatch . timedMatches . forEach { matchSchedule in
if let match = flattenedMatches . first ( where : { $0 . id = = matchSchedule . matchID } ) {
@ -750,7 +760,50 @@ final class MatchScheduler : ModelObject, Storable {
} )
}
func getFirstFreeCourt ( startDate : Date , duration : Int , courts : [ Int ] , courtsUnavailability : [ DateInterval ] ) -> ( earliestFreeDate : Date , availableCourts : [ Int ] ) {
var earliestEndDate : Date ?
var availableCourtsAtEarliest : [ Int ] = [ ]
// I t e r a t e t h r o u g h e a c h c o u r t a n d f i n d t h e e a r l i e s t t i m e i t b e c o m e s f r e e
for courtIndex in courts {
let unavailabilityForCourt = courtsUnavailability . filter { $0 . courtIndex = = courtIndex }
var isAvailable = true
for interval in unavailabilityForCourt {
if interval . startDate <= startDate && interval . endDate > startDate {
isAvailable = false
if let currentEarliest = earliestEndDate {
earliestEndDate = min ( currentEarliest , interval . endDate )
} else {
earliestEndDate = interval . endDate
}
}
}
// I f t h e c o u r t i s a v a i l a b l e a t t h e s t a r t d a t e , a d d i t t o t h e l i s t o f a v a i l a b l e c o u r t s
if isAvailable {
availableCourtsAtEarliest . append ( courtIndex )
}
}
// I f t h e r e a r e n o u n a v a i l a b l e c o u r t s , r e t u r n t h e o r i g i n a l s t a r t d a t e a n d a l l c o u r t s
if let earliestEndDate = earliestEndDate {
// F i n d w h i c h c o u r t s w i l l b e a v a i l a b l e a t t h e e a r l i e s t f r e e d a t e
let courtsAvailableAtEarliest = courts . filter { courtIndex in
let unavailabilityForCourt = courtsUnavailability . filter { $0 . courtIndex = = courtIndex }
return unavailabilityForCourt . allSatisfy { $0 . endDate <= earliestEndDate }
}
return ( earliestFreeDate : earliestEndDate , availableCourts : courtsAvailableAtEarliest )
} else {
// I f n o c o u r t s w e r e u n a v a i l a b l e , a l l c o u r t s a r e a v a i l a b l e a t t h e s t a r t d a t e
return ( earliestFreeDate : startDate . addingTimeInterval ( Double ( duration ) * 60 ) , availableCourts : courts )
}
}
func updateSchedule ( tournament : Tournament ) -> Bool {
if tournament . courtCount < courtsAvailable . count {
courtsAvailable = Set ( tournament . courtsAvailable ( ) )
}
var lastDate = tournament . startDate
if tournament . groupStageCount > 0 {
lastDate = updateGroupStageSchedule ( tournament : tournament )