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.
2062 lines
58 KiB
2062 lines
58 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.localizedFederalAgeLabel()
|
|
}
|
|
|
|
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.localizedFederalAgeLabel(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 localizedFederalAgeLabel(_ 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 displayStyle == .short ? "" : "Senior"
|
|
case .a45:
|
|
return "+45 ans"
|
|
case .a55:
|
|
return "+55 ans"
|
|
}
|
|
}
|
|
|
|
var tournamentDescriptionLabel: String {
|
|
return localizedFederalAgeLabel()
|
|
}
|
|
|
|
func isAgeValid(age: Int?) -> Bool {
|
|
guard let age else { return true }
|
|
switch self {
|
|
case .unlisted:
|
|
return true
|
|
case .a11_12:
|
|
return age < 13
|
|
case .a13_14:
|
|
return age < 15
|
|
case .a15_16:
|
|
return age < 17
|
|
case .a17_18:
|
|
return age < 19
|
|
case .senior:
|
|
return age >= 11
|
|
case .a45:
|
|
return age >= 45
|
|
case .a55:
|
|
return age >= 55
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
case championship = 1
|
|
|
|
init?(rawValue: Int?) {
|
|
guard let value = rawValue else { return nil }
|
|
self.init(rawValue: value)
|
|
}
|
|
|
|
static var assimilationAllCases: [TournamentLevel] = {
|
|
return [.p25, .p100, .p250, .p500, .p1000, .p1500, .p2000]
|
|
}()
|
|
|
|
var entryFee: Double? {
|
|
switch self {
|
|
case .unlisted, .championship:
|
|
return nil
|
|
case .p25:
|
|
return 15
|
|
default:
|
|
return 20
|
|
}
|
|
}
|
|
|
|
func isAnimation() -> Bool {
|
|
switch self {
|
|
case .unlisted:
|
|
return true
|
|
case .championship:
|
|
return false
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
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 wildcardArePossible() -> Bool {
|
|
switch self {
|
|
case .p500, .p1000, .p1500, .p2000:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
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
|
|
case .championship:
|
|
return 8
|
|
}
|
|
}
|
|
|
|
func localizedLevelLabel(_ displayStyle: DisplayStyle = .wide) -> String {
|
|
if self == .unlisted {
|
|
if DeviceHelper.isBigScreen() {
|
|
return "Animation"
|
|
} else {
|
|
return displayStyle == .title ? "Animation" : "Anim."
|
|
}
|
|
}
|
|
if self == .championship {
|
|
if DeviceHelper.isBigScreen() {
|
|
return "Championnat"
|
|
} else {
|
|
return displayStyle == .title ? "Championnat" : "CHPT"
|
|
}
|
|
}
|
|
|
|
return String(describing: self).capitalized
|
|
}
|
|
|
|
var coachingIsAuthorized: Bool {
|
|
switch self {
|
|
case .p500, .p1000, .p1500, .p2000, .championship:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func points(for rank: Int, count: Int) -> Int {
|
|
if self == .unlisted { return 0 }
|
|
if self == .championship { 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, .championship:
|
|
return []
|
|
case .p25:
|
|
switch count {
|
|
case 9...12:
|
|
return [17, 15, 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, .championship:
|
|
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, .championship:
|
|
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
|
|
}
|
|
}
|
|
|
|
func localizedBranchLabel() -> String {
|
|
switch self {
|
|
case .one:
|
|
return "Branche du haut"
|
|
case .two:
|
|
return "Branche du bas"
|
|
}
|
|
}
|
|
}
|
|
|
|
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, Identifiable {
|
|
var id: Int { self.rawValue }
|
|
case twoSets
|
|
case twoSetsSuperTie
|
|
case twoSetsOfFourGames
|
|
case nineGames
|
|
case superTie
|
|
case megaTie
|
|
|
|
case twoSetsDecisivePoint
|
|
case twoSetsDecisivePointSuperTie
|
|
case twoSetsOfFourGamesDecisivePoint
|
|
case nineGamesDecisivePoint
|
|
|
|
case twoSetsOfSuperTie
|
|
case singleSet
|
|
case singleSetDecisivePoint
|
|
case singleSetOfFourGames
|
|
case singleSetOfFourGamesDecisivePoint
|
|
|
|
|
|
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
|
|
case .twoSetsOfSuperTie:
|
|
return 6
|
|
case .singleSet, .singleSetDecisivePoint:
|
|
return 7
|
|
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
|
|
return 8
|
|
}
|
|
}
|
|
|
|
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
|
|
case .twoSetsOfSuperTie:
|
|
return 6
|
|
case .singleSet, .singleSetDecisivePoint:
|
|
return 7
|
|
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
|
|
return 8
|
|
}
|
|
}
|
|
|
|
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, .twoSetsOfSuperTie, .singleSet, .singleSetDecisivePoint, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint]
|
|
}()
|
|
|
|
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, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
|
|
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 += " de pause après \(breakTime.matchCount) match"
|
|
label += breakTime.matchCount.pluralSuffix
|
|
} else {
|
|
label += " de pause"
|
|
}
|
|
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 20
|
|
case .superTie:
|
|
return 15
|
|
case .twoSetsOfSuperTie:
|
|
return 25
|
|
case .singleSet:
|
|
return 30
|
|
case .singleSetDecisivePoint:
|
|
return 25
|
|
case .singleSetOfFourGames:
|
|
return 15
|
|
case .singleSetOfFourGamesDecisivePoint:
|
|
return 10
|
|
}
|
|
}
|
|
|
|
|
|
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)
|
|
default:
|
|
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
|
|
default:
|
|
return 10
|
|
}
|
|
}
|
|
|
|
var hasDecisivePoint: Bool {
|
|
switch self {
|
|
case .nineGamesDecisivePoint, .twoSetsDecisivePoint, .twoSetsOfFourGamesDecisivePoint, .twoSetsDecisivePointSuperTie, .singleSetDecisivePoint, .singleSetOfFourGamesDecisivePoint:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func newSetFormat(setCount: Int) -> SetFormat {
|
|
if setCount == 2 && canSuperTie {
|
|
return .superTieBreak
|
|
}
|
|
return setFormat
|
|
}
|
|
|
|
func formatTitle(_ displayStyle: DisplayStyle = .wide) -> String {
|
|
switch displayStyle {
|
|
case .short:
|
|
return ["Format ", shortFormat].joined()
|
|
default:
|
|
return ["Format ", shortFormat, suffix].joined()
|
|
}
|
|
}
|
|
|
|
var suffix: String {
|
|
switch self {
|
|
case .twoSetsDecisivePoint, .twoSetsDecisivePointSuperTie, .twoSetsOfFourGamesDecisivePoint, .nineGamesDecisivePoint, .singleSetDecisivePoint:
|
|
return " [Point Décisif]"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
var longPrefix: String {
|
|
return "Format \(format) : "
|
|
}
|
|
|
|
var shortPrefix: String {
|
|
return "\(format) : "
|
|
}
|
|
|
|
var isFederal: Bool {
|
|
switch self {
|
|
case .megaTie, .twoSetsOfSuperTie, .singleSet, .singleSetDecisivePoint, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
var format: String {
|
|
shortFormat + (isFederal ? "" : " (non officiel)")
|
|
}
|
|
var shortFormat: String {
|
|
switch self {
|
|
case .twoSets:
|
|
return "A1"
|
|
case .twoSetsSuperTie:
|
|
return "B1"
|
|
case .twoSetsOfFourGames:
|
|
return "C1"
|
|
case .nineGames:
|
|
return "D1"
|
|
case .superTie:
|
|
return "E"
|
|
case .twoSetsOfSuperTie:
|
|
return "G"
|
|
case .megaTie:
|
|
return "F"
|
|
case .singleSet:
|
|
return "H1"
|
|
case .singleSetDecisivePoint:
|
|
return "H2"
|
|
case .twoSetsDecisivePoint:
|
|
return "A2"
|
|
case .twoSetsDecisivePointSuperTie:
|
|
return "B2"
|
|
case .twoSetsOfFourGamesDecisivePoint:
|
|
return "C2"
|
|
case .nineGamesDecisivePoint:
|
|
return "D2"
|
|
case .singleSetOfFourGames:
|
|
return "I1"
|
|
case .singleSetOfFourGamesDecisivePoint:
|
|
return "I2"
|
|
}
|
|
}
|
|
|
|
var longLabel: String {
|
|
switch self {
|
|
case .singleSet, .singleSetDecisivePoint:
|
|
return "1 set de 6"
|
|
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 .twoSetsOfSuperTie:
|
|
return "2 sets de supertie de 10 points"
|
|
case .superTie:
|
|
return "supertie de 10 points"
|
|
case .megaTie:
|
|
return "supertie de 15 points"
|
|
case .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
|
|
return "1 set de 4 jeux, tiebreak à 4/4"
|
|
}
|
|
}
|
|
|
|
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, .twoSetsOfSuperTie:
|
|
return 2
|
|
case .nineGames, .nineGamesDecisivePoint, .superTie, .megaTie, .singleSet, .singleSetDecisivePoint, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
|
|
return 1
|
|
}
|
|
}
|
|
|
|
var setFormat: SetFormat {
|
|
switch self {
|
|
case .twoSets, .twoSetsSuperTie, .twoSetsDecisivePoint, .twoSetsDecisivePointSuperTie, .singleSet, .singleSetDecisivePoint:
|
|
return .six
|
|
case .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint:
|
|
return .four
|
|
case .nineGames, .nineGamesDecisivePoint:
|
|
return .nine
|
|
case .superTie, .twoSetsOfSuperTie:
|
|
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", "#336633", "#DD6600", "#EE6633", "#EE6633", "#EE6633"]
|
|
|
|
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 {
|
|
if teams == 0 { return 0 }
|
|
return 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"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum PadelTournamentStructurePreset: Int, Identifiable, CaseIterable {
|
|
var id: Int { self.rawValue }
|
|
|
|
case manual
|
|
case doubleGroupStage
|
|
case federalStructure_8
|
|
case federalStructure_12
|
|
case federalStructure_16
|
|
case federalStructure_20
|
|
case federalStructure_24
|
|
case federalStructure_32
|
|
case federalStructure_48
|
|
case federalStructure_64
|
|
|
|
// Maximum qualified pairs based on the structure preset
|
|
func tableDimension() -> Int {
|
|
switch self {
|
|
case .federalStructure_8:
|
|
return 8
|
|
case .federalStructure_12:
|
|
return 12
|
|
case .federalStructure_16:
|
|
return 16
|
|
case .federalStructure_20:
|
|
return 20
|
|
case .federalStructure_24:
|
|
return 24
|
|
case .federalStructure_32:
|
|
return 32
|
|
case .federalStructure_48:
|
|
return 48
|
|
case .federalStructure_64:
|
|
return 64
|
|
case .manual:
|
|
return 24
|
|
case .doubleGroupStage:
|
|
return 9
|
|
}
|
|
}
|
|
|
|
// Wildcards allowed in the Qualifiers
|
|
func wildcardBrackets() -> Int {
|
|
switch self {
|
|
case .federalStructure_8:
|
|
return 0
|
|
case .federalStructure_12:
|
|
return 1
|
|
case .federalStructure_16, .federalStructure_20, .federalStructure_24, .federalStructure_32:
|
|
return 2
|
|
case .federalStructure_48, .federalStructure_64:
|
|
return 4
|
|
case .manual, .doubleGroupStage:
|
|
return 0
|
|
}
|
|
}
|
|
// Wildcards allowed in the Qualifiers
|
|
func wildcardQualifiers() -> Int {
|
|
switch self {
|
|
case .federalStructure_8:
|
|
return 0
|
|
case .federalStructure_12, .federalStructure_16:
|
|
return 1
|
|
case .federalStructure_20, .federalStructure_24:
|
|
return 2
|
|
case .federalStructure_32:
|
|
return 4
|
|
case .federalStructure_48:
|
|
return 6
|
|
case .federalStructure_64:
|
|
return 8
|
|
case .manual, .doubleGroupStage:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// Number of teams admitted to the Qualifiers
|
|
func teamsInQualifiers() -> Int {
|
|
switch self {
|
|
case .federalStructure_8:
|
|
return 8
|
|
case .federalStructure_12:
|
|
return 12
|
|
case .federalStructure_16:
|
|
return 16
|
|
case .federalStructure_20:
|
|
return 20
|
|
case .federalStructure_24:
|
|
return 24
|
|
case .federalStructure_32:
|
|
return 32
|
|
case .federalStructure_48:
|
|
return 48
|
|
case .federalStructure_64:
|
|
return 64
|
|
case .manual, .doubleGroupStage:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// Maximum teams that can qualify from the Qualifiers to the Final Table
|
|
func maxTeamsFromQualifiers() -> Int {
|
|
switch self {
|
|
case .federalStructure_8, .federalStructure_12:
|
|
return 2
|
|
case .federalStructure_16, .federalStructure_20, .federalStructure_24:
|
|
return 4
|
|
case .federalStructure_32, .federalStructure_48, .federalStructure_64:
|
|
return 8
|
|
case .manual, .doubleGroupStage:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func localizedStructurePresetTitle() -> String {
|
|
switch self {
|
|
case .manual:
|
|
return "Défaut"
|
|
case .doubleGroupStage:
|
|
return "2 phases de poules"
|
|
case .federalStructure_8:
|
|
return "Structure fédérale 8"
|
|
case .federalStructure_12:
|
|
return "Structure fédérale 12"
|
|
case .federalStructure_16:
|
|
return "Structure fédérale 16"
|
|
case .federalStructure_20:
|
|
return "Structure fédérale 20"
|
|
case .federalStructure_24:
|
|
return "Structure fédérale 24"
|
|
case .federalStructure_32:
|
|
return "Structure fédérale 32"
|
|
case .federalStructure_48:
|
|
return "Structure fédérale 48"
|
|
case .federalStructure_64:
|
|
return "Structure fédérale 64"
|
|
}
|
|
}
|
|
|
|
func localizedDescriptionStructurePresetTitle() -> String {
|
|
switch self {
|
|
case .manual:
|
|
return "24 équipes, 4 poules de 4, 1 qualifié par poule"
|
|
case .doubleGroupStage:
|
|
return "Poules qui enchaînent sur une autre phase de poules: les premiers de chaque se retrouvent ensemble, puis les deuxièmes, etc."
|
|
case .federalStructure_8:
|
|
return "Tableau final à 8 paires, dont 2 qualifiées sortant de qualifications à 8 paires maximum. Aucune wildcard."
|
|
case .federalStructure_12, .federalStructure_16, .federalStructure_20, .federalStructure_24, .federalStructure_32, .federalStructure_48, .federalStructure_64:
|
|
return "Tableau final à \(tableDimension()) paires, dont \(maxTeamsFromQualifiers()) qualifiées sortant de qualifications à \(teamsInQualifiers()) paires maximum. \(wildcardBrackets()) wildcard\(wildcardBrackets().pluralSuffix) en tableau et \(wildcardQualifiers()) wildcard\(wildcardQualifiers().pluralSuffix) en qualifications."
|
|
}
|
|
}
|
|
|
|
func groupStageCount() -> Int {
|
|
switch self {
|
|
case .manual:
|
|
4
|
|
case .doubleGroupStage:
|
|
3
|
|
case .federalStructure_8:
|
|
2
|
|
case .federalStructure_12:
|
|
2
|
|
case .federalStructure_16:
|
|
4
|
|
case .federalStructure_20:
|
|
4
|
|
case .federalStructure_24:
|
|
4
|
|
case .federalStructure_32:
|
|
8
|
|
case .federalStructure_48:
|
|
8
|
|
case .federalStructure_64:
|
|
8
|
|
}
|
|
}
|
|
|
|
func teamsPerGroupStage() -> Int {
|
|
switch self {
|
|
case .manual:
|
|
4
|
|
case .doubleGroupStage:
|
|
3
|
|
case .federalStructure_8:
|
|
4
|
|
case .federalStructure_12:
|
|
6
|
|
case .federalStructure_16:
|
|
4
|
|
case .federalStructure_20:
|
|
5
|
|
case .federalStructure_24:
|
|
6
|
|
case .federalStructure_32:
|
|
4
|
|
case .federalStructure_48:
|
|
6
|
|
case .federalStructure_64:
|
|
8
|
|
}
|
|
}
|
|
|
|
func qualifiedPerGroupStage() -> Int {
|
|
switch self {
|
|
case .doubleGroupStage:
|
|
0
|
|
default:
|
|
1
|
|
}
|
|
}
|
|
|
|
func hasWildcards() -> Bool {
|
|
wildcardBrackets() > 0 || wildcardQualifiers() > 0
|
|
}
|
|
|
|
func isFederalPreset() -> Bool {
|
|
switch self {
|
|
case .manual:
|
|
return false
|
|
case .doubleGroupStage:
|
|
return false
|
|
case .federalStructure_8, .federalStructure_12, .federalStructure_16, .federalStructure_20, .federalStructure_24, .federalStructure_32, .federalStructure_48, .federalStructure_64:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
enum TournamentDeadlineType: String, CaseIterable {
|
|
case inscription = "Inscription"
|
|
case broadcastList = "Publication de la liste"
|
|
case wildcardRequest = "Demande de WC"
|
|
case wildcardLicensePurchase = "Prise de licence des WC"
|
|
case definitiveBroadcastList = "Publication définitive"
|
|
|
|
func daysOffset(level: TournamentLevel) -> Int {
|
|
if level == .p500 {
|
|
switch self {
|
|
case .inscription:
|
|
return -6
|
|
case .broadcastList:
|
|
return -6
|
|
case .wildcardRequest:
|
|
return -4
|
|
case .wildcardLicensePurchase, .definitiveBroadcastList:
|
|
return -4
|
|
}
|
|
} else {
|
|
switch self {
|
|
case .inscription:
|
|
return -13
|
|
case .broadcastList:
|
|
return -12
|
|
case .wildcardRequest:
|
|
return -9
|
|
case .wildcardLicensePurchase, .definitiveBroadcastList:
|
|
return -8
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
var timeOffset: DateComponents {
|
|
switch self {
|
|
case .broadcastList, .definitiveBroadcastList:
|
|
return DateComponents(hour: 12)
|
|
case .inscription, .wildcardRequest, .wildcardLicensePurchase:
|
|
return DateComponents(minute: -1)
|
|
}
|
|
}
|
|
}
|
|
|