From 97a15edc9f40d4b97c9ba424bb423a47bc50d5af Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Fri, 22 Mar 2024 06:54:37 +0100 Subject: [PATCH] add padel rule --- PadelClub.xcodeproj/project.pbxproj | 16 + PadelClub/Manager/PadelRule.swift | 1374 +++++++++++++++++ PadelClub/Views/Event/EventCreationView.swift | 18 + .../Navigation/Agenda/EmptyActivityView.swift | 8 +- 4 files changed, 1414 insertions(+), 2 deletions(-) create mode 100644 PadelClub/Manager/PadelRule.swift create mode 100644 PadelClub/Views/Event/EventCreationView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 707187f..875a58c 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -67,6 +67,8 @@ FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */; }; FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; + FF8F26382BAD523300650388 /* PadelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26352BAD523300650388 /* PadelRule.swift */; }; + FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; @@ -192,6 +194,8 @@ FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionManagerView.swift; sourceTree = ""; }; FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = ""; }; FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; + FF8F26352BAD523300650388 /* PadelRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelRule.swift; sourceTree = ""; }; + FF8F263A2BAD528600650388 /* EventCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCreationView.swift; sourceTree = ""; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = ""; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = ""; }; @@ -335,6 +339,7 @@ C425D4022B6D249D002A7B48 /* ContentView.swift */, C4A47D732B72881F00ADC637 /* ClubView.swift */, FF39719B2B8DE04B004C4E75 /* Navigation */, + FF8F26392BAD526A00650388 /* Event */, FF1DC54D2BAB34FA00FD8220 /* Club */, FF3F74F72B919F96004CFE0E /* Tournament */, C4A47D882B7BBB5000ADC637 /* Subscription */, @@ -515,6 +520,14 @@ path = Network; sourceTree = ""; }; + FF8F26392BAD526A00650388 /* Event */ = { + isa = PBXGroup; + children = ( + FF8F263A2BAD528600650388 /* EventCreationView.swift */, + ); + path = Event; + sourceTree = ""; + }; FFD783FB2B91B919000F62A6 /* Agenda */ = { isa = PBXGroup; children = ( @@ -543,6 +556,7 @@ FFF8ACD12B9238C3008466FA /* FileImportManager.swift */, FFF8ACD32B92392C008466FA /* SourceFileManager.swift */, FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */, + FF8F26352BAD523300650388 /* PadelRule.swift */, FF6EC9072B947A1E00EA7F5A /* Network */, ); path = Manager; @@ -729,6 +743,7 @@ FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */, C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, + FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, FF70916C2B91005400AB08DA /* TournamentView.swift in Sources */, FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */, @@ -772,6 +787,7 @@ C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */, FFF8ACD22B9238C3008466FA /* FileImportManager.swift in Sources */, FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */, + FF8F26382BAD523300650388 /* PadelRule.swift in Sources */, FFF8ACDB2B923F48008466FA /* Date+Extensions.swift in Sources */, FF1DC5592BAB767000FD8220 /* Tips.swift in Sources */, FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */, diff --git a/PadelClub/Manager/PadelRule.swift b/PadelClub/Manager/PadelRule.swift new file mode 100644 index 0000000..ac564a0 --- /dev/null +++ b/PadelClub/Manager/PadelRule.swift @@ -0,0 +1,1374 @@ +// +// PadelRule.swift +// Padel Tournament +// +// Created by razmig on 27/02/2023. +// + +import Foundation + +enum RankSource: Hashable { + case national + case ligue + case club(assimilation: Bool) + + var localizedLabel: String { + switch self { + case .national: + return "Classement National" + case .ligue: + return "Classement Ligue" + case .club: + return "Classement Club" + } + } +} + +struct TournamentBuild: Hashable, Codable, Identifiable { + var id: String { identifier } + let category: TournamentCategory + let level: TournamentLevel + let age: FederalTournamentAge + var japIdentifier: Int? = nil + var japFirstName: String? = nil + var japLastName: String? = nil + + var identifier: String { + level.localizedLabel+":"+category.localizedLabel+":"+age.localizedLabel + } + + var computedLabel: String { + if age == .senior { return localizedLabel } + return localizedLabel + " " + localizedAge + } + + var localizedLabel: String { + level.localizedLabel + category.localizeLabelShort + } + + var localizedTitle: String { + level.localizedLabel + " " + category.localizedLabel + } + + var localizedAge: String { + age.tournamentDescriptionLabel + } +} + +extension TournamentBuild { + + init?(category: String, level: String, age: FederalTournamentAge = .senior) { + guard let levelFound = TournamentLevel.allCases.first(where: { $0.localizedLabel == level }) else { return nil } + + var c = category + if c.hasPrefix("ME") { + c = "H" + } + + if c.hasPrefix("F") { + c = "D" + } + + guard let categoryFound = TournamentCategory.allCases.first(where: { c.canonicalVersion.hasPrefix($0.buildLabel.canonicalVersion) }) else { return nil } + self.level = levelFound + self.category = categoryFound + self.age = age + } +} + +enum FederalTournamentType: String, Hashable, Codable, CaseIterable, Identifiable { + case tournoi = "P" + case championnatParEquipe = "S" + case championnatParPaire = "L" + + var id: String { self.rawValue } + var localizedLabel: String { + switch self { + case .tournoi: + return "Tournois" + case .championnatParEquipe: + return "Championnats par équipes" + case .championnatParPaire: + return "Championnats par paires" + } + } +} + +enum TournamentDifficulty { + + case rankS + case rankA + case rankB + case rankC + + init?(gameDifference: Double) { + switch gameDifference { + case ..<3: + self = .rankS + case ..<5: + self = .rankA + case ..<7: + self = .rankB + case 7...: + self = .rankC + default: + return nil + } + } + + var localizedLabel: String { + switch self { + case .rankS: + return "S" + case .rankA: + return "A" + case .rankB: + return "B" + case .rankC: + return "C" + } + } + + var backgroundColor: String { + switch self { + case .rankS: + return "#d4af37" + case .rankA: + return "#c0c0c0" + case .rankB: + return "#cd7f32" + case .rankC: + return "#DCC2E0" + } + } +} + +enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifiable { + case a11_12 = 120 + case a13_14 = 140 + case a15_16 = 160 + case a17_18 = 180 + case senior = 200 + case a45 = 450 + case a55 = 550 + + static func mostRecent(tournaments: [Tournament] = []) -> Self { + .senior +// return tournaments.first?.federalTournamentAge ?? .a11_12 + } + + static func mostUsed(tournaments: [Tournament] = []) -> Self { +// let countedSet = NSCountedSet(array: tournaments.map { $0.federalTournamentAge }) +// let mostFrequent = countedSet.max { countedSet.count(for: $0) < countedSet.count(for: $1) } +// if mostFrequent != nil { +// return mostFrequent as! FederalTournamentAge +// } else { +// return mostRecent(tournaments: tournaments) +// } + .senior + } + + var id: Int { self.rawValue } + + var order: Int { + switch self { + case .a11_12: + return 6 + case .a13_14: + return 5 + case .a15_16: + return 4 + case .a17_18: + return 3 + case .senior: + return 0 + case .a45: + return 1 + case .a55: + return 2 + } + } + + var localizedLabel: String { + switch self { + case .a11_12: + return "11/12 ans" + case .a13_14: + return "13/14 ans" + case .a15_16: + return "15/16 ans" + case .a17_18: + return "17/18 ans" + case .senior: + return "Senior" + case .a45: + return "45 ans" + case .a55: + return "55 ans" + } + } + + var tournamentDescriptionLabel: String { + switch self { + case .senior: + return "" + case .a45, .a55: + return "+" + localizedLabel + default: + return localizedLabel + } + } +} + +enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable { + case p25 = 25 + case p100 = 100 + case p250 = 250 + case p500 = 500 + case p1000 = 1000 + case p1500 = 1500 + case p2000 = 2000 + + static func mostRecent(tournaments: [Tournament] = []) -> Self { + //return tournaments.first?.tournamentLevel ?? .p25 + .p100 + } + + static func mostUsed(tournaments: [Tournament] = []) -> Self { +// let countedSet = NSCountedSet(array: tournaments.map { $0.tournamentLevel }) +// let mostFrequent = countedSet.max { countedSet.count(for: $0) < countedSet.count(for: $1) } +// if mostFrequent != nil { +// return mostFrequent as! TournamentLevel +// } else { +// return mostRecent(tournaments: tournaments) +// } + .p100 + } + + var id: Int { self.rawValue } + + func maximumDuration() -> Double { + switch self { + case .p25: + return 0.5 + default: + return 3 + } + } + + func minimumPlayerRank(category: TournamentCategory, ageCategory: FederalTournamentAge) -> Int { + switch self { + case .p25: + switch ageCategory { + case .senior: + return category == .men ? 10000 : 1000 + default: + return 0 + } + case .p100: + switch ageCategory { + case .senior: + return category == .men ? 2000 : 300 + default: + return 0 + } + + case .p250: + switch ageCategory { + case .senior: + return category == .men ? 500 : 100 + default: + return 0 + } + + default: + return 0 + } + } + + func federalFormatForGroupStage() -> MatchFormat { + federalFormatForBracketRound(5) + } + + func federalFormatForBracketRound(_ roundIndex: Int) -> MatchFormat { + switch self { + case .p25: + return .superTie + case .p100: + return .nineGamesDecisivePoint + case .p250: + if roundIndex == 0 { //finale + return .twoSetsDecisivePointSuperTie + } else { + return .nineGamesDecisivePoint + } + + case .p500: + if roundIndex == 0 { //finale + return .twoSetsDecisivePoint + } else if roundIndex == 1 { //demi-finale + return .twoSetsDecisivePointSuperTie + } else { + return .nineGamesDecisivePoint + } + case .p1000, .p1500: + if roundIndex <= 1 { //demi / finale + return .twoSetsDecisivePoint + } else { + return .twoSetsDecisivePointSuperTie + } + default: + return .twoSetsDecisivePoint + } + } + + func decisivePointRequired(ageCategory: FederalTournamentAge) -> Bool { + switch ageCategory { + case .a11_12, .a13_14, .a15_16, .a17_18: + return true + default: + return false + } + } + + func federalFormatForLoserBracketRound(_ roundIndex: Int) -> MatchFormat { + switch self { + case .p25: + return .superTie + case .p100, .p250, .p500: + return .nineGamesDecisivePoint + default: + if roundIndex <= 1 { //petite finale + return .twoSetsDecisivePointSuperTie + } else { + return .nineGamesDecisivePoint + } + } + } + + + var defaultEntriesSortingType: EntriesSortingType { + switch self { + case .p25, .p100, .p250: + return .inscriptionDate + default: + return .rank + } + } + + var order: Int { + switch self { + case .p25: + return 6 + case .p100: + return 5 + case .p250: + return 4 + case .p500: + return 3 + case .p1000: + return 2 + case .p1500: + return 1 + case .p2000: + return 0 + } + } + + var localizedLabel: String { + return String(describing: self).capitalized + } + + var coachingIsAuthorized: Bool { + switch self { + case .p500, .p1000, .p1500, .p2000: + return true + default: + return false + } + } + + func points(for rank: Int, count: Int) -> Int { + let points = points(for: count) + if rank >= points.count { + return points.last! + } else if rank == 0 { + return Int(self.rawValue) + } else { + return points[rank-1] + } + } + + func allPoints(for count: Int) -> [Int] { + [Int(self.rawValue)] + points(for: count) + } + + func points(for count: Int) -> [Int] { + switch self { + case .p25: + switch count { + case 9...12: + return [17, 13, 11, 9, 7, 5, 4, 3, 2, 1] + case 13...16: + return [18,16,15,14,13,12,11,10,9,7,5,4,3,2, 1] + case 17...20: + return [20,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2, 1] + case 21...24: + return [20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2, 1] + case 25...28: + return [21,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2, 1] + case _ where count > 28: + return [23,21,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2, 1] + default: + return [15, 12, 9, 6, 4, 2, 1] + } + case .p100: + switch count { + case 9...12: + return [65,55, 50, 35, 25, 20, 15, 10, 5, 3, 1] + case 13...16: + return [70, 60, 55, 45, 40, 35, 30, 25, 21, 18, 15, 10, 5, 3, 1] + case 17...20: + return [75,65,60,55,50,45,40,35,30,25,23,20,18,15,12,10,5,3, 1] + case 21...24: + return [75,70,65,60,55,50,47,43,40,37,33,30,28,25,23,20,18,15,12,10,5,3, 1] + case 25...28: + return [80,75,70,65,60,55,53,50,48,45,43,40,38,35,33,30,28,25,23,20,18,15,12,10,5,3, 1] + case _ where count > 28: + return [80,75,72,70,65,63,60,58,55,53,50,48,45,43,40,38,35,33,30,28,25,23,20,18,15,12,10,8,5,3, 1] + default: + return [60,50,40,25,10,5] + } + case .p250: + switch count { + case 9...12: + return [163,138,125,88,63,50,38,25,13,8,3] + case 13...16: + return [175,150,138,113,100,88,75,63,53,45,38,25,13,8,3] + case 17...20: + return [188,163,150,138,123,113,100,88,75,63,58,50,45,38,30,25,13,8,3] + case 21...24: + return [188,175,163,150,138,125,118,108,100,93,83,75,70,63,58,50,45,38,30,25,13,8,3] + case 25...28: + return [200,188,175,163,150,138,133,125,120,113,108,100,95,88,83,75,70,63,58,50,45,38,30,25,13,8,3] + case _ where count > 28: + return [200,188,180,175,163,158,150,145,138,133,125,120,113,108,100,95,88,83,75,70,63,58,50,45,38,30,25,20,13,8,3] + default: + return [150,125,100,63,25,13,3] + } + case .p500: + switch count { + case 9...12: + return [325,275,250,175,125,100,75,50,25,15,5] + case 13...16: + return [350,300,275,225,200,175,150,125,105,90,75,50,25,15,5] + case 17...20: + return [375,325,300,275,250,225,200,175,150,125,115,100,90,75,60,50,25,15,5] + case 21...24: + return [375,350,325,300,275,250,235,215,200,185,165,150,140,125,115,100,80,75,60,50,25,15,5] + case 25...28: + return [400,375,350,325,300,275,265,250,240,225,215,200,190,175,165,150,140,125,115,100,80,75,60,50,25,15,5] + case _ where count > 28: + return [400,375,360,350,325,315,300,290,275,265,250,240,225,215,200,190,175,165,150,140,125,115,100,80,75,60,50,40,25,15,5] + default: + return [300,250,200,125,50,25,5] + } + case .p1000: + switch count { + case 9...12: + return [650,550, 500, 350, 250, 200, 150, 100, 50, 30, 10] + case 13...16: + return [700, 600, 550, 450, 400, 350, 300, 250, 210, 180, 150, 100, 50, 30, 10] + case 17...20: + return [750,650,600,550,500,450,400,350,300,250,230,200,180,150,120,100,50,30, 10] + case 21...24: + return [750,700,650,600,550,500,470,430,400,370,330,300,280,250,230,200,180,150,120,100,50,30, 10] + case 25...28: + return [800,750,700,650,600,550,530,500,480,450,430,400,380,350,330,300,280,250,230,200,180,150,120,100,50,30, 10] + case _ where count > 28: + return [800,750,720,700,650,630,600,580,550,530,500,480,450,430,400,380,350,330,300,280,250,230,200,180,150,120,100,80,50,30, 10] + default: + return [600,500,400,250,100,50] + } + case .p1500: + switch count { + case 21...24: + return [1125,1050,975,900,825,750,705,645,600,555,495,450,420,375,345,300,270,225,180,150,75,45,15] + case 25...28: + return [1200,1125,1050,975,900,825,795,750,720,675,645,600,570,525,495,450,420,375,345,300,270,225,180,150,75,45,15] + case _ where count > 28: + return [1200,1125,1080,1050,975,945,900,870,825,795,750,720,675,645,600,570,525,495,450,420,375,345,300,270,225,180,150,120,75,45,15] + default: + return [1125,975,900,825,750,675,600,525,450,375,345,300,270,225,180,150,75,45,15] + } + case .p2000: + return [1600,1440,1300,1180,1120,1060,1000,880,840,800,760,720,680,640,600,540,520,500,480,460,440,420,400,260,200,40] + } + } + + var ranges: [PlayersCountRange] { + switch self { + case .p1500: + return [.N16, .N24, .N28, .N32] + case .p2000: + return [.N32] + default: + return PlayersCountRange.allCases + } + } + + enum NewBallSystem { + case perField + case perMatch(fromRound: Int?) + + func localizedLabel(loserBracket: Bool = false) -> String { + switch self { + case .perField: + return "3 / piste" + case .perMatch(let fromRound): + if fromRound != nil { + if loserBracket { + return "3 / match pour les perdants des \(RoundLabel.shortLabels[fromRound!].lowercased())s" + } else { + return "3 / match à partir des \(RoundLabel.shortLabels[fromRound!].lowercased())s" + } + } else { + return "3 / match" + } + } + } + } + + func minimumFormatFinalTableAndQualifier(roundIndex: Int) -> MatchFormat? { + switch self { + case .p25: + return nil + case .p100: + return .nineGamesDecisivePoint + case .p250: + if roundIndex == 0 { //final + return .twoSetsDecisivePointSuperTie + } else { + return .nineGamesDecisivePoint + } + case .p500: + if roundIndex == 0 { //final + return .twoSetsDecisivePoint + } else if roundIndex == 1 { //demi + return .twoSetsDecisivePointSuperTie + } else { + return .nineGamesDecisivePoint + } + case .p1000, .p1500, .p2000: + if roundIndex == 3 { //16eme + return .twoSetsDecisivePoint + } else { + return .twoSetsDecisivePointSuperTie + } + } + } + + func minimumFormatLoserBracket(roundIndex: Int) -> MatchFormat? { + switch self { + case .p25: + return nil + case .p100, .p250, .p500: + return .nineGamesDecisivePoint + case .p1000, .p1500, .p2000: + if roundIndex == 1 { //demi + return .twoSetsDecisivePoint + } else { + return .nineGamesDecisivePoint + } + } + } + + + func newBallsFinalTable() -> NewBallSystem? { + switch self { + case .p25, .p100: + return .perField + case .p250: + return .perMatch(fromRound: 1) //demi + case .p500: + return .perMatch(fromRound: 2) //quart + case .p1000, .p1500, .p2000: + return .perMatch(fromRound: nil) + } + } + + func newBallsLoserBracket() -> NewBallSystem? { + switch self { + case .p25, .p100: + return nil + case .p250: + return .perMatch(fromRound: 1) //demi + case .p500, .p1000, .p1500, .p2000: + return .perMatch(fromRound: 2) //quart + } + } + +} + +enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { + case men + case women + case mix + + var localizedPlayerLabel: String { + switch self { + case .women: + return "joueuse" + default: + return "joueur" + } + } + + static func mostRecent(tournaments: [Tournament] = []) -> Self { + //return tournaments.first?.tournamentCategory ?? .mix + .men + } + + static func mostUsed(tournaments: [Tournament] = []) -> Self { +// let countedSet = NSCountedSet(array: tournaments.map { $0.tournamentCategory }) +// let mostFrequent = countedSet.max { countedSet.count(for: $0) < countedSet.count(for: $1) } +// if mostFrequent != nil { +// return mostFrequent as! TournamentCategory +// } else { +// return mostRecent(tournaments: tournaments) +// } + .men + } + + var next: TournamentCategory { + switch self { + case .men: + return .women + case .women: + return .mix + case .mix: + return .men + } + } + + var id: Int { self.rawValue } + + var sexValue: Int { + switch self { + case .men: + return 1 + case .women: + return 0 + case .mix: + return -1 + } + } + + var order: Int { + switch self { + case .men: + return 0 + case .women: + return 1 + case .mix: + return 2 + } + } + + var buildLabel: String { + switch self { + case .men: + return "H" + case .women: + return "D" + case .mix: + return "M" + } + } + + var localizeLabelShort: String { + switch self { + case .men: + return "H" + case .women: + return "D" + case .mix: + return "MX" + } + } + + var requestLabel: String { + switch self { + case .men: + return "DM" + case .women: + return "DD" + case .mix: + return "DX" + } + } + + var importingRawValue: String { + switch self { + case .men: + return "messieurs" + case .women: + return "dames" + case .mix: + return "mixte" + } + } + + var localizedLabel: String { + switch self { + case .men: + return "Hommes" + case .women: + return "Dames" + case .mix: + return "Mixte" + } + } + + var rankingDataSourceMale: Bool { + switch self { + case .men: + return true + case .women: + return false + case .mix: + return false + } + } + + var playerFilterOption: PlayerFilterOption { + switch self { + case .men: + return .male + case .women: + return .female + case .mix: + return .all + } + } +} + + +enum BracketOrderingMode: Int, Hashable, Codable, CaseIterable, Identifiable { + case random + case snake + case swiss + + var id: Int { self.rawValue } + var localizedLabel: String { + switch self { + case .random: + return "Au hasard" + case .snake: + return "En serpentin" + case .swiss: + return "Suisse" + } + } + + var systemImage: String { + switch self { + case .random: + return "dice.fill" + case .snake: + return "arrow.triangle.swap" + case .swiss: + return "cross.fill" + } + } +} + +enum TournamentType: Int, Hashable, Codable, CaseIterable, Identifiable { + case classic + case doubleBrackets + + var id: Int { self.rawValue } + var localizedLabel: String { + switch self { + case .classic: + return "Classique" + case .doubleBrackets: + return "Double Poules" + } + } +} + +enum TeamData: Int, Hashable, Codable, CaseIterable { + case one + case two + + var otherTeam: TeamData { + switch self { + case .one: + return .two + case .two: + return .one + } + } + + var localizedLabel: String { + switch self { + case .one: + return "#1" + case .two: + return "#2" + } + } +} + +enum SetFormat: Int, Hashable, Codable { + case nine + case four + case six + case superTieBreak + case megaTieBreak + + func shouldTiebreak(scoreTeamOne: Int, scoreTeamTwo: Int) -> Bool { + if let tieBreak { + return (scoreTeamOne + scoreTeamTwo) >= (2 * tieBreak) && (scoreTeamOne == tieBreak || scoreTeamTwo == tieBreak) + } else { + return false + } + } + + func winner(teamOne: Int, teamTwo: Int) -> TeamData { + return teamOne >= teamTwo ? .one : .two + } + + func hasEnded(teamOne: Int, teamTwo: Int) -> Bool { + switch self { + case .nine: + if teamOne == 9 || teamTwo == 9 { + return true + } + case .four: + if teamOne == 5 || teamTwo == 5 { + return true + } + case .six: + if teamOne == 7 || teamTwo == 7 { + return true + } + case .superTieBreak, .megaTieBreak: + if teamOne == 1 || teamTwo == 1 { + return true + } + } + return (teamOne >= scoreToWin && teamOne >= teamTwo + 2) || (teamTwo >= scoreToWin && teamTwo >= teamOne + 2) + } + + var tieBreak: Int? { + switch self { + case .nine: + return 8 + case .four: + return 4 + case .six: + return 6 + case .superTieBreak, .megaTieBreak: + return nil + } + } + + func disableValuesForTeamTwo(with teamOneScore: Int) -> [Int] { + switch self { + case .nine: + if teamOneScore == 9 { + return [9] + } + case .four: + if teamOneScore == 4 { + return [] + } + case .six: + if teamOneScore == 6 { + return [] + } + case .superTieBreak: + if teamOneScore == 10 { + return [] + } + case .megaTieBreak: + if teamOneScore == 15 { + return [] + } + } + return [] + } + + var possibleValues: [Int] { + switch self { + case .nine: + return [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + case .four: + return [5, 4, 3, 2, 1, 0] + case .six: + return [7, 6, 5, 4, 3, 2, 1, 0] + case .superTieBreak: + return [10,9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + case .megaTieBreak: + return [15, 14, 13, 12, 11, 10,9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + } + } + + var scoreToWin: Int { + switch self { + case .nine: + return 9 + case .four: + return 4 + case .six: + return 6 + case .superTieBreak: + return 10 + case .megaTieBreak: + return 15 + } + } + + var firstGameFormat: Format { + switch self { + case .megaTieBreak: + return .tiebreakFiveTeen + case .superTieBreak: + return .tiebreakTen + default: + return .normal + } + } +} + +enum MatchType: String { + case bracket = "bracket" + case groupStage = "groupStage" + case loserBracket = "loserBracket" +} + +enum MatchFormat: Int, Hashable, Codable, CaseIterable { + case twoSets + case twoSetsSuperTie + case twoSetsOfFourGames + case nineGames + case superTie + case megaTie + + case twoSetsDecisivePoint + case twoSetsDecisivePointSuperTie + case twoSetsOfFourGamesDecisivePoint + case nineGamesDecisivePoint + + var weight: Int { + switch self { + case .twoSets, .twoSetsDecisivePoint: + return 0 + case .twoSetsSuperTie, .twoSetsDecisivePointSuperTie: + return 1 + case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint: + return 2 + case .nineGames, .nineGamesDecisivePoint: + return 3 + case .superTie: + return 4 + case .megaTie: + return 5 + } + } + + var rank: Int { + switch self { + case .twoSets: + return 0 + case .twoSetsDecisivePoint: + return 1 + case .twoSetsSuperTie: + return 2 + case .twoSetsDecisivePointSuperTie: + return 3 + case .twoSetsOfFourGames: + return 4 + case .twoSetsOfFourGamesDecisivePoint: + return 5 + case .nineGames: + return 6 + case .nineGamesDecisivePoint: + return 7 + case .superTie: + return 8 + case .megaTie: + return 9 + } + } + + static func defaultFormatForMatchType(_ matchType: MatchType) -> MatchFormat { + if UserDefaults.standard.object(forKey: matchType.rawValue + "MatchFormatPreference") == nil { + return .nineGamesDecisivePoint + } + return MatchFormat(rawValue: UserDefaults.standard.integer(forKey: matchType.rawValue + "MatchFormatPreference")) ?? .nineGamesDecisivePoint + } + + static var allCases: [MatchFormat] { + [.twoSets, .twoSetsDecisivePoint, .twoSetsSuperTie, .twoSetsDecisivePointSuperTie, .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .nineGames, .nineGamesDecisivePoint, .superTie, .megaTie] + } + + func winner(scoreTeamOne: Int, scoreTeamTwo: Int) -> TeamData { + scoreTeamOne >= scoreTeamTwo ? .one : .two + } + + func hasEnded(scoreTeamOne: Int, scoreTeamTwo: Int) -> Bool { + scoreTeamOne == setsToWin || scoreTeamTwo == setsToWin + } + + var canSuperTie: Bool { + switch self { + case .twoSetsSuperTie, .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .twoSetsDecisivePointSuperTie: + return true + default: + return false + } + } + + var estimatedDuration: Int { + if UserDefaults.standard.object(forKey: format) != nil { + return UserDefaults.standard.integer(forKey: format) + } else { + return defaultEstimatedDuration + } + } + + var defaultEstimatedDuration: Int { + switch self { + case .twoSets: + return 105 + case .twoSetsDecisivePoint: + return 90 + case .twoSetsSuperTie: + return 80 + case .twoSetsDecisivePointSuperTie: + return 75 + case .twoSetsOfFourGames: + return 60 + case .twoSetsOfFourGamesDecisivePoint: + return 50 + case .nineGames: + return 45 + case .nineGamesDecisivePoint: + return 35 + case .megaTie: + return 20 + case .superTie: + return 15 + } + } + + + var estimatedTimeWithBreak: Int { + estimatedDuration + breakTime.breakTime + } + + var breakTime: (breakTime: Int, matchCount: Int) { + switch self { + case .twoSets, .twoSetsDecisivePoint: + return (90, 1) + case .twoSetsSuperTie, .twoSetsDecisivePointSuperTie: + return (60, 1) + case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .nineGames, .nineGamesDecisivePoint: + return (30, 1) + case .superTie: + return (15, 3) + case .megaTie: + return (5, 1) + } + } + + func maximumMatchPerDay(for matchCount: Int) -> Int { + switch self { + case .twoSets, .twoSetsDecisivePoint: + return matchCount < 5 ? 2 : 0 + case .twoSetsSuperTie, .twoSetsDecisivePointSuperTie: + return matchCount < 6 ? 3 : 0 + case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .nineGames, .nineGamesDecisivePoint: + return matchCount < 7 ? 6 : 2 + case .superTie: + return 7 + case .megaTie: + return 7 + } + } + + var hasDecisivePoint: Bool { + switch self { + case .nineGamesDecisivePoint, .twoSetsDecisivePoint, .twoSetsOfFourGamesDecisivePoint, .twoSetsDecisivePointSuperTie: + return true + default: + return false + } + } + + func newSetFormat(setCount: Int) -> SetFormat { + if setCount == 2 && canSuperTie { + return .superTieBreak + } + return setFormat + } + + var suffix: String { + switch self { + case .twoSetsDecisivePoint, .twoSetsDecisivePointSuperTie, .twoSetsOfFourGamesDecisivePoint, .nineGamesDecisivePoint: + return " [Point Décisif]" + default: + return "" + } + } + + var longPrefix: String { + return "Format \(format) : " + } + + var shortPrefix: String { + return "\(format) : " + } + + var format: String { + switch self { + case .twoSets: + return "A1" + case .twoSetsSuperTie: + return "B1" + case .twoSetsOfFourGames: + return "C1" + case .nineGames: + return "D1" + case .superTie: + return "E" + case .megaTie: + return "F" + case .twoSetsDecisivePoint: + return "A2" + case .twoSetsDecisivePointSuperTie: + return "B2" + case .twoSetsOfFourGamesDecisivePoint: + return "C2" + case .nineGamesDecisivePoint: + return "D2" + } + } + + var longLabel: String { + switch self { + case .twoSets, .twoSetsDecisivePoint: + return "2 sets de 6" + case .twoSetsSuperTie, .twoSetsDecisivePointSuperTie: + return "2 sets de 6, supertie au 3ème" + case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint: + return "2 sets de 4, tiebreak à 4/4, supertie au 3ème" + case .nineGames, .nineGamesDecisivePoint: + return "9 jeux, tiebreak à 8/8" + case .superTie: + return "supertie de 10 points" + case .megaTie: + return "supertie de 15 points" + } + } + + var computedShortLabelWithoutPrefix: String { + longLabel + suffix + } + + var computedShortLabel: String { + shortPrefix + longLabel + suffix + } + + var computedLongLabel: String { + longPrefix + longLabel + suffix + } + + var setsToWin: Int { + switch self { + case .twoSets, .twoSetsSuperTie, .twoSetsOfFourGames, .twoSetsDecisivePoint, .twoSetsOfFourGamesDecisivePoint, .twoSetsDecisivePointSuperTie: + return 2 + case .nineGames, .nineGamesDecisivePoint, .superTie, .megaTie: + return 1 + } + } + + var setFormat: SetFormat { + switch self { + case .twoSets, .twoSetsSuperTie, .twoSetsDecisivePoint, .twoSetsDecisivePointSuperTie: + return .six + case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint: + return .four + case .nineGames, .nineGamesDecisivePoint: + return .nine + case .superTie: + return .superTieBreak + case .megaTie: + return .megaTieBreak + } + } +} + +enum Format: Int, Hashable, Codable { + case normal + case tiebreakSeven + case tiebreakTen + case tiebreakFiveTeen + + var localizedLabel: String { + switch self { + case .normal: + return "normal" + case .tiebreakSeven: + return "tie-break en 7" + case .tiebreakTen: + return "tie-break en 10" + case .tiebreakFiveTeen: + return "tie-break en 15" + } + } + var isTiebreak: Bool { + switch self { + case .normal: + return false + case .tiebreakSeven, .tiebreakTen, .tiebreakFiveTeen: + return true + } + } + + var scoreToWin: Int? { + switch self { + case .normal: + return nil + case .tiebreakSeven: + return 7 + case .tiebreakTen: + return 10 + case .tiebreakFiveTeen: + return 15 + } + } +} + +enum ActionType: Int, Identifiable { + case fault + case winner + + var id: Self { + self + } + + var localizedLabel: String { + switch self { + case .fault: + return "Faute" + case .winner: + return "Point" + } + } + /* + Break points won + Break points + Errors + Smash winners + Smashes + Winners + Volley winners + + Total points won + % points won + Total break points + Break points won + Gold points won + Gold points won returning + Gold points won with service + Most consecutive points won + Total set points + Set points won + Match points + */ +} + +enum EventType: Int, CaseIterable, Identifiable { + case approvedTournament + case animation + + var id: Self { + self + } + + var localizedLabel: String { + switch self { + case .approvedTournament: + return "Tournoi homologué" + case .animation: + return "Animation" + } + } +} + +enum EntriesSortingType: Int, Identifiable, CaseIterable, Hashable { + case rank = 1 + case inscriptionDate = 2 + + var id: Int { rawValue } + var localizedLabel: String { + switch self { + case .rank: + return "Rang" + case .inscriptionDate: + return "Date d'inscription" + } + } +} + +enum PlayersCountRange: Int, CaseIterable { + case N8 = 8 + case N12 = 12 + case N16 = 16 + case N20 = 20 + case N24 = 24 + case N28 = 28 + case N32 = 32 + + var localizedLabel: String { + switch self { + case .N8: + return "4 à 8" + case .N12: + return "9 à 12" + case .N16: + return "13 à 16" + case .N20: + return "17 à 20" + case .N24: + return "21 à 24" + case .N28: + return "25 à 28" + case .N32: + return "29 à 32" + } + } +} + +enum RoundLabel { + static let labels = ["Finale", "Demi-finale", "Quart de finale", "8ème de finale", "16ème de finale", "32ème de finale", "64ème de finale"] + static let shortLabels = ["Finale", "Demi", "Quart", "8ème", "16ème", "32ème", "64ème"] + static let colors = ["#d4afb9", "#d1cfe2", "#9cadce", "#7ec4cf", "#daeaf6", "#caffbf"] + static let freeMatchLabels = ["Finale", "Demi-finale", "Quart de finale", "8ème de finale", "16ème de finale", "32ème de finale", "Poule"] + + static func loserBrackets(index: Int) -> [String] { + switch index { + case 1: + return ["#3/#4"] + case 2: + return ["#5/#6", "#7/#8"] + default: + return ["#9/#10", "#11/#12", "#13/#14", "#15/#16", "#17/#18", "#19/#20", "#21/#22", "#23/#24", "#25/#26", "#27/#28", "#29/#30", "#31/#32"] + } + } +} + diff --git a/PadelClub/Views/Event/EventCreationView.swift b/PadelClub/Views/Event/EventCreationView.swift new file mode 100644 index 0000000..1d99939 --- /dev/null +++ b/PadelClub/Views/Event/EventCreationView.swift @@ -0,0 +1,18 @@ +// +// EventCreationView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 22/03/2024. +// + +import SwiftUI + +struct EventCreationView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + EventCreationView() +} diff --git a/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift b/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift index 12d91c9..9a40d16 100644 --- a/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift @@ -8,6 +8,8 @@ import SwiftUI struct EmptyActivityView: View { + @State private var presentTournamentCreation: Bool = false + var body: some View { NavigationStack { List { @@ -15,8 +17,7 @@ struct EmptyActivityView: View { Section { RowButtonView(title: "Créer votre premier événement", action: { - let tournament = Tournament(name: "P100", club_id: "", category: 0, playerCount: 24) - try? DataStore.shared.tournaments.append(contentOfs: [tournament]) + presentTournamentCreation = true }) } @@ -26,6 +27,9 @@ struct EmptyActivityView: View { }) } } + .sheet(isPresented: $presentTournamentCreation) { + EventCreationView() + } } } }