@ -7,11 +7,34 @@
import Foundation
struct GroupStageTimeMatch {
let matchID : String
let rotationIndex : Int
var courtIndex : Int
let groupIndex : Int
}
struct TimeMatch {
let matchID : String
let rotationIndex : Int
var courtIndex : Int
let groupIndex : Int
var startDate : Date
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
func estimatedEndDate ( includeBreakTime : Bool ) -> Date {
let minutesToAdd = Double ( durationLeft + ( includeBreakTime ? minimumBreakTime : 0 ) )
return startDate . addingTimeInterval ( minutesToAdd * 60.0 )
}
}
struct GroupStageMatchDispatcher {
let timedMatches : [ GroupStageTimeMatch ]
let freeCourtPerRotation : [ Int : [ Int ] ]
let rotationCount : Int
let groupLastRotation : [ Int : Int ]
}
struct MatchDispatcher {
@ -34,7 +57,7 @@ extension Match {
class MatchScheduler {
static let shared = MatchScheduler ( )
func groupStageDispatcher ( numberOfCourtsAvailablePerRotation : Int , groupStages : [ GroupStage ] , startingDate : Date ? , randomizeCourts : Bool ) -> MatchDispatcher {
func groupStageDispatcher ( numberOfCourtsAvailablePerRotation : Int , groupStages : [ GroupStage ] , startingDate : Date ? , randomizeCourts : Bool ) -> GroupStage MatchDispatcher {
let _groupStages = groupStages . filter { startingDate = = nil || $0 . startDate = = startingDate }
@ -50,7 +73,7 @@ class MatchScheduler {
}
}
var slots = [ TimeMatch ] ( )
var slots = [ GroupStage TimeMatch] ( )
var availableMatchs = flattenedMatches
var rotationIndex = 0
var teamsPerRotation = [ Int : [ String ] ] ( )
@ -81,7 +104,8 @@ class MatchScheduler {
if let first = rotationMatches . first ( where : { match in
teamsPerRotation [ rotationIndex ] ! . allSatisfy ( { match . containsTeamId ( $0 ) = = false } ) = = true
} ) {
slots . append ( TimeMatch ( 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 )
slots . append ( timeMatch )
teamsPerRotation [ rotationIndex ] ! . append ( contentsOf : first . teamIds ( ) )
rotationMatches . removeAll ( where : { $0 . id = = first . id } )
availableMatchs . removeAll ( where : { $0 . id = = first . id } )
@ -96,7 +120,7 @@ class MatchScheduler {
rotationIndex += 1
}
var organizedSlots = [ TimeMatch ] ( )
var organizedSlots = [ GroupStage TimeMatch] ( )
for i in 0. . < rotationIndex {
let courtsSorted = slots . filter ( { $0 . rotationIndex = = i } ) . map { $0 . courtIndex } . sorted ( )
let courts = randomizeCourts ? courtsSorted . shuffled ( ) : courtsSorted
@ -109,10 +133,10 @@ class MatchScheduler {
}
return MatchDispatcher ( timedMatches : organizedSlots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , groupLastRotation : groupLastRotation )
return GroupStage MatchDispatcher( timedMatches : organizedSlots , freeCourtPerRotation : freeCourtPerRotation , rotationCount : rotationIndex , groupLastRotation : groupLastRotation )
}
func roundMatchCanBePlayed ( _ match : Match , roundObject : Round , slots : [ TimeMatch ] , rotationIndex : Int ) -> Bool {
func roundMatchCanBePlayed ( _ match : Match , roundObject : Round , slots : [ TimeMatch ] , rotationIndex : Int , targetedStartDate : Date ) -> Bool {
print ( roundObject . roundTitle ( ) , match . matchTitle ( ) )
let previousMatches = roundObject . precedentMatches ( ofMatch : match )
if previousMatches . isEmpty { return true }
@ -135,34 +159,91 @@ class MatchScheduler {
return false
}
let previousMatchIsInPreviousRotation = previousMatchSlots . allSatisfy ( { $0 . rotationIndex + ( roundObject . loser = = nil ? 1 : 0 ) < rotationIndex } )
return previousMatchIsInPreviousRotation
// 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 {
return previousMatchIsInPreviousRotation
}
return targetedStartDate >= minimumPossibleEndDate
}
func getAvailableCourt ( inSlots slots : [ TimeMatch ] , nextStartDate : Date ) -> [ TimeMatch ] {
guard let minimumDuration = slots . compactMap ( { $0 . durationLeft } ) . min ( ) else { return [ ] }
var newSlots = [ TimeMatch ] ( )
slots . forEach { timeMatch in
let durationLeft = timeMatch . durationLeft
if durationLeft - minimumDuration > 0 {
let timeMatch = TimeMatch ( matchID : timeMatch . matchID , rotationIndex : timeMatch . rotationIndex + 1 , courtIndex : timeMatch . courtIndex , groupIndex : timeMatch . groupIndex , startDate : nextStartDate , durationLeft : durationLeft , minimumBreakTime : timeMatch . minimumBreakTime , courtLocked : true )
newSlots . append ( timeMatch )
}
}
return newSlots
}
func roundDispatcher ( numberOfCourtsAvailablePerRotation : Int , flattenedMatches : [ Match ] , randomizeCourts : Bool , initialOccupiedCourt : Int = 0 ) -> MatchDispatcher {
func getNextStartDate ( fromPreviousRotationSlots slots : [ TimeMatch ] , includeBreakTime : Bool ) -> Date ? {
slots . map { $0 . estimatedEndDate ( includeBreakTime : includeBreakTime ) } . min ( )
}
func roundDispatcher ( numberOfCourtsAvailablePerRotation : Int , flattenedMatches : [ Match ] , randomizeCourts : Bool , initialOccupiedCourt : Int = 0 , dispatcherStartDate : Date ) -> MatchDispatcher {
var slots = [ TimeMatch ] ( )
var availableMatchs = flattenedMatches
var rotationIndex = 0
var freeCourtPerRotation = [ Int : [ Int ] ] ( )
var groupLastRotation = [ Int : Int ] ( )
while slots . count < flattenedMatches . count {
var courts = [ Int ] ( )
var timeToAdd = 0.0
while availableMatchs . count > 0 {
freeCourtPerRotation [ rotationIndex ] = [ ]
var matchPerRound = [ Int : Int ] ( )
var availableCourt = numberOfCourtsAvailablePerRotation
if rotationIndex = = 0 {
availableCourt = availableCourt - initialOccupiedCourt
}
( 0. . < availableCourt ) . forEach { courtIndex in
courts = ( 0. . < availableCourt ) . map { $0 }
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 in
duplicatedSlots . anySatisfy { $0 . courtIndex = = index }
} )
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 {
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 ] ) } )
if let first = availableMatchs . first ( where : { match in
let roundObject = match . roundObject !
let canBePlayed = roundMatchCanBePlayed ( match , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex )
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 ) {
if canBePlayed && roundMatchCanBePlayed ( nextMatch , roundObject : roundObject , slots : slots , rotationIndex : rotationIndex , targetedStartDate : rotationStartDate ) {
return true
} else {
return false
@ -184,11 +265,13 @@ class MatchScheduler {
matchPerRound [ first . roundObject ! . index ] = 1
}
}
slots . append ( TimeMatch ( matchID : first . id , rotationIndex : rotationIndex , courtIndex : courtIndex , groupIndex : first . roundObject ! . index ) )
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 )
}
@ -199,7 +282,7 @@ class MatchScheduler {
var organizedSlots = [ TimeMatch ] ( )
for i in 0. . < rotationIndex {
let courtsSorted = slots . filter ( { $0 . rotationIndex = = i } ) . map { $0 . courtIndex } . sorted ( )
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 ) )
@ -235,12 +318,11 @@ class MatchScheduler {
flattenedMatches . forEach ( { $0 . startDate = nil } )
let roundDispatch = self . roundDispatcher ( numberOfCourtsAvailablePerRotation : tournament . courtCount , flattenedMatches : flattenedMatches , randomizeCourts : randomizeCourts , initialOccupiedCourt : 0 )
let roundDispatch = self . roundDispatcher ( numberOfCourtsAvailablePerRotation : tournament . courtCount , flattenedMatches : flattenedMatches , randomizeCourts : randomizeCourts , initialOccupiedCourt : 0 , dispatcherStartDate : startDate )
roundDispatch . timedMatches . forEach { matchSchedule in
if let match = flattenedMatches . first ( where : { $0 . id = = matchSchedule . matchID } ) {
let timeIntervalToAdd = ( Double ( matchSchedule . rotationIndex ) ) * Double ( match . matchFormat . estimatedDuration ) * 60
match . startDate = startDate . addingTimeInterval ( timeIntervalToAdd )
match . startDate = matchSchedule . startDate
match . setCourt ( matchSchedule . courtIndex + 1 )
}
}