fix issue with matchscheduler

disable any locks for creatiing a player manually
sync2
Raz 1 year ago
parent d3ed0147be
commit dd4501b95c
  1. 2
      PadelClub.xcodeproj/project.pbxproj
  2. 6
      PadelClub/Data/Match.swift
  3. 279
      PadelClub/Data/MatchScheduler.swift
  4. 2
      PadelClub/Views/Player/Components/PlayerPopoverView.swift
  5. 2
      PadelClub/Views/Round/RoundView.swift

@ -3139,6 +3139,7 @@
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
GCC_OPTIMIZATION_LEVEL = 0;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club";
@ -3182,6 +3183,7 @@
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
GCC_OPTIMIZATION_LEVEL = 0;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club";

@ -408,7 +408,7 @@ defer {
} }
func next() -> Match? { func next() -> Match? {
let matches: [Match] = self.tournamentStore.matches.filter { $0.round == round && $0.index > index } let matches: [Match] = self.tournamentStore.matches.filter { $0.round == round && $0.index > index && $0.disabled == false }
return matches.sorted(by: \.index).first return matches.sorted(by: \.index).first
} }
@ -435,6 +435,10 @@ defer {
else { return nil } else { return nil }
} }
func roundAndMatchTitle() -> String {
[roundTitle(), matchTitle()].compactMap({ $0 }).joined(separator: " ")
}
func topPreviousRoundMatchIndex() -> Int { func topPreviousRoundMatchIndex() -> Int {
return index * 2 + 1 return index * 2 + 1
} }

@ -179,30 +179,39 @@ final class MatchScheduler : ModelObject, Storable {
// Get the maximum count of matches in any group // Get the maximum count of matches in any group
let maxMatchesCount = _groupStages.map { $0._matches().count }.max() ?? 0 let maxMatchesCount = _groupStages.map { $0._matches().count }.max() ?? 0
// Use zip and flatMap to flatten matches in the desired order // Flatten matches in a round-robin order by cycling through each group
let flattenedMatches = (0..<maxMatchesCount).flatMap { index in let flattenedMatches = (0..<maxMatchesCount).flatMap { index in
_groupStages.compactMap { group in _groupStages.compactMap { group in
// Use optional subscript to safely access matches // Safely access matches, return nil if index is out of bounds
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 availableMatches = flattenedMatches
var rotationIndex = 0 var rotationIndex = 0
var teamsPerRotation = [Int: [String]]() var teamsPerRotation = [Int: [String]]() // Tracks teams assigned to each rotation
var freeCourtPerRotation = [Int: [Int]]() var freeCourtPerRotation = [Int: [Int]]() // Tracks free courts per rotation
var groupLastRotation = [Int: Int]() var groupLastRotation = [Int: Int]() // Tracks the last rotation each group was involved in
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 // Check if all teams from the match are not already scheduled in the current rotation
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,29 +225,42 @@ final class MatchScheduler : ModelObject, Storable {
} }
(0..<numberOfCourtsAvailablePerRotation).forEach { courtIndex in (0..<numberOfCourtsAvailablePerRotation).forEach { courtIndex in
//print(mt.map { ($0.bracket!.index.intValue, counts[$0.bracket!.index.intValue]) }) 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 courtsUnavailable.contains(courtIndex) { if courtsUnavailable.contains(courtIndex) {
print("Court \(courtIndex) is unavailable at \(rotationStartDate)")
return false
}
let teamsAvailable = teamsPerRotation[rotationIndex]!.allSatisfy({ !match.containsTeamId($0) })
if !teamsAvailable {
print("Teams from match \(match.roundAndMatchTitle()) are already scheduled in this rotation")
return false return false
} else {
return teamsPerRotation[rotationIndex]!.allSatisfy({ match.containsTeamId($0) == false }) == true
} }
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(first.matchTitle())
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)
} }
} }
@ -246,6 +268,9 @@ final class MatchScheduler : ModelObject, Storable {
rotationIndex += 1 rotationIndex += 1
} }
print("All matches scheduled. Total rotations: \(rotationIndex)")
// Organize slots and ensure courts are randomized or sorted
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()
@ -258,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
@ -271,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 start \(targetedStartDate) earlier than \(roundStartDate)") print("Cannot start at \(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
}
guard let minimumPossibleEndDate = previousMatchSlots.map({ $0.estimatedEndDate(includeBreakTime: includeBreakTime) }).max() else { 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 {
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()
} }
@ -370,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
@ -378,6 +433,9 @@ final class MatchScheduler : ModelObject, Storable {
let courtsUnavailability = courtsUnavailability let courtsUnavailability = courtsUnavailability
var issueFound: Bool = false var issueFound: Bool = false
// Log start of the function
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
@ -390,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
@ -414,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 { // Log courts availability and start date
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)")
// Check for court availability and break time conflicts
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 {
@ -438,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)))
} }
} }
// Dispatch courts and schedule matches
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
} }
// Organize matches in slots
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]
@ -473,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)
// Log dispatch attempt
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)")
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: duration, courtsUnavailability: courtsUnavailability)
if courtsUnavailable.contains(courtPosition) { 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
}
} }
} }
//if all is ok, we do a final check to see if the first
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).")
//not adding a last match of a 4-match round (final not included obviously)
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)
}
}
} }
} }

@ -31,7 +31,7 @@ struct PlayerPopoverView: View {
@State private var source: String? @State private var source: String?
init(source: String?, sex: Int, requiredField: [PlayerCreationField] = [.firstName, .lastName], creationCompletionHandler: @escaping (PlayerRegistration) -> Void) { init(source: String?, sex: Int, requiredField: [PlayerCreationField] = [], creationCompletionHandler: @escaping (PlayerRegistration) -> Void) {
if let source { if let source {
let words = source.components(separatedBy: .whitespaces) let words = source.components(separatedBy: .whitespaces)
if words.isEmpty == false { if words.isEmpty == false {

@ -259,7 +259,7 @@ struct RoundView: View {
#if DEBUG #if DEBUG
Spacer() Spacer()
Text(match.teamScores.count.formatted()) Text(match.index.formatted() + " " + match.teamScores.count.formatted())
#endif #endif
} }
} footer: { } footer: {

Loading…
Cancel
Save