@ -179,30 +179,39 @@ final class MatchScheduler : ModelObject, Storable {
// G e t t h e m a x i m u m c o u n t o f m a t c h e s i n a n y g r o u p
// G e t t h e m a x i m u m c o u n t o f m a t c h e s i n a n y g r o u p
let maxMatchesCount = _groupStages . map { $0 . _matches ( ) . count } . max ( ) ? ? 0
let maxMatchesCount = _groupStages . map { $0 . _matches ( ) . count } . max ( ) ? ? 0
// U s e z i p a n d f l a t M a p t o f l a t t e n m a t c h e s i n t h e d e s i r e d o r d e r
// F l a t t e n m a t c h e s i n a r o u n d - r o b i n o r d e r b y c y c l i n g t h r o u g h e a c h g r o u p
let flattenedMatches = ( 0. . < maxMatchesCount ) . flatMap { index in
let flattenedMatches = ( 0. . < maxMatchesCount ) . flatMap { index in
_groupStages . compactMap { group in
_groupStages . compactMap { group in
// U s e o p t i o n a l s u b s c r i p t t o s a f e l y a c c e s s m a t c h e s
// S a f e l y a c c e s s m a t c h e s , r e t u r n n i l i f i n d e x i s o u t o f b o u n d s
let playedMatches = group . playedMatches ( )
let playedMatches = group . playedMatches ( )
return playedMatches . indices . contains ( index ) ? playedMatches [ index ] : nil
return playedMatches . indices . contains ( index ) ? playedMatches [ index ] : nil
}
}
}
}
var slots = [ GroupStageTimeMatch ] ( )
var slots = [ GroupStageTimeMatch ] ( )
var availableMatchs = flattenedMatches
var availableMatche s = flattenedMatches
var rotationIndex = 0
var rotationIndex = 0
var teamsPerRotation = [ Int : [ String ] ] ( )
var teamsPerRotation = [ Int : [ String ] ] ( ) // T r a c k s t e a m s a s s i g n e d t o e a c h r o t a t i o n
var freeCourtPerRotation = [ Int : [ Int ] ] ( )
var freeCourtPerRotation = [ Int : [ Int ] ] ( ) // T r a c k s f r e e c o u r t s p e r r o t a t i o n
var groupLastRotation = [ Int : Int ] ( )
var groupLastRotation = [ Int : Int ] ( ) // T r a c k s t h e l a s t r o t a t i o n e a c h g r o u p w a s i n v o l v e d i n
let courtsUnavailability = courtsUnavailability
let courtsUnavailability = courtsUnavailability
while slots . count < flattenedMatches . count {
while slots . count < flattenedMatches . count {
print ( " Starting rotation \( rotationIndex ) with \( availableMatches . count ) matches left " )
teamsPerRotation [ rotationIndex ] = [ ]
teamsPerRotation [ rotationIndex ] = [ ]
freeCourtPerRotation [ rotationIndex ] = [ ]
freeCourtPerRotation [ rotationIndex ] = [ ]
let previousRotationBracketIndexes = slots . filter { $0 . rotationIndex = = rotationIndex - 1 } . map { ( $0 . groupIndex , 1 ) }
let previousRotationBracketIndexes = slots . filter { $0 . rotationIndex = = rotationIndex - 1 }
. map { ( $0 . groupIndex , 1 ) }
let counts = Dictionary ( previousRotationBracketIndexes , uniquingKeysWith : + )
let counts = Dictionary ( previousRotationBracketIndexes , uniquingKeysWith : + )
var rotationMatches = Array ( availableMatchs . filter ( { match in
var rotationMatches = Array ( availableMatches . filter ( { match in
teamsPerRotation [ rotationIndex ] ! . allSatisfy ( { match . containsTeamId ( $0 ) = = false } ) = = true
// C h e c k i f a l l t e a m s f r o m t h e m a t c h a r e n o t a l r e a d y s c h e d u l e d i n t h e c u r r e n t r o t a t i o n
let teamsAvailable = teamsPerRotation [ rotationIndex ] ! . allSatisfy ( { ! match . containsTeamId ( $0 ) } )
if ! teamsAvailable {
print ( " Match \( match . roundAndMatchTitle ( ) ) has teams already scheduled in rotation \( rotationIndex ) " )
}
return teamsAvailable
} ) . prefix ( numberOfCourtsAvailablePerRotation ) )
} ) . prefix ( numberOfCourtsAvailablePerRotation ) )
if rotationIndex > 0 {
if rotationIndex > 0 {
@ -216,28 +225,42 @@ final class MatchScheduler : ModelObject, Storable {
}
}
( 0. . < numberOfCourtsAvailablePerRotation ) . forEach { courtIndex in
( 0. . < numberOfCourtsAvailablePerRotation ) . 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 ] ) } )
print ( " Checking availability for court \( courtIndex ) in rotation \( rotationIndex ) " )
if let first = rotationMatches . first ( where : { match in
if let first = rotationMatches . first ( where : { match in
let estimatedDuration = match . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
let estimatedDuration = match . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
let timeIntervalToAdd = ( Double ( rotationIndex ) ) * Double ( estimatedDuration ) * 60
let timeIntervalToAdd = Double ( rotationIndex ) * Double ( estimatedDuration ) * 60
let rotationStartDate : Date = startingDate . addingTimeInterval ( timeIntervalToAdd )
let rotationStartDate : Date = startingDate . addingTimeInterval ( timeIntervalToAdd )
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : match . matchFormat . getEstimatedDuration ( additionalEstimationDuration ) , courtsUnavailability : courtsUnavailability )
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : match . matchFormat . getEstimatedDuration ( additionalEstimationDuration ) , courtsUnavailability : courtsUnavailability )
if courtIndex >= numberOfCourtsAvailablePerRotation - courtsUnavailable . count {
if courtsUnavailable . contains ( courtIndex ) {
print ( " Court \( courtIndex ) is unavailable at \( rotationStartDate ) " )
return false
return false
} else {
return teamsPerRotation [ rotationIndex ] ! . allSatisfy ( { match . containsTeamId ( $0 ) = = false } ) = = true
}
}
let teamsAvailable = teamsPerRotation [ rotationIndex ] ! . allSatisfy ( { ! match . containsTeamId ( $0 ) } )
if ! teamsAvailable {
print ( " Teams from match \( match . roundAndMatchTitle ( ) ) are already scheduled in this rotation " )
return false
}
print ( " Match \( match . roundAndMatchTitle ( ) ) is available for court \( courtIndex ) at \( rotationStartDate ) " )
return true
} ) {
} ) {
let timeMatch = GroupStageTimeMatch ( matchID : first . id , rotationIndex : rotationIndex , courtIndex : courtIndex , groupIndex : first . groupStageObject ! . index )
let timeMatch = GroupStageTimeMatch ( matchID : first . id , rotationIndex : rotationIndex , courtIndex : courtIndex , groupIndex : first . groupStageObject ! . index )
print ( " Scheduled match: \( first . roundAndMatchTitle ( ) ) on court \( courtIndex ) at rotation \( rotationIndex ) " )
slots . append ( timeMatch )
slots . append ( timeMatch )
teamsPerRotation [ rotationIndex ] ! . append ( contentsOf : first . teamIds ( ) )
teamsPerRotation [ rotationIndex ] ! . append ( contentsOf : first . teamIds ( ) )
rotationMatches . removeAll ( where : { $0 . id = = first . id } )
rotationMatches . removeAll ( where : { $0 . id = = first . id } )
availableMatchs . removeAll ( where : { $0 . id = = first . id } )
availableMatches . removeAll ( where : { $0 . id = = first . id } )
if let index = first . groupStageObject ? . index {
if let index = first . groupStageObject ? . index {
groupLastRotation [ index ] = rotationIndex
groupLastRotation [ index ] = rotationIndex
}
}
} else {
} else {
print ( " No available matches for court \( courtIndex ) in rotation \( rotationIndex ) , adding to free court list " )
freeCourtPerRotation [ rotationIndex ] ! . append ( courtIndex )
freeCourtPerRotation [ rotationIndex ] ! . append ( courtIndex )
}
}
}
}
@ -245,6 +268,9 @@ final class MatchScheduler : ModelObject, Storable {
rotationIndex += 1
rotationIndex += 1
}
}
print ( " All matches scheduled. Total rotations: \( rotationIndex ) " )
// O r g a n i z e s l o t s a n d e n s u r e c o u r t s a r e r a n d o m i z e d o r s o r t e d
var organizedSlots = [ GroupStageTimeMatch ] ( )
var organizedSlots = [ GroupStageTimeMatch ] ( )
for i in 0. . < rotationIndex {
for i in 0. . < rotationIndex {
let courtsSorted : [ Int ] = slots . filter ( { $0 . rotationIndex = = i } ) . map { $0 . courtIndex } . sorted ( )
let courtsSorted : [ Int ] = slots . filter ( { $0 . rotationIndex = = i } ) . map { $0 . courtIndex } . sorted ( )
@ -257,10 +283,15 @@ final class MatchScheduler : ModelObject, Storable {
}
}
}
}
return GroupStageMatchDispatcher (
return GroupStageMatchDispatcher ( timedMatches : organizedSlots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , groupLastRotation : groupLastRotation )
timedMatches : organizedSlots ,
freeCourtPerRotation : freeCourtPerRotation ,
rotationCount : rotationIndex ,
groupLastRotation : groupLastRotation
)
}
}
func rotationDifference ( loserBracket : Bool ) -> Int {
func rotationDifference ( loserBracket : Bool ) -> Int {
if loserBracket {
if loserBracket {
return loserBracketRotationDifference
return loserBracketRotationDifference
@ -270,71 +301,97 @@ final class MatchScheduler : ModelObject, Storable {
}
}
func roundMatchCanBePlayed ( _ match : Match , roundObject : Round , slots : [ TimeMatch ] , rotationIndex : Int , targetedStartDate : Date , minimumTargetedEndDate : inout Date ) -> Bool {
func roundMatchCanBePlayed ( _ match : Match , roundObject : Round , slots : [ TimeMatch ] , rotationIndex : Int , targetedStartDate : Date , minimumTargetedEndDate : inout Date ) -> Bool {
print ( roundObject . roundTitle ( ) , match . matchTitle ( ) )
print ( " Evaluating match: \( match . roundAndMatchTitle ( ) ) in round: \( roundObject . roundTitle ( ) ) with index: \( match . index ) " )
if let roundStartDate = roundObject . startDate , targetedStartDate < roundStartDate {
if let roundStartDate = roundObject . startDate , targetedStartDate < roundStartDate {
print ( " can't star t \( targetedStartDate ) earlier than \( roundStartDate ) " )
print ( " Cannot start a t \( targetedStartDate ) , earlier than round start date \( roundStartDate ) " )
if targetedStartDate = = minimumTargetedEndDate {
if targetedStartDate = = minimumTargetedEndDate {
print ( " Updating minimumTargetedEndDate to roundStartDate: \( roundStartDate ) " )
minimumTargetedEndDate = roundStartDate
minimumTargetedEndDate = roundStartDate
} else {
} else {
print ( " Setting minimumTargetedEndDate to the earlier of \( roundStartDate ) and \( minimumTargetedEndDate ) " )
minimumTargetedEndDate = min ( roundStartDate , minimumTargetedEndDate )
minimumTargetedEndDate = min ( roundStartDate , minimumTargetedEndDate )
}
}
print ( " Returning false: Match cannot start earlier than the round start date. " )
return false
return false
}
}
let previousMatches = roundObject . precedentMatches ( ofMatch : match )
let previousMatches = roundObject . precedentMatches ( ofMatch : match )
if previousMatches . isEmpty { return true }
if previousMatches . isEmpty {
print ( " No ancestors matches for this match, returning true. (eg beginning of tournament 1st bracket " )
return true
}
let previousMatchSlots = slots . filter ( { slot in
let previousMatchSlots = slots . filter { previousMatches . map { $0 . id } . contains ( $0 . matchID ) }
previousMatches . map { $0 . id } . contains ( slot . matchID )
} )
if previousMatchSlots . isEmpty {
if previousMatchSlots . isEmpty {
if previousMatches . filter ( { $0 . disabled = = false } ) . allSatisfy ( { $0 . startDate != nil } ) {
if previousMatches . filter ( { ! $0 . disabled } ) . allSatisfy ( { $0 . startDate != nil } ) {
print ( " All previous matches have start dates, returning true. " )
return true
return true
}
}
print ( " Some previous matches are pending, returning false. " )
return false
return false
}
}
if previousMatches . filter ( { $0 . disabled = = false } ) . count > previousMatchSlots . count {
if previousMatches . filter ( { ! $0 . disabled } ) . count > previousMatchSlots . count {
if previousMatches . filter ( { $0 . disabled = = false } ) . anySatisfy ( { $0 . startDate != nil } ) {
if previousMatches . filter ( { ! $0 . disabled } ) . anySatisfy ( { $0 . startDate != nil } ) {
print ( " Some previous matches started, returning true. " )
return true
return true
}
}
print ( " Not enough previous matches have started, returning false. " )
return false
return false
}
}
var includeBreakTime = false
var includeBreakTime = false
if accountLoserBracketBreakTime && roundObject . isLoserBracket ( ) {
if accountLoserBracketBreakTime && roundObject . isLoserBracket ( ) {
includeBreakTime = true
includeBreakTime = true
print ( " Including break time for loser bracket. " )
}
}
if accountUpperBracketBreakTime && roundObject . isLoserBracket ( ) = = false {
if accountUpperBracketBreakTime && ! roundObject . isLoserBracket ( ) {
includeBreakTime = true
includeBreakTime = true
print ( " Including break time for upper bracket. " )
}
let previousMatchIsInPreviousRotation = previousMatchSlots . allSatisfy {
$0 . rotationIndex + rotationDifference ( loserBracket : roundObject . isLoserBracket ( ) ) < rotationIndex
}
}
let previousMatchIsInPreviousRotation = previousMatchSlots . allSatisfy ( { $0 . rotationIndex + rotationDifference ( loserBracket : roundObject . isLoserBracket ( ) ) < rotationIndex } )
if previousMatchIsInPreviousRotation {
print ( " All previous matches are from earlier rotations, returning true. " )
} else {
print ( " Some previous matches are from the current rotation. " )
}
guard let minimumPossibleEndDate = previousMatchSlots . map ( { $0 . estimatedEndDate ( includeBreakTime : includeBreakTime ) } ) . max ( ) else {
guard let minimumPossibleEndDate = previousMatchSlots . map ( {
$0 . estimatedEndDate ( includeBreakTime : includeBreakTime )
} ) . max ( ) else {
print ( " No valid previous match end date, returning \( previousMatchIsInPreviousRotation ) . " )
return previousMatchIsInPreviousRotation
return previousMatchIsInPreviousRotation
}
}
if targetedStartDate >= minimumPossibleEndDate {
if targetedStartDate >= minimumPossibleEndDate {
if rotationDifferenceIsImportant {
if rotationDifferenceIsImportant {
print ( " Targeted start date is after the minimum possible end date and rotation difference is important, returning \( previousMatchIsInPreviousRotation ) . " )
return previousMatchIsInPreviousRotation
return previousMatchIsInPreviousRotation
} else {
} else {
print ( " Targeted start date is after the minimum possible end date, returning true. " )
return true
return true
}
}
} else {
} else {
if targetedStartDate = = minimumTargetedEndDate {
if targetedStartDate = = minimumTargetedEndDate {
print ( " Updating minimumTargetedEndDate to minimumPossibleEndDate: \( minimumPossibleEndDate ) " )
minimumTargetedEndDate = minimumPossibleEndDate
minimumTargetedEndDate = minimumPossibleEndDate
} else {
} else {
print ( " Setting minimumTargetedEndDate to the earlier of \( minimumPossibleEndDate ) and \( minimumTargetedEndDate ) " )
minimumTargetedEndDate = min ( minimumPossibleEndDate , minimumTargetedEndDate )
minimumTargetedEndDate = min ( minimumPossibleEndDate , minimumTargetedEndDate )
}
}
print ( " Targeted start date is before the minimum possible end date, returning false. " )
return false
return false
}
}
}
}
func getNextStartDate ( fromPreviousRotationSlots slots : [ TimeMatch ] , includeBreakTime : Bool ) -> Date ? {
func getNextStartDate ( fromPreviousRotationSlots slots : [ TimeMatch ] , includeBreakTime : Bool ) -> Date ? {
slots . map { $0 . estimatedEndDate ( includeBreakTime : includeBreakTime ) } . min ( )
slots . map { $0 . estimatedEndDate ( includeBreakTime : includeBreakTime ) } . min ( )
}
}
@ -369,7 +426,6 @@ final class MatchScheduler : ModelObject, Storable {
}
}
func roundDispatcher ( numberOfCourtsAvailablePerRotation : Int , flattenedMatches : [ Match ] , dispatcherStartDate : Date , initialCourts : [ Int ] ? ) -> MatchDispatcher {
func roundDispatcher ( numberOfCourtsAvailablePerRotation : Int , flattenedMatches : [ Match ] , dispatcherStartDate : Date , initialCourts : [ Int ] ? ) -> MatchDispatcher {
var slots = [ TimeMatch ] ( )
var slots = [ TimeMatch ] ( )
var _startDate : Date ?
var _startDate : Date ?
var rotationIndex = 0
var rotationIndex = 0
@ -377,6 +433,9 @@ final class MatchScheduler : ModelObject, Storable {
let courtsUnavailability = courtsUnavailability
let courtsUnavailability = courtsUnavailability
var issueFound : Bool = false
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 " )
flattenedMatches . filter { $0 . startDate != nil } . sorted ( by : \ . startDate ! ) . forEach { match in
flattenedMatches . filter { $0 . startDate != nil } . sorted ( by : \ . startDate ! ) . forEach { match in
if _startDate = = nil {
if _startDate = = nil {
_startDate = match . startDate
_startDate = match . startDate
@ -389,19 +448,16 @@ final class MatchScheduler : ModelObject, Storable {
slots . append ( timeMatch )
slots . append ( timeMatch )
}
}
if slots . isEmpty = = false {
if ! slots . isEmpty {
rotationIndex += 1
rotationIndex += 1
}
}
var freeCourtPerRotation = [ Int : [ Int ] ] ( )
var freeCourtPerRotation = [ Int : [ Int ] ] ( )
let availableCourt = numberOfCourtsAvailablePerRotation
let availableCourt = numberOfCourtsAvailablePerRotation
var courts = initialCourts ? ? ( 0. . < availableCourt ) . map { $0 }
var courts = initialCourts ? ? ( 0. . < availableCourt ) . map { $0 }
var shouldStartAtDispatcherDate = rotationIndex > 0
var shouldStartAtDispatcherDate = rotationIndex > 0
while availableMatchs . count > 0 && issueFound = = false {
while ! availableMatchs . isEmpty && ! issueFound && rotationIndex < 100 {
freeCourtPerRotation [ rotationIndex ] = [ ]
freeCourtPerRotation [ rotationIndex ] = [ ]
let previousRotationSlots = slots . filter ( { $0 . rotationIndex = = rotationIndex - 1 } )
let previousRotationSlots = slots . filter ( { $0 . rotationIndex = = rotationIndex - 1 } )
var rotationStartDate : Date = getNextStartDate ( fromPreviousRotationSlots : previousRotationSlots , includeBreakTime : false ) ? ? dispatcherStartDate
var rotationStartDate : Date = getNextStartDate ( fromPreviousRotationSlots : previousRotationSlots , includeBreakTime : false ) ? ? dispatcherStartDate
@ -413,23 +469,28 @@ final class MatchScheduler : ModelObject, Storable {
courts = rotationIndex = = 0 ? courts : ( 0. . < availableCourt ) . map { $0 }
courts = rotationIndex = = 0 ? courts : ( 0. . < availableCourt ) . map { $0 }
}
}
courts . sort ( )
courts . sort ( )
print ( " courts available at rotation \( rotationIndex ) " , courts )
print ( " rotationStartDate " , rotationStartDate )
if rotationIndex > 0 , let freeCourtPreviousRotation = freeCourtPerRotation [ rotationIndex - 1 ] , freeCourtPreviousRotation . count > 0 {
// L o g c o u r t s a v a i l a b i l i t y a n d s t a r t d a t e
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 " )
print ( " Courts available at rotation \( rotationIndex ) : \( courts ) " )
let previousPreviousRotationSlots = slots . filter ( { $0 . rotationIndex = = rotationIndex - 2 && freeCourtPreviousRotation . contains ( $0 . courtIndex ) } )
print ( " Rotation start date: \( rotationStartDate ) " )
// C h e c k f o r c o u r t a v a i l a b i l i t y a n d b r e a k t i m e c o n f l i c t s
if rotationIndex > 0 , let freeCourtPreviousRotation = freeCourtPerRotation [ rotationIndex - 1 ] , ! freeCourtPreviousRotation . isEmpty {
print ( " Handling break time conflicts or waiting for free courts " )
let previousPreviousRotationSlots = slots . filter { $0 . rotationIndex = = rotationIndex - 2 && freeCourtPreviousRotation . contains ( $0 . courtIndex ) }
let previousEndDate = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : accountUpperBracketBreakTime )
let previousEndDate = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : accountUpperBracketBreakTime )
let previousEndDateNoBreak = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : false )
let previousEndDateNoBreak = getNextStartDate ( fromPreviousRotationSlots : previousPreviousRotationSlots , includeBreakTime : false )
let noBreakAlreadyTested = previousRotationSlots . anySatisfy ( { $0 . startDate = = previousEndDateNoBreak } )
let noBreakAlreadyTested = previousRotationSlots . anySatisfy { $0 . startDate = = previousEndDateNoBreak }
if let previousEndDate , let previousEndDateNoBreak {
if let previousEndDate , let previousEndDateNoBreak {
let differenceWithBreak = rotationStartDate . timeIntervalSince ( previousEndDate )
let differenceWithBreak = rotationStartDate . timeIntervalSince ( previousEndDate )
let differenceWithoutBreak = rotationStartDate . timeIntervalSince ( previousEndDateNoBreak )
let differenceWithoutBreak = rotationStartDate . timeIntervalSince ( previousEndDateNoBreak )
print ( " difference w break " , differenceWithBreak )
print ( " Difference with break: \( differenceWithBreak ) , without break: \( differenceWithoutBreak ) " )
print ( " difference w/o break " , differenceWithoutBreak )
let timeDifferenceLimitInSeconds = Double ( timeDifferenceLimit * 60 )
let timeDifferenceLimitInSeconds = Double ( timeDifferenceLimit * 60 )
var difference = differenceWithBreak
var difference = differenceWithBreak
if differenceWithBreak <= 0 {
if differenceWithBreak <= 0 {
difference = differenceWithoutBreak
difference = differenceWithoutBreak
} else if differenceWithBreak > timeDifferenceLimitInSeconds && differenceWithoutBreak > timeDifferenceLimitInSeconds {
} else if differenceWithBreak > timeDifferenceLimitInSeconds && differenceWithoutBreak > timeDifferenceLimitInSeconds {
@ -437,34 +498,35 @@ final class MatchScheduler : ModelObject, Storable {
}
}
if difference > timeDifferenceLimitInSeconds && rotationStartDate . addingTimeInterval ( - difference ) != previousEndDate {
if difference > timeDifferenceLimitInSeconds && rotationStartDate . addingTimeInterval ( - difference ) != previousEndDate {
courts . removeAll ( where : { index in freeCourtPreviousRotation . contains ( index )
courts . removeAll ( where : { freeCourtPreviousRotation . contains ( $0 ) } )
} )
freeCourtPerRotation [ rotationIndex ] = courts
freeCourtPerRotation [ rotationIndex ] = courts
courts = freeCourtPreviousRotation
courts = freeCourtPreviousRotation
rotationStartDate = rotationStartDate . addingTimeInterval ( - difference )
rotationStartDate = rotationStartDate . addingTimeInterval ( - difference )
}
}
}
}
} else if let first = availableMatchs . first {
} else if let firstMatch = availableMatchs . first {
let duration = first . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
let duration = firstMatch . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : duration , courtsUnavailability : courtsUnavailability )
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : duration , courtsUnavailability : courtsUnavailability )
if courtsUnavailable . count = = numberOfCourtsAvailablePerRotation {
if courtsUnavailable . count = = numberOfCourtsAvailablePerRotation {
print ( " issue " )
print ( " Issue: All courts unavailable in this rotation " )
issueFound = true
issueFound = true
} else {
} else {
courts = Array ( Set ( courts ) . subtracting ( Set ( courtsUnavailable ) ) )
courts = Array ( Set ( courts ) . 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 ( availableCourts : numberOfCourtsAvailablePerRotation , courts : courts , availableMatchs : & availableMatchs , slots : & slots , rotationIndex : rotationIndex , rotationStartDate : rotationStartDate , freeCourtPerRotation : & freeCourtPerRotation , courtsUnavailability : courtsUnavailability )
rotationIndex += 1
rotationIndex += 1
}
}
// O r g a n i z e m a t c h e s i n s l o t s
var organizedSlots = [ TimeMatch ] ( )
var organizedSlots = [ TimeMatch ] ( )
for i in 0. . < rotationIndex {
for i in 0. . < rotationIndex {
let courtsSorted = slots . filter ( { $0 . rotationIndex = = i } ) . map { $0 . courtIndex } . sorted ( )
let courtsSorted = slots . filter { $0 . rotationIndex = = i } . map { $0 . courtIndex } . sorted ( )
let courts = randomizeCourts ? courtsSorted . shuffled ( ) : courtsSorted
let courts = randomizeCourts ? courtsSorted . shuffled ( ) : courtsSorted
var matches = slots . filter ( { $0 . rotationIndex = = i } ) . sorted ( using : . keyPath ( \ . courtIndex ) )
var matches = slots . filter { $0 . rotationIndex = = i } . sorted ( using : . keyPath ( \ . courtIndex ) )
for j in 0. . < matches . count {
for j in 0. . < matches . count {
matches [ j ] . courtIndex = courts [ j ]
matches [ j ] . courtIndex = courts [ j ]
@ -472,110 +534,88 @@ final class MatchScheduler : ModelObject, Storable {
}
}
}
}
print ( " Finished roundDispatcher with \( organizedSlots . count ) scheduled matches " )
return MatchDispatcher ( timedMatches : slots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , issueFound : issueFound )
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 ( availableCourts : Int , courts : [ Int ] , availableMatchs : inout [ Match ] , slots : inout [ TimeMatch ] , rotationIndex : Int , rotationStartDate : Date , freeCourtPerRotation : inout [ Int : [ Int ] ] , courtsUnavailability : [ DateInterval ] ? ) {
var matchPerRound = [ String : Int ] ( )
var matchPerRound = [ String : Int ] ( )
var minimumTargetedEndDate : Date = rotationStartDate
var minimumTargetedEndDate = rotationStartDate
print ( " dispatchCourts " , courts . sorted ( ) , rotationStartDate , rotationIndex )
// L o g d i s p a t c h a t t e m p t
print ( " Dispatching courts for rotation \( rotationIndex ) with start date \( rotationStartDate ) and available courts \( courts . sorted ( ) ) " )
for ( courtPosition , courtIndex ) in courts . sorted ( ) . enumerated ( ) {
for ( courtPosition , courtIndex ) in courts . sorted ( ) . enumerated ( ) {
if let first = availableMatchs . first ( where : { match in
if let firstMatch = availableMatchs . first ( where : { match in
print ( " trying to find a match for \( courtIndex ) in \( rotationIndex ) " )
print ( " Trying to find a match for court \( courtIndex ) in rotation \( rotationIndex ) " )
let roundObject = match . roundObject !
let roundObject = match . roundObject !
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : match . matchFormat . getEstimatedDuration ( additionalEstimationDuration ) , courtsUnavailability : courtsUnavailability )
let duration = match . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
print ( " courtsUnavailable \( courtsUnavailable ) " )
if courtPosition >= availableCourts - courtsUnavailable . count {
let courtsUnavailable = courtsUnavailable ( startDate : rotationStartDate , duration : duration , courtsUnavailability : courtsUnavailability )
if courtsUnavailable . contains ( courtPosition ) {
print ( " Returning false: Court \( courtIndex ) unavailable due to schedule conflicts during \( rotationStartDate ) . " )
return false
return false
}
}
let canBePlayed = roundMatchCanBePlayed ( match , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate , minimumTargetedEndDate : & minimumTargetedEndDate )
let canBePlayed = roundMatchCanBePlayed ( match , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate , minimumTargetedEndDate : & minimumTargetedEndDate )
let currentRotationSameRoundMatches = matchPerRound [ roundObject . id ] ? ? 0
if ! canBePlayed {
print ( " Returning false: Match \( match . roundAndMatchTitle ( ) ) can't be played due to constraints. " )
return false
}
let currentRotationSameRoundMatches = matchPerRound [ roundObject . id ] ? ? 0
let roundMatchesCount = roundObject . playedMatches ( ) . count
let roundMatchesCount = roundObject . playedMatches ( ) . count
if shouldHandleUpperRoundSlice {
if shouldHandleUpperRoundSlice {
print ( " shouldHandleUpperRoundSlice \( roundMatchesCount ) " )
if roundObject . parent = = nil && roundMatchesCount > courts . count && currentRotationSameRoundMatches >= min ( roundMatchesCount / 2 , courts . count ) {
if roundObject . parent = = nil && roundMatchesCount > courts . count {
print ( " Returning false: Too many matches already played in the current rotation for round \( roundObject . roundTitle ( ) ) . " )
print ( " roundMatchesCount \( roundMatchesCount ) > \( courts . count ) " )
return false
if currentRotationSameRoundMatches >= min ( roundMatchesCount / 2 , courts . count ) {
print ( " return false, \( currentRotationSameRoundMatches ) >= \( min ( roundMatchesCount / 2 , courts . count ) ) " )
return false
}
}
}
}
}
// i f a l l i s o k , w e d o a f i n a l c h e c k t o s e e i f t h e f i r s t
let indexInRound = match . indexInRound ( )
let indexInRound = match . indexInRound ( )
print ( " Upper Round, index > 0, first Match of round \( indexInRound ) and more than one court available; looking for next match (same round) \( indexInRound + 1 ) " )
if roundObject . parent = = nil && roundObject . index > 0 && indexInRound = = 0 , let nextMatch = match . next ( ) {
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 ) {
guard courtPosition < courts . count - 1 , courts . count > 1 else {
print ( " Returning true: Both current \( match . index ) and next match \( nextMatch . index ) can be played in rotation \( rotationIndex ) . " )
print ( " next match and this match can not be played at the same time, returning false " )
return false
}
if canBePlayed && roundMatchCanBePlayed ( nextMatch , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate , minimumTargetedEndDate : & minimumTargetedEndDate ) {
print ( " next match and this match can be played, returning true " )
return true
return true
}
} else {
}
print ( " Returning false: Either current match or next match cannot be played in rotation \( rotationIndex ) . " )
// n o t a d d i n g a l a s t m a t c h o f a 4 - m a t c h r o u n d ( f i n a l n o t i n c l u d e d o b v i o u s l y )
print ( " \( currentRotationSameRoundMatches ) modulo \( currentRotationSameRoundMatches % 2 ) same round match is even, index of round is not 0 and upper bracket. If it's not the last court available \( courtIndex ) == \( courts . count - 1 ) " )
if shouldTryToFillUpCourtsAvailable = = false {
if roundMatchesCount <= 4 && currentRotationSameRoundMatches % 2 = = 0 && roundObject . index != 0 && roundObject . parent = = nil && ( ( courts . count > 1 && courtPosition >= courts . count - 1 ) || courts . count = = 1 && availableCourts > 1 ) {
print ( " we return false " )
return false
return false
}
}
}
}
print ( " Returning true: Match \( match . roundAndMatchTitle ( ) ) can be played on court \( courtIndex ) . " )
return canBePlayed
return canBePlayed
} ) {
} ) {
print ( first . roundObject ! . roundTitle ( ) , first . matchTitle ( ) , courtIndex , rotationStartDate )
print ( " Found match: \( firstMatch . roundAndMatchTitle ( ) ) for court \( courtIndex ) at \( rotationStartDate ) " )
if first . roundObject ! . parent = = nil {
matchPerRound [ firstMatch . roundObject ! . id , default : 0 ] += 1
if let roundIndex = matchPerRound [ first . roundObject ! . id ] {
matchPerRound [ first . roundObject ! . id ] = roundIndex + 1
} else {
matchPerRound [ first . roundObject ! . id ] = 1
}
}
let timeMatch = TimeMatch ( matchID : first . id , rotationIndex : rotationIndex , courtIndex : courtIndex , startDate : rotationStartDate , durationLeft : first . matchFormat . getEstimatedDuration ( additionalEstimationDuration ) , minimumBreakTime : first . matchFormat . breakTime . breakTime )
slots . append ( timeMatch )
availableMatchs . removeAll ( where : { $0 . id = = first . id } )
} else {
freeCourtPerRotation [ rotationIndex ] ! . append ( courtIndex )
}
}
if freeCourtPerRotation [ rotationIndex ] ! . count = = availableCourts {
let timeMatch = TimeMatch (
print ( " no match found to be put in this rotation, check if we can put anything to another date " )
matchID : firstMatch . id ,
freeCourtPerRotation [ rotationIndex ] = [ ]
rotationIndex : rotationIndex ,
let courtsUsed = getNextEarliestAvailableDate ( from : slots )
courtIndex : courtIndex ,
var freeCourts : [ Int ] = [ ]
startDate : rotationStartDate ,
if courtsUsed . isEmpty {
durationLeft : firstMatch . matchFormat . getEstimatedDuration ( additionalEstimationDuration ) ,
freeCourts = ( 0. . < availableCourts ) . map { $0 }
minimumBreakTime : firstMatch . matchFormat . breakTime . breakTime
)
slots . append ( timeMatch )
availableMatchs . removeAll ( where : { $0 . id = = firstMatch . id } )
} else {
} else {
freeCourts = courtsUsed . filter { ( courtIndex , availableDate ) in
print ( " No suitable match found for court \( courtIndex ) in rotation \( rotationIndex ) . Adding court to freeCourtPerRotation. " )
availableDate <= minimumTargetedEndDate
freeCourtPerRotation [ rotationIndex ] ? . append ( courtIndex )
} . sorted ( by : \ . 1 ) . map { $0 . 0 }
}
}
}
if let first = availableMatchs . first {
if freeCourtPerRotation [ rotationIndex ] ? . count = = availableCourts {
let duration = first . matchFormat . getEstimatedDuration ( additionalEstimationDuration )
print ( " All courts in rotation \( rotationIndex ) are free " )
let courtsUnavailable = courtsUnavailable ( startDate : minimumTargetedEndDate , duration : duration , courtsUnavailability : courtsUnavailability )
if courtsUnavailable . count < availableCourts {
dispatchCourts ( availableCourts : availableCourts , courts : freeCourts , availableMatchs : & availableMatchs , slots : & slots , rotationIndex : rotationIndex , rotationStartDate : minimumTargetedEndDate , freeCourtPerRotation : & freeCourtPerRotation , courtsUnavailability : courtsUnavailability )
}
}
}
}
}
}
@ -586,16 +626,20 @@ final class MatchScheduler : ModelObject, Storable {
var rounds = [ Round ] ( )
var rounds = [ Round ] ( )
if let groupStageLoserBracketRound = tournament . groupStageLoserBracket ( ) {
rounds . append ( groupStageLoserBracketRound )
}
if shouldEndRoundBeforeStartingNext {
if shouldEndRoundBeforeStartingNext {
rounds = upperRounds . flatMap {
rounds . append ( contentsOf : upperRounds . flatMap {
[ $0 ] + $0 . loserRoundsAndChildren ( )
[ $0 ] + $0 . loserRoundsAndChildren ( )
}
} )
} else {
} else {
rounds = upperRounds . map {
rounds . append ( contentsOf : upperRounds . map {
$0
$0
} + upperRounds . flatMap {
} + upperRounds . flatMap {
$0 . loserRoundsAndChildren ( )
$0 . loserRoundsAndChildren ( )
}
} )
}
}
let flattenedMatches = rounds . flatMap { round in
let flattenedMatches = rounds . flatMap { round in