You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
PadelClub/PadelClub/Utils/PadelRule.swift

1639 lines
46 KiB

//
// PadelRule.swift
// Padel Tournament
//
// Created by razmig on 27/02/2023.
//
import Foundation
import LeStorage
enum RankSource: Hashable {
case national
case ligue
case club(assimilation: Bool)
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .national:
return "Classement National"
case .ligue:
return "Classement Ligue"
case .club:
return "Classement Club"
}
}
}
protocol TournamentBuildHolder: Identifiable {
var id: String { get }
var category: TournamentCategory { get }
var level: TournamentLevel { get }
var age: FederalTournamentAge { get }
func buildHolderTitle(_ displayStyle: DisplayStyle) -> String
}
struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable {
var uniqueId: String = Store.randomId()
var id: String { uniqueId }
let category: TournamentCategory
let level: TournamentLevel
let age: FederalTournamentAge
// var japIdentifier: Int? = nil
// var japFirstName: String? = nil
// var japLastName: String? = nil
func buildHolderTitle(_ displayStyle: DisplayStyle) -> String {
computedLabel(displayStyle)
}
var identifier: String {
level.localizedLevelLabel()+":"+category.localizedLabel()+":"+age.localizedLabel()
}
func computedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if age == .senior { return localizedLabel(displayStyle) }
return localizedLabel(displayStyle) + " " + localizedAge(displayStyle)
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle)
}
func localizedTitle(_ displayStyle: DisplayStyle = .wide) -> String {
level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle)
}
func localizedAge(_ displayStyle: DisplayStyle = .wide) -> String {
age.localizedLabel(displayStyle)
}
}
extension TournamentBuild {
init?(category: String, level: String, age: FederalTournamentAge = .senior) {
guard let levelFound = TournamentLevel.allCases.first(where: { $0.localizedLevelLabel() == 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 }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> 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
}
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> 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 unlisted = 0
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
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
func computedBirthYear() -> (Int?, Int?) {
let year = Calendar.current.getSportAge()
switch self {
case .unlisted:
return (nil, nil)
case .a11_12:
return (year - 12, year - 11)
case .a13_14:
return (year - 14, year - 13)
case .a15_16:
return (year - 16, year - 15)
case .a17_18:
return (year - 18, year - 17)
case .senior:
return (nil, year - 19)
case .a45:
return (nil, year - 45)
case .a55:
return (nil, year - 55)
}
}
var importingRawValue: String {
switch self {
case .unlisted:
return "Animation"
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"
}
}
static func mostRecent(inTournaments tournaments: [Tournament]) -> Self {
return tournaments.first?.federalTournamentAge ?? .senior
}
static func mostUsed(inTournaments 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(inTournaments: tournaments)
}
}
var id: Int { self.rawValue }
var order: Int {
switch self {
case .unlisted:
return 7
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
}
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .unlisted:
return displayStyle == .title ? "Aucune" : ""
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 {
return localizedLabel()
}
}
enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
case unlisted = 0
case p25 = 25
case p100 = 100
case p250 = 250
case p500 = 500
case p1000 = 1000
case p1500 = 1500
case p2000 = 2000
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
func searchRawValue() -> String {
String(describing: self)
}
func pointsRange(first: Int, last: Int, teamsCount: Int) -> String {
let range = [points(for: first - 1, count: teamsCount),
points(for: last - 1, count: teamsCount)]
return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts"
}
func hideWeight() -> Bool {
switch self {
case .unlisted:
return true
default:
return false
}
}
func shouldShareTeams() -> Bool {
switch self {
case .p500, .p1000, .p1500, .p2000:
return true
default:
return false
}
}
static func mostRecent(inTournaments tournaments: [Tournament]) -> Self {
return tournaments.first?.tournamentLevel ?? .p100
}
static func mostUsed(inTournaments 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(inTournaments: tournaments)
}
}
var id: Int { self.rawValue }
func minimumPlayerRank(category: TournamentCategory, ageCategory: FederalTournamentAge) -> Int {
switch self {
case .p25:
switch ageCategory {
case .senior, .a45, .a55:
return category == .men ? 20000 : 1000
default:
return 0
}
case .p100:
switch ageCategory {
case .senior, .a45, .a55:
return category == .men ? 2000 : 300
default:
return 0
}
case .p250:
switch ageCategory {
case .senior, .a45, .a55:
if category == .mix { return 0 }
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 .twoSetsDecisivePointSuperTie
} else if roundIndex == 1 { //demi-finale
return .twoSetsDecisivePointSuperTie
} else {
return .nineGamesDecisivePoint
}
case .p1000:
if roundIndex <= 3 { //demi / finale / quart / 8eme
return .twoSetsDecisivePoint
} else {
return .twoSetsDecisivePointSuperTie
}
case .p1500, .p2000:
if roundIndex <= 3 { //demi / finale / quart / 8eme
return .twoSetsDecisivePoint
} else {
return .twoSetsSuperTie
}
default:
return .superTie
}
}
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
case .p1000:
return .nineGamesDecisivePoint
case .p1500, .p2000:
return .twoSetsSuperTie
default:
return .nineGamesDecisivePoint
}
}
var defaultTeamSortingType: TeamSortingType {
switch self {
case .p25, .p100, .p250:
return .inscriptionDate
default:
return .rank
}
}
var order: Int {
switch self {
case .unlisted:
return 7
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
}
}
func localizedLevelLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if self == .unlisted { return displayStyle == .title ? "Animation" : "Anim." }
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 {
if self == .unlisted { return 0 }
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 .unlisted:
return []
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, .unlisted:
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, .unlisted:
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
case unlisted
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
func mandatoryPlayerType() -> [Int] {
switch self {
case .unlisted:
return []
case .mix:
return [0, 1]
case .women:
return [0, 0]
case .men:
return [1, 1]
}
}
var localizedPlayerLabel: String {
switch self {
case .women:
return "joueuse"
default:
return "joueur"
}
}
var showFemaleInMaleAssimilation: Bool {
switch self {
case .men:
return true
default:
return false
}
}
static func femaleInMaleAssimilationAddition(_ rank: Int) -> Int {
switch rank {
case 1...10: return 400
case 11...30: return 1000
case 31...60: return 2000
case 61...100: return 3500
case 101...200: return 10000
case 201...500: return 15000
case 501...1000: return 25000
case 1001...2000: return 35000
case 2001...3000: return 45000
default:
return 50000
}
}
static func mostRecent(inTournaments tournaments: [Tournament]) -> Self {
return tournaments.first?.tournamentCategory ?? .men
}
static func mostUsed(inTournaments 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(inTournaments: tournaments)
}
}
var id: Int { self.rawValue }
var order: Int {
switch self {
case .unlisted:
return 0
case .men:
return 1
case .women:
return 2
case .mix:
return 3
}
}
var buildLabel: String {
switch self {
case .unlisted:
return ""
case .men:
return "H"
case .women:
return "D"
case .mix:
return "M"
}
}
var requestLabel: String {
switch self {
case .unlisted:
return ""
case .men:
return "DM"
case .women:
return "DD"
case .mix:
return "DX"
}
}
var importingRawValue: String {
switch self {
case .unlisted:
return "messieurs"
case .men:
return "messieurs"
case .women:
return "dames"
case .mix:
return "mixte"
}
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .unlisted:
return displayStyle == .title ? "Aucune" : ""
case .men:
switch displayStyle {
case .title:
return "Hommes"
case .wide:
return "Hommes"
case .short:
return "H"
}
case .women:
switch displayStyle {
case .title:
return "Dames"
case .wide:
return "Dames"
case .short:
return "D"
}
case .mix:
switch displayStyle {
case .title:
return "Mixte"
case .wide:
return "Mixte"
case .short:
return "MX"
}
}
}
var playerFilterOption: PlayerFilterOption {
switch self {
case .men, .unlisted:
return .all
case .women:
return .female
case .mix:
return .all
}
}
}
enum GroupStageOrderingMode: Int, Hashable, Codable, CaseIterable, Identifiable {
case random
case snake
case swiss
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
var id: Int { self.rawValue }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> 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 }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .classic:
return "Classique"
case .doubleBrackets:
return "Double Poules"
}
}
}
enum TeamPosition: Int, Identifiable, Hashable, Codable, CaseIterable {
case one
case two
var id: Int { self.rawValue }
var otherTeam: TeamPosition {
switch self {
case .one:
return .two
case .two:
return .one
}
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
var shortName: String {
switch self {
case .one:
return "#1"
case .two:
return "#2"
}
}
switch displayStyle {
case .wide, .title:
return "Équipe " + shortName
case .short:
return shortName
}
}
}
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) -> TeamPosition {
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 .tiebreakFifteen
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
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
func defaultWalkOutScore(_ asWalkOutTeam: Bool) -> [Int] {
Array(repeating: asWalkOutTeam ? 0 : setFormat.scoreToWin, count: setsToWin)
}
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 0
case .twoSetsSuperTie:
return 1
case .twoSetsDecisivePointSuperTie:
return 1
case .twoSetsOfFourGames:
return 2
case .twoSetsOfFourGamesDecisivePoint:
return 2
case .nineGames:
return 3
case .nineGamesDecisivePoint:
return 3
case .superTie:
return 4
case .megaTie:
return 5
}
}
static func defaultFormatForMatchType(_ matchType: MatchType) -> MatchFormat {
switch matchType {
case .bracket:
DataStore.shared.user.bracketMatchFormatPreference ?? .nineGamesDecisivePoint
case .groupStage:
DataStore.shared.user.groupStageMatchFormatPreference ?? .nineGamesDecisivePoint
case .loserBracket:
DataStore.shared.user.loserBracketMatchFormatPreference ?? .nineGamesDecisivePoint
}
}
static var allCases: [MatchFormat] {
[.twoSets, .twoSetsDecisivePoint, .twoSetsSuperTie, .twoSetsDecisivePointSuperTie, .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .nineGames, .nineGamesDecisivePoint, .superTie, .megaTie]
}
func winner(scoreTeamOne: Int, scoreTeamTwo: Int) -> TeamPosition {
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
}
}
func getEstimatedDuration(_ additionalDuration: Int = 0) -> Int {
estimatedDuration + additionalDuration
}
private var estimatedDuration: Int {
DataStore.shared.user.matchFormatsDefaultDuration?[self] ?? defaultEstimatedDuration
}
func formattedEstimatedDuration(_ additionalDuration: Int = 0) -> String {
Duration.seconds((estimatedDuration + additionalDuration) * 60).formatted(.units(allowed: [.minutes]))
}
func formattedEstimatedBreakDuration() -> String {
var label = Duration.seconds(breakTime.breakTime * 60).formatted(.units(allowed: [.minutes]))
if breakTime.matchCount > 1 {
label += " après \(breakTime.matchCount) match"
label += breakTime.matchCount.pluralSuffix
}
return label
}
var defaultEstimatedDuration: Int {
switch self {
case .twoSets:
return 105
case .twoSetsDecisivePoint:
return 90
case .twoSetsSuperTie:
return 80
case .twoSetsDecisivePointSuperTie:
return 70
case .twoSetsOfFourGames:
return 60
case .twoSetsOfFourGamesDecisivePoint:
return 50
case .nineGames:
return 45
case .nineGamesDecisivePoint:
return 40
case .megaTie:
return 30
case .superTie:
return 25
}
}
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 tiebreakFifteen
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .normal:
return "normal"
case .tiebreakSeven:
return "tie-break en 7"
case .tiebreakTen:
return "tie-break en 10"
case .tiebreakFifteen:
return "tie-break en 15"
}
}
var isTiebreak: Bool {
switch self {
case .normal:
return false
case .tiebreakSeven, .tiebreakTen, .tiebreakFifteen:
return true
}
}
var scoreToWin: Int? {
switch self {
case .normal:
return nil
case .tiebreakSeven:
return 7
case .tiebreakTen:
return 10
case .tiebreakFifteen:
return 15
}
}
}
enum ActionType: Int, Identifiable {
case fault
case winner
var id: Self {
self
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> 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 friendlyTournament
case simulation
case animation
var id: Self {
self
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .approvedTournament:
return "Tournoi homologué"
case .friendlyTournament:
return "Tournoi amical"
case .simulation:
return "Simulation"
case .animation:
return "Animation"
}
}
}
enum TeamSortingType: Int, Identifiable, CaseIterable, Hashable, Codable {
case rank = 1
case inscriptionDate = 2
var id: Int { rawValue }
init?(rawValue: Int?) {
guard let value = rawValue else { return nil }
self.init(rawValue: value)
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> 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
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> 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 RoundRule {
static let colors = ["#99ff99", "#66ff66", "#33cc33", "#009900", "#006600", "#006600", "#006600", "#006600", "#006600", "#006600"]
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"]
}
}
static func teamsInFirstRound(forTeams teams: Int) -> Int {
Int(pow(2.0, ceil(log2(Double(teams)))))
}
static func numberOfMatches(forTeams teams: Int) -> Int {
teamsInFirstRound(forTeams: teams) - 1
}
static func numberOfRounds(forTeams teams: Int) -> Int {
Int(log2(Double(teamsInFirstRound(forTeams: teams))))
}
static func matchIndex(fromRoundIndex roundIndex: Int) -> Int {
guard roundIndex >= 0 else {
return -1 // Invalid round index
}
return (1 << roundIndex) - 1
}
static func matchIndex(fromBracketPosition: Int) -> Int {
roundIndex(fromMatchIndex: fromBracketPosition / 2) + fromBracketPosition%2
}
static func roundIndex(fromMatchIndex matchIndex: Int) -> Int {
Int(log2(Double(matchIndex + 1)))
}
static func numberOfMatches(forRoundIndex roundIndex: Int) -> Int {
Int(pow(2.0, Double(roundIndex)))
}
static func matchIndexWithinRound(fromMatchIndex matchIndex: Int) -> Int {
let roundIndex = roundIndex(fromMatchIndex: matchIndex)
let matchIndexWithinRound = matchIndex - (Int(pow(2.0, Double(roundIndex))) - 1)
return matchIndexWithinRound
}
static func roundName(fromMatchIndex matchIndex: Int) -> String {
let roundIndex = roundIndex(fromMatchIndex: matchIndex)
return roundName(fromRoundIndex: roundIndex)
}
static func roundName(fromRoundIndex roundIndex: Int, displayStyle: DisplayStyle = .wide) -> String {
switch roundIndex {
case 0:
return "Finale"
case 1:
if displayStyle == .short {
return "Demi"
}
return "Demi-finale"
case 2:
if displayStyle == .short {
return "Quart"
}
return "Quart de finale"
default:
return "\(Int(pow(2.0, Double(roundIndex))))ème"
}
}
}
enum AnimationType: Int, CaseIterable, Hashable, Identifiable {
case playerAnimation
case upAndDown
case brawl
var id: Int { rawValue }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .playerAnimation:
return "Par joueur"
case .upAndDown:
return "Montante / Descendante"
case .brawl:
return "Brawl"
}
}
var descriptionLabel: String {
switch self {
case .playerAnimation:
return "Chaque joueur joue avec quelqu'un de différent à chaque rotation (8 à 12 joueurs)"
case .upAndDown:
return "Les gagnants montent sur le terrain d'à côté, les perdants descendent"
case .brawl:
return "A chaque rotation, les gagnants de la rotation précédente se jouent entre eux"
}
}
}