From 883fdaea187a1936a8cb546ea9f064467963f996 Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 24 Oct 2024 20:17:28 +0200 Subject: [PATCH 1/4] wip --- PadelClub/Data/GroupStage.swift | 108 +++++++++++++++--- .../Components/GroupStageSettingsView.swift | 6 + 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 15eee50..e00435d 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -115,6 +115,25 @@ final class GroupStage: ModelObject, Storable { return match } + func addReturnMatches() { + var teamScores = [TeamScore]() + var matches = [Match]() + + for i in 0..<_numberOfMatchesToBuild() { + let newMatch = self._createMatch(index: i + matchCount) +// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i)) + teamScores.append(contentsOf: newMatch.createTeamScores()) + matches.append(newMatch) + } + + do { + try self.tournamentStore.matches.addOrUpdate(contentOfs: matches) + try self.tournamentStore.teamScores.addOrUpdate(contentOfs: teamScores) + } catch { + Logger.error(error) + } + } + func buildMatches(keepExistingMatches: Bool = false) { var teamScores = [TeamScore]() var matches = [Match]() @@ -291,40 +310,58 @@ final class GroupStage: ModelObject, Storable { return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() } + func isReturnMatchEnabled() -> Bool { + _matches().count > matchCount + } + private func _matchOrder() -> [Int] { + var order: [Int] + switch size { case 3: - return [1, 2, 0] + order = [1, 2, 0] case 4: - return [2, 3, 1, 4, 5, 0] + order = [2, 3, 1, 4, 5, 0] case 5: -// return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9] - return [3, 5, 8, 2, 6, 1, 9, 4, 7, 0] + order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0] case 6: - //return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0] - return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] + order = [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] default: - return [] + order = [] + } + + if isReturnMatchEnabled() { + let arraySize = order.count + // Duplicate and increment each value by the array size + order += order.map { $0 + arraySize } } + + return order } - + + func indexOf(_ matchIndex: Int) -> Int { _matchOrder().firstIndex(of: matchIndex) ?? matchIndex } private func _matchUp(for matchIndex: Int) -> [Int] { - Array((0.. String { let matchUp = _matchUp(for: matchIndex) if let index = matchUp.first, let index2 = matchUp.last { - return "#\(index + 1) vs #\(index2 + 1)" + return "#\(index + 1) vs #\(index2 + 1)" + (matchIndex >= matchCount ? " - retour" : "") } else { return "--" } } + var matchCount: Int { + (size * size - 1) / 2 + } + func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? { let _teams = _teams(for: matchIndex) switch team { @@ -337,7 +374,7 @@ final class GroupStage: ModelObject, Storable { private func _teams(for matchIndex: Int) -> [TeamRegistration?] { let combinations = Array(0.. Bool { let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted() let combos = Array((0.. 1 { + let scoreA = calculateScore(for: teamPosition, matches: matches, groupStagePosition: teamPosition.groupStagePosition!) + let scoreB = calculateScore(for: otherTeam, matches: matches, groupStagePosition: otherTeam.groupStagePosition!) + + let teamsSorted = [scoreA, scoreB].sorted { (lhs, rhs) in + let predicates: [TeamScoreAreInIncreasingOrder] = [ + { $0.wins < $1.wins }, + { $0.setDifference < $1.setDifference }, + { $0.gameDifference < $1.gameDifference}, + { [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! } + ] + + for predicate in predicates { + if !predicate(lhs, rhs) && !predicate(rhs, lhs) { + continue + } + + return predicate(lhs, rhs) + } + + return false + }.map({ $0.team }) + + return teamsSorted.first == teamPosition } else { - return false + + if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) { + return teamPosition.id == match.losingTeamId + } else { + return false + } + } } @@ -428,16 +495,19 @@ final class GroupStage: ModelObject, Storable { guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) if matches.isEmpty && nilIfEmpty { return nil } + let score = calculateScore(for: team, matches: matches, groupStagePosition: groupStagePosition) + scoreCache[groupStagePosition] = score + return score + } + + private func calculateScore(for team: TeamRegistration, matches: [Match], groupStagePosition: Int) -> TeamGroupStageScore { let wins = matches.filter { $0.winningTeamId == team.id }.count let loses = matches.filter { $0.losingTeamId == team.id }.count let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) } let setDifference = differences.map { $0.set }.reduce(0,+) let gameDifference = differences.map { $0.game }.reduce(0,+) - // Calculate the score and store it in the cache - let score = (team, wins, loses, setDifference, gameDifference) - scoreCache[groupStagePosition] = score - return score + return (team, wins, loses, setDifference, gameDifference) } // Clear the cache if necessary, for example when starting a new step or when matches update diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift index ad0d943..9f7851a 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -149,6 +149,12 @@ struct GroupStageSettingsView: View { Text("Tous les matchs seront recronstruits, les données des matchs seront perdus.") } + Section { + RowButtonView("Rajouter les matchs retours", role: .destructive) { + groupStage.addReturnMatches() + } + } + Section { RowButtonView("Rafraichir", role: .destructive) { let playedMatches = groupStage.playedMatches() From 8fbafd8c08cdd4419b5d64d5ae64e769e47905d3 Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 25 Oct 2024 11:14:52 +0200 Subject: [PATCH 2/4] wip --- PadelClub/Data/GroupStage.swift | 9 +++++ .../Components/GroupStageSettingsView.swift | 10 ++++-- .../GroupStage/GroupStagesSettingsView.swift | 36 ++++++++++++++++--- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index e00435d..b7bd594 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -115,6 +115,15 @@ final class GroupStage: ModelObject, Storable { return match } + func removeReturnMatches() { + let returnMatches = _matches().filter({ $0.index >= matchCount }) + do { + try self.tournamentStore.matches.delete(contentOfs: returnMatches) + } catch { + Logger.error(error) + } + } + func addReturnMatches() { var teamScores = [TeamScore]() var matches = [Match]() diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift index 9f7851a..7bf706d 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -150,8 +150,14 @@ struct GroupStageSettingsView: View { } Section { - RowButtonView("Rajouter les matchs retours", role: .destructive) { - groupStage.addReturnMatches() + if groupStage.isReturnMatchEnabled() { + RowButtonView("Effacer les matchs retours", role: .destructive) { + groupStage.removeReturnMatches() + } + } else { + RowButtonView("Rajouter les matchs retours", role: .destructive) { + groupStage.addReturnMatches() + } } } diff --git a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift index 8f31945..bafec28 100644 --- a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift @@ -12,7 +12,7 @@ struct GroupStagesSettingsView: View { @EnvironmentObject var dataStore: DataStore @Environment(\.dismiss) private var dismiss @Environment(Tournament.self) var tournament: Tournament - @State private var generationDone: Bool = false + @State private var generationDoneMessage: String? let step: Int var tournamentStore: TournamentStore { @@ -167,6 +167,32 @@ struct GroupStagesSettingsView: View { } footer: { Text("Redistribue les équipes par la méthode du serpentin") } + + let groupStages = tournament.groupStages() + + Section { + if groupStages.anySatisfy({ $0.isReturnMatchEnabled() }) { + RowButtonView("Effacer les matchs retours", role: .destructive) { + groupStages.filter({ $0.isReturnMatchEnabled() }).forEach { groupStage in + groupStage.removeReturnMatches() + } + generationDoneMessage = "Matchs retours effacés" + } + } + + } + + Section { + if groupStages.anySatisfy({ $0.isReturnMatchEnabled() == false }) { + RowButtonView("Rajouter les matchs retours", role: .destructive) { + groupStages.filter({ $0.isReturnMatchEnabled() == false }).forEach { groupStage in + groupStage.addReturnMatches() + } + + generationDoneMessage = "Matchs retours créés" + } + } + } Section { RowButtonView("Nommer les poules alphabétiquement", role: .destructive) { @@ -220,8 +246,8 @@ struct GroupStagesSettingsView: View { } .overlay(alignment: .bottom) { - if generationDone { - Label("Poules mises à jour", systemImage: "checkmark.circle.fill") + if let generationDoneMessage { + Label(generationDoneMessage, systemImage: "checkmark.circle.fill") .toastFormatted() .deferredRendering(for: .seconds(2)) } @@ -238,7 +264,7 @@ struct GroupStagesSettingsView: View { RowButtonView("Refaire les poules", role: .destructive) { tournament.deleteGroupStages() tournament.buildGroupStages() - generationDone = true + generationDoneMessage = "Poules mises à jour" tournament.shouldVerifyGroupStage = false _save() } @@ -249,7 +275,7 @@ struct GroupStagesSettingsView: View { RowButtonView("Poule \(mode.localizedLabel().lowercased())", role: .destructive, systemImage: mode.systemImage) { tournament.groupStageOrderingMode = mode tournament.refreshGroupStages(keepExistingMatches: true) - generationDone = true + generationDoneMessage = "Poules mises à jour" tournament.shouldVerifyGroupStage = false _save() } From dced9bb0be0f5167dd7df84afe7945a9adba2ffb Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 25 Oct 2024 11:52:03 +0200 Subject: [PATCH 3/4] fix --- PadelClub/Data/GroupStage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index b7bd594..7b21f1b 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -255,7 +255,7 @@ final class GroupStage: ModelObject, Storable { matchIndexes.append(index) } } - return _matches().filter { matchIndexes.contains($0.index) } + return _matches().filter { matchIndexes.contains($0.index%matchCount) } } func initialStartDate(forTeam team: TeamRegistration) -> Date? { From b7f2b338167427b1762ebef77cc9fc83343207ea Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 25 Oct 2024 15:18:03 +0200 Subject: [PATCH 4/4] fix issue with match deletion in groupstage --- PadelClub/Data/GroupStage.swift | 1 + .../Views/GroupStage/Components/GroupStageSettingsView.swift | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 7b21f1b..da476cb 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -146,6 +146,7 @@ final class GroupStage: ModelObject, Storable { func buildMatches(keepExistingMatches: Bool = false) { var teamScores = [TeamScore]() var matches = [Match]() + clearScoreCache() if keepExistingMatches == false { _removeMatches() diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift index 7bf706d..5bfa715 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -143,7 +143,11 @@ struct GroupStageSettingsView: View { Section { RowButtonView("Recommencer tous les matchs", role: .destructive) { + let isReturnMatchesEnabled = groupStage.isReturnMatchEnabled() groupStage.buildMatches() + if isReturnMatchesEnabled { + groupStage.addReturnMatches() + } } } footer: { Text("Tous les matchs seront recronstruits, les données des matchs seront perdus.")