@ -23,7 +23,7 @@ struct TimeMatch {
var durationLeft : Int // i n m i n u t e s
var minimumBreakTime : Int // i n m i n u t e s
var courtLocked : Bool = false
var freeCourt : Bool = false
func estimatedEndDate ( includeBreakTime : Bool ) -> Date {
let minutesToAdd = Double ( durationLeft + ( includeBreakTime ? minimumBreakTime : 0 ) )
return startDate . addingTimeInterval ( minutesToAdd * 60.0 )
@ -136,8 +136,8 @@ class MatchScheduler {
return GroupStageMatchDispatcher ( timedMatches : organizedSlots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , groupLastRotation : groupLastRotation )
}
func roundMatchCanBePlayed ( _ match : Match , roundObject : Round , slots : [ TimeMatch ] , rotationIndex : Int , targetedStartDate : Date ) -> Bool {
print ( roundObject . roundTitle ( ) , match . matchTitle ( ) )
func roundMatchCanBePlayed ( _ match : Match , roundObject : Round , slots : [ TimeMatch ] , rotationIndex : Int , targetedStartDate : Date , minimumTargetedEndDate : inout Date ) -> Bool {
// p r i n t ( r o u n d O b j e c t . r o u n d T i t l e ( ) , m a t c h . m a t c h T i t l e ( ) )
let previousMatches = roundObject . precedentMatches ( ofMatch : match )
if previousMatches . isEmpty { return true }
@ -159,18 +159,21 @@ class MatchScheduler {
return false
}
// i f r o u n d O b j e c t . i s L o s e r B r a c k e t ( ) {
// l e t p r e v i o u s M a t c h I s I n P r e v i o u s R o t a t i o n = p r e v i o u s M a t c h S l o t s . a l l S a t i s f y ( { $ 0 . r o t a t i o n I n d e x < r o t a t i o n I n d e x } )
// r e t u r n p r e v i o u s M a t c h I s I n P r e v i o u s R o t a t i o n
// }
let previousMatchIsInPreviousRotation = previousMatchSlots . allSatisfy ( { $0 . rotationIndex < rotationIndex } )
guard let minimumPossibleEndDate = previousMatchSlots . map ( { $0 . estimatedEndDate ( includeBreakTime : true ) } ) . max ( ) else {
guard let minimumPossibleEndDate = previousMatchSlots . map ( { $0 . estimatedEndDate ( includeBreakTime : roundObject . isLoserBracket ( ) = = false ) } ) . max ( ) else {
return previousMatchIsInPreviousRotation
}
return targetedStartDate >= minimumPossibleEndDate
if targetedStartDate >= minimumPossibleEndDate {
return true
} else {
if targetedStartDate = = minimumTargetedEndDate {
minimumTargetedEndDate = minimumPossibleEndDate
} else {
minimumTargetedEndDate = min ( minimumPossibleEndDate , minimumTargetedEndDate )
}
return false
}
}
func getAvailableCourt ( inSlots slots : [ TimeMatch ] , nextStartDate : Date ) -> [ TimeMatch ] {
@ -212,102 +215,141 @@ class MatchScheduler {
let previousRotationSlots = slots . filter ( { $0 . rotationIndex = = rotationIndex - 1 } )
var rotationStartDate : Date = getNextStartDate ( fromPreviousRotationSlots : previousRotationSlots , includeBreakTime : false ) ? ? dispatcherStartDate
let duplicatedSlots = getAvailableCourt ( inSlots : previousRotationSlots , nextStartDate : rotationStartDate )
print ( " duplicatedSlots " , duplicatedSlots )
slots . append ( contentsOf : duplicatedSlots )
courts . removeAll ( where : { index i n
duplicatedSlots . anySatisfy { $0 . courtIndex = = index }
} )
// l e t d u p l i c a t e d S l o t s = g e t A v a i l a b l e C o u r t ( i n S l o t s : p r e v i o u s R o t a t i o n S l o t s , n e x t S t a r t D a t e : r o t a t i o n S t a r t D a t e )
// p r i n t ( " d u p l i c a t e d S l o t s " , d u p l i c a t e d S l o t s )
// s l o t s . a p p e n d ( c o n t e n t s O f : d u p l i c a t e d S l o t s )
// c o u r t s . r e m o v e A l l ( w h e r e : { i n d e x i n
// d u p l i c a t e d S l o t s . a n y S a t i s f y { $ 0 . c o u r t I n d e x = = i n d e x }
// } )
courts . sort ( )
print ( " courts available at rotation \( rotationIndex ) " , courts )
print ( " rotationStartDate " , rotationStartDate )
let freeCourtPreviousRotation = rotationIndex > 0 ? freeCourtPerRotation [ rotationIndex - 1 ] ! . count : 0
if previousRotationSlots . isEmpty && rotationIndex > 0 {
let previousPreviousRotationSlots = slots . filter ( { $0 . rotationIndex = = rotationIndex - 2 } )
rotationStartDate = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : false ) ? ? dispatcherStartDate
} else if freeCourtPreviousRotation > 0 {
if rotationIndex > 0 , let freeCourtPreviousRotation = freeCourtPerRotation [ rotationIndex - 1 ] , freeCourtPreviousRotation . count > 0 {
print ( " scenario where we are waiting for a breaktime to be over without any match to play in between or a free court was available and we need to recheck breaktime left on it " )
let previousPreviousRotationSlots = slots . filter ( { $0 . rotationIndex = = rotationIndex - 2 } )
if let previousEndDate = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : true ) {
rotationStartDate = previousEndDate
courts = freeCourtPerRotation [ rotationIndex - 1 ] !
}
}
courts . forEach { courtIndex in
// p r i n t ( m t . m a p { ( $ 0 . b r a c k e t ! . i n d e x . i n t V a l u e , c o u n t s [ $ 0 . b r a c k e t ! . i n d e x . i n t V a l u e ] ) } )
let previousPreviousRotationSlots = slots . filter ( { $0 . rotationIndex = = rotationIndex - 2 && freeCourtPreviousRotation . contains ( $0 . courtIndex ) } )
let previousEndDate = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : true )
let previousEndDateNoBreak = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : false )
if let first = availableMatchs . first ( where : { match in
let roundObject = match . roundObject !
let canBePlayed = roundMatchCanBePlayed ( match , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate )
if roundObject . loser = = nil && roundObject . index > 0 , match . indexInRound ( ) = = 0 , numberOfCourtsAvailablePerRotation > 1 , let nextMatch = match . next ( ) {
if canBePlayed && roundMatchCanBePlayed ( nextMatch , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate ) {
return true
} else {
return false
}
}
if ( matchPerRound [ roundObject . index ] ? ? 0 ) % 2 = = 0 && roundObject . index != 0 && roundObject . loser = = nil && courtIndex = = numberOfCourtsAvailablePerRotation - 1 {
return false
let noBreakAlreadyTested = previousRotationSlots . anySatisfy ( { $0 . startDate = = previousEndDateNoBreak } )
if let previousEndDate , let previousEndDateNoBreak {
let differenceWithBreak = rotationStartDate . timeIntervalSince ( previousEndDate )
let differenceWithoutBreak = rotationStartDate . timeIntervalSince ( previousEndDateNoBreak )
print ( " difference w break " , differenceWithBreak )
print ( " difference w/o break " , differenceWithoutBreak )
var difference = differenceWithBreak
if differenceWithBreak <= 0 {
difference = differenceWithoutBreak
} else if differenceWithBreak > 0 && differenceWithoutBreak > 0 {
difference = noBreakAlreadyTested ? differenceWithBreak : max ( differenceWithBreak , differenceWithoutBreak )
}
return canBePlayed
} ) {
// p r i n t ( f i r s t . r o u n d O b j e c t ! . r o u n d T i t l e ( ) , f i r s t . m a t c h T i t l e ( ) )
if first . roundObject ! . loser = = nil {
if let roundIndex = matchPerRound [ first . roundObject ! . index ] {
matchPerRound [ first . roundObject ! . index ] = roundIndex + 1
} else {
matchPerRound [ first . roundObject ! . index ] = 1
}
if difference > 0 {
courts . removeAll ( where : { index in freeCourtPreviousRotation . contains ( index )
} )
freeCourtPerRotation [ rotationIndex ] = courts
courts = freeCourtPreviousRotation
rotationStartDate = rotationStartDate . addingTimeInterval ( - difference )
}
let timeMatch = TimeMatch ( matchID : first . id , rotationIndex : rotationIndex , courtIndex : courtIndex , groupIndex : first . roundObject ! . index , startDate : rotationStartDate , durationLeft : first . matchFormat . estimatedDuration , minimumBreakTime : first . matchFormat . breakTime . breakTime )
slots . append ( timeMatch )
availableMatchs . removeAll ( where : { $0 . id = = first . id } )
if let index = first . roundObject ? . index {
groupLastRotation [ index ] = rotationIndex
}
timeToAdd = 0.0
} else {
freeCourtPerRotation [ rotationIndex ] ! . append ( courtIndex )
}
}
// i f p r e v i o u s R o t a t i o n S l o t s . i s E m p t y & & r o t a t i o n I n d e x > 0 {
// l e t p r e v i o u s P r e v i o u s R o t a t i o n S l o t s = s l o t s . f i l t e r ( { $ 0 . r o t a t i o n I n d e x = = r o t a t i o n I n d e x - 2 } )
// r o t a t i o n S t a r t D a t e = g e t N e x t S t a r t D a t e ( f r o m P r e v i o u s R o t a t i o n S l o t s : p r e v i o u s P r e v i o u s R o t a t i o n S l o t s , i n c l u d e B r e a k T i m e : f a l s e ) ? ? d i s p a t c h e r S t a r t D a t e
// } e l s e i f f r e e C o u r t P r e v i o u s R o t a t i o n > 0 {
// p r i n t ( " s c e n a r i o w h e r e w e a r e w a i t i n g f o r a b r e a k t i m e t o b e o v e r w i t h o u t a n y m a t c h t o p l a y i n b e t w e e n o r a f r e e c o u r t w a s a v a i l a b l e a n d w e n e e d t o r e c h e c k b r e a k t i m e l e f t o n i t " )
// l e t p r e v i o u s P r e v i o u s R o t a t i o n S l o t s = s l o t s . f i l t e r ( { $ 0 . r o t a t i o n I n d e x = = r o t a t i o n I n d e x - 2 } )
// i f l e t p r e v i o u s E n d D a t e = g e t N e x t S t a r t D a t e ( f r o m P r e v i o u s R o t a t i o n S l o t s : p r e v i o u s P r e v i o u s R o t a t i o n S l o t s , i n c l u d e B r e a k T i m e : t r u e ) {
// r o t a t i o n S t a r t D a t e = p r e v i o u s E n d D a t e
// c o u r t s = f r e e C o u r t P e r R o t a t i o n [ r o t a t i o n I n d e x - 1 ] !
// }
// }
dispatchCourts ( availableCourts : numberOfCourtsAvailablePerRotation , courts : courts , availableMatchs : & availableMatchs , slots : & slots , rotationIndex : rotationIndex , rotationStartDate : rotationStartDate , freeCourtPerRotation : & freeCourtPerRotation )
rotationIndex += 1
}
var organizedSlots = [ TimeMatch ] ( )
for i in 0. . < rotationIndex {
let courtsSorted = slots . filter ( { $0 . rotationIndex = = i && $0 . courtLocked = = false } ) . map { $0 . courtIndex } . sorted ( )
let courts = randomizeCourts ? courtsSorted . shuffled ( ) : courtsSorted
var matches = slots . filter ( { $0 . rotationIndex = = i } ) . sorted ( using : . keyPath ( \ . groupIndex ) , . keyPath ( \ . courtIndex ) )
// v a r o r g a n i z e d S l o t s = [ T i m e M a t c h ] ( )
// f o r i i n 0 . . < r o t a t i o n I n d e x {
// l e t c o u r t s S o r t e d = s l o t s . f i l t e r ( { $ 0 . r o t a t i o n I n d e x = = i & & $ 0 . c o u r t L o c k e d = = f a l s e } ) . m a p { $ 0 . c o u r t I n d e x } . s o r t e d ( )
// l e t c o u r t s = r a n d o m i z e C o u r t s ? c o u r t s S o r t e d . s h u f f l e d ( ) : c o u r t s S o r t e d
// v a r m a t c h e s = s l o t s . f i l t e r ( { $ 0 . r o t a t i o n I n d e x = = i } ) . s o r t e d ( u s i n g : . k e y P a t h ( \ . g r o u p I n d e x ) , . k e y P a t h ( \ . c o u r t I n d e x ) )
//
// f o r j i n 0 . . < m a t c h e s . c o u n t {
// m a t c h e s [ j ] . c o u r t I n d e x = c o u r t s [ j ]
// o r g a n i z e d S l o t s . a p p e n d ( m a t c h e s [ j ] )
// }
// }
//
//
return MatchDispatcher ( timedMatches : slots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , groupLastRotation : groupLastRotation )
}
func dispatchCourts ( availableCourts : Int , courts : [ Int ] , availableMatchs : inout [ Match ] , slots : inout [ TimeMatch ] , rotationIndex : Int , rotationStartDate : Date , freeCourtPerRotation : inout [ Int : [ Int ] ] ) {
var matchPerRound = [ Int : Int ] ( )
var minimumTargetedEndDate : Date = rotationStartDate
courts . forEach { courtIndex in
// p r i n t ( m t . m a p { ( $ 0 . b r a c k e t ! . i n d e x . i n t V a l u e , c o u n t s [ $ 0 . b r a c k e t ! . i n d e x . i n t V a l u e ] ) } )
for j in 0. . < matches . count {
matches [ j ] . courtIndex = courts [ j ]
organizedSlots . append ( matches [ j ] )
if let first = availableMatchs . first ( where : { match in
let roundObject = match . roundObject !
let canBePlayed = roundMatchCanBePlayed ( match , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate , minimumTargetedEndDate : & minimumTargetedEndDate )
if roundObject . loser = = nil && roundObject . index > 0 , match . indexInRound ( ) = = 0 , courts . count > 1 , let nextMatch = match . next ( ) {
if canBePlayed && roundMatchCanBePlayed ( nextMatch , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate , minimumTargetedEndDate : & minimumTargetedEndDate ) {
return true
} else {
return false
}
}
if ( matchPerRound [ roundObject . index ] ? ? 0 ) % 2 = = 0 && roundObject . index != 0 && roundObject . loser = = nil && courtIndex = = courts . count - 1 {
return false
}
return canBePlayed
} ) {
print ( first . roundObject ! . roundTitle ( ) , first . matchTitle ( ) , courtIndex , rotationStartDate )
if first . roundObject ! . loser = = nil {
if let roundIndex = matchPerRound [ first . roundObject ! . index ] {
matchPerRound [ first . roundObject ! . index ] = roundIndex + 1
} else {
matchPerRound [ first . roundObject ! . index ] = 1
}
}
let timeMatch = TimeMatch ( matchID : first . id , rotationIndex : rotationIndex , courtIndex : courtIndex , groupIndex : first . roundObject ! . index , startDate : rotationStartDate , durationLeft : first . matchFormat . estimatedDuration , minimumBreakTime : first . matchFormat . breakTime . breakTime )
slots . append ( timeMatch )
availableMatchs . removeAll ( where : { $0 . id = = first . id } )
} else {
freeCourtPerRotation [ rotationIndex ] ! . append ( courtIndex )
}
}
return MatchDispatcher ( timedMatches : organizedSlots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , groupLastRotation : groupLastRotation )
if freeCourtPerRotation [ rotationIndex ] ! . count = = availableCourts {
freeCourtPerRotation [ rotationIndex ] = [ ]
let freeCourts = ( 0. . < availableCourts ) . map { $0 }
dispatchCourts ( availableCourts : availableCourts , courts : freeCourts , availableMatchs : & availableMatchs , slots : & slots , rotationIndex : rotationIndex , rotationStartDate : minimumTargetedEndDate , freeCourtPerRotation : & freeCourtPerRotation )
}
}
func updateSchedule ( tournament : Tournament , fromRoundId roundId : String ? , fromMatchId matchId : String ? , randomizeCourts : Bool , startDate : Date ) {
let upperRounds = tournament . rounds ( )
var roundIndex = 0
if let roundId {
roundIndex = upperRounds . firstIndex ( where : { $0 . id = = roundId } ) ? ? 0
}
let rounds = upperRounds . flatMap {
[ $0 ] + $0 . loserRoundsAndChildren ( )
let rounds = upperRounds . map {
$0
} + upperRounds . flatMap {
$0 . loserRoundsAndChildren ( )
}
if let roundId {
roundIndex = rounds . firstIndex ( where : { $0 . id = = roundId } ) ? ? 0
}
var flattenedMatches = rounds [ roundIndex . . . ] . flatMap { round in
round . _matches ( ) . filter ( { $0 . disabled = = false } ) . sorted ( by : \ . index )
}