Merge branch 'match-retour'

paca_championship
Raz 1 year ago
commit eb451f6655
  1. 120
      PadelClub/Data/GroupStage.swift
  2. 16
      PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift
  3. 36
      PadelClub/Views/GroupStage/GroupStagesSettingsView.swift

@ -115,9 +115,38 @@ final class GroupStage: ModelObject, Storable {
return match 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]()
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) { func buildMatches(keepExistingMatches: Bool = false) {
var teamScores = [TeamScore]() var teamScores = [TeamScore]()
var matches = [Match]() var matches = [Match]()
clearScoreCache()
if keepExistingMatches == false { if keepExistingMatches == false {
_removeMatches() _removeMatches()
@ -227,7 +256,7 @@ final class GroupStage: ModelObject, Storable {
matchIndexes.append(index) matchIndexes.append(index)
} }
} }
return _matches().filter { matchIndexes.contains($0.index) } return _matches().filter { matchIndexes.contains($0.index%matchCount) }
} }
func initialStartDate(forTeam team: TeamRegistration) -> Date? { func initialStartDate(forTeam team: TeamRegistration) -> Date? {
@ -291,40 +320,58 @@ final class GroupStage: ModelObject, Storable {
return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed()
} }
func isReturnMatchEnabled() -> Bool {
_matches().count > matchCount
}
private func _matchOrder() -> [Int] { private func _matchOrder() -> [Int] {
var order: [Int]
switch size { switch size {
case 3: case 3:
return [1, 2, 0] order = [1, 2, 0]
case 4: case 4:
return [2, 3, 1, 4, 5, 0] order = [2, 3, 1, 4, 5, 0]
case 5: case 5:
// return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9] order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0]
return [3, 5, 8, 2, 6, 1, 9, 4, 7, 0]
case 6: case 6:
//return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0] order = [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0]
return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0]
default: 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 { func indexOf(_ matchIndex: Int) -> Int {
_matchOrder().firstIndex(of: matchIndex) ?? matchIndex _matchOrder().firstIndex(of: matchIndex) ?? matchIndex
} }
private func _matchUp(for matchIndex: Int) -> [Int] { private func _matchUp(for matchIndex: Int) -> [Int] {
Array((0..<size).combinations(ofCount: 2))[safe: matchIndex] ?? [] let combinations = Array((0..<size).combinations(ofCount: 2))
return combinations[safe: matchIndex%matchCount] ?? []
} }
func localizedMatchUpLabel(for matchIndex: Int) -> String { func localizedMatchUpLabel(for matchIndex: Int) -> String {
let matchUp = _matchUp(for: matchIndex) let matchUp = _matchUp(for: matchIndex)
if let index = matchUp.first, let index2 = matchUp.last { 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 { } else {
return "--" return "--"
} }
} }
var matchCount: Int {
(size * size - 1) / 2
}
func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? { func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? {
let _teams = _teams(for: matchIndex) let _teams = _teams(for: matchIndex)
switch team { switch team {
@ -337,7 +384,7 @@ final class GroupStage: ModelObject, Storable {
private func _teams(for matchIndex: Int) -> [TeamRegistration?] { private func _teams(for matchIndex: Int) -> [TeamRegistration?] {
let combinations = Array(0..<size).combinations(ofCount: 2).map {$0} let combinations = Array(0..<size).combinations(ofCount: 2).map {$0}
return combinations[safe: matchIndex]?.map { teamAt(groupStagePosition: $0) } ?? [] return combinations[safe: matchIndex%matchCount]?.map { teamAt(groupStagePosition: $0) } ?? []
} }
private func _removeMatches() { private func _removeMatches() {
@ -363,10 +410,40 @@ final class GroupStage: ModelObject, Storable {
fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool { fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool {
let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted() let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted()
let combos = Array((0..<size).combinations(ofCount: 2)) let combos = Array((0..<size).combinations(ofCount: 2))
if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) { let matchIndexes = combos.enumerated().compactMap { $0.element == indexes ? $0.offset : nil }
return teamPosition.id == match.losingTeamId let matches = _matches().filter { matchIndexes.contains($0.index) }
if matches.count > 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 { } 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 +505,19 @@ final class GroupStage: ModelObject, Storable {
guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
if matches.isEmpty && nilIfEmpty { return nil } 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 wins = matches.filter { $0.winningTeamId == team.id }.count
let loses = matches.filter { $0.losingTeamId == team.id }.count let loses = matches.filter { $0.losingTeamId == team.id }.count
let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) } let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) }
let setDifference = differences.map { $0.set }.reduce(0,+) let setDifference = differences.map { $0.set }.reduce(0,+)
let gameDifference = differences.map { $0.game }.reduce(0,+) let gameDifference = differences.map { $0.game }.reduce(0,+)
// Calculate the score and store it in the cache return (team, wins, loses, setDifference, gameDifference)
let score = (team, wins, loses, setDifference, gameDifference)
scoreCache[groupStagePosition] = score
return score
} }
// Clear the cache if necessary, for example when starting a new step or when matches update // Clear the cache if necessary, for example when starting a new step or when matches update

@ -143,12 +143,28 @@ struct GroupStageSettingsView: View {
Section { Section {
RowButtonView("Recommencer tous les matchs", role: .destructive) { RowButtonView("Recommencer tous les matchs", role: .destructive) {
let isReturnMatchesEnabled = groupStage.isReturnMatchEnabled()
groupStage.buildMatches() groupStage.buildMatches()
if isReturnMatchesEnabled {
groupStage.addReturnMatches()
}
} }
} footer: { } footer: {
Text("Tous les matchs seront recronstruits, les données des matchs seront perdus.") Text("Tous les matchs seront recronstruits, les données des matchs seront perdus.")
} }
Section {
if groupStage.isReturnMatchEnabled() {
RowButtonView("Effacer les matchs retours", role: .destructive) {
groupStage.removeReturnMatches()
}
} else {
RowButtonView("Rajouter les matchs retours", role: .destructive) {
groupStage.addReturnMatches()
}
}
}
Section { Section {
RowButtonView("Rafraichir", role: .destructive) { RowButtonView("Rafraichir", role: .destructive) {
let playedMatches = groupStage.playedMatches() let playedMatches = groupStage.playedMatches()

@ -12,7 +12,7 @@ struct GroupStagesSettingsView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@State private var generationDone: Bool = false @State private var generationDoneMessage: String?
let step: Int let step: Int
var tournamentStore: TournamentStore { var tournamentStore: TournamentStore {
@ -167,6 +167,32 @@ struct GroupStagesSettingsView: View {
} footer: { } footer: {
Text("Redistribue les équipes par la méthode du serpentin") 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 { Section {
RowButtonView("Nommer les poules alphabétiquement", role: .destructive) { RowButtonView("Nommer les poules alphabétiquement", role: .destructive) {
@ -220,8 +246,8 @@ struct GroupStagesSettingsView: View {
} }
.overlay(alignment: .bottom) { .overlay(alignment: .bottom) {
if generationDone { if let generationDoneMessage {
Label("Poules mises à jour", systemImage: "checkmark.circle.fill") Label(generationDoneMessage, systemImage: "checkmark.circle.fill")
.toastFormatted() .toastFormatted()
.deferredRendering(for: .seconds(2)) .deferredRendering(for: .seconds(2))
} }
@ -238,7 +264,7 @@ struct GroupStagesSettingsView: View {
RowButtonView("Refaire les poules", role: .destructive) { RowButtonView("Refaire les poules", role: .destructive) {
tournament.deleteGroupStages() tournament.deleteGroupStages()
tournament.buildGroupStages() tournament.buildGroupStages()
generationDone = true generationDoneMessage = "Poules mises à jour"
tournament.shouldVerifyGroupStage = false tournament.shouldVerifyGroupStage = false
_save() _save()
} }
@ -249,7 +275,7 @@ struct GroupStagesSettingsView: View {
RowButtonView("Poule \(mode.localizedLabel().lowercased())", role: .destructive, systemImage: mode.systemImage) { RowButtonView("Poule \(mode.localizedLabel().lowercased())", role: .destructive, systemImage: mode.systemImage) {
tournament.groupStageOrderingMode = mode tournament.groupStageOrderingMode = mode
tournament.refreshGroupStages(keepExistingMatches: true) tournament.refreshGroupStages(keepExistingMatches: true)
generationDone = true generationDoneMessage = "Poules mises à jour"
tournament.shouldVerifyGroupStage = false tournament.shouldVerifyGroupStage = false
_save() _save()
} }

Loading…
Cancel
Save