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.
738 lines
25 KiB
738 lines
25 KiB
//
|
|
// Match_v2.swift
|
|
// Padel Tournament
|
|
//
|
|
// Created by razmig on 10/03/2024.
|
|
//
|
|
|
|
import Foundation
|
|
import LeStorage
|
|
|
|
@Observable
|
|
class Match: ModelObject, Storable {
|
|
static func resourceName() -> String { "matches" }
|
|
static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
|
|
|
|
static func setServerTitle(upperRound: Round, matchIndex: Int) -> String {
|
|
if upperRound.index == 0 { return upperRound.roundTitle() }
|
|
return upperRound.roundTitle() + " #" + (matchIndex + 1).formatted()
|
|
}
|
|
|
|
var byeState: Bool = false
|
|
|
|
var id: String = Store.randomId()
|
|
var round: String?
|
|
var groupStage: String?
|
|
var startDate: Date?
|
|
var endDate: Date?
|
|
var index: Int
|
|
private var format: MatchFormat?
|
|
//var court: String?
|
|
var servingTeamId: String?
|
|
var winningTeamId: String?
|
|
var losingTeamId: String?
|
|
//var broadcasted: Bool
|
|
var name: String?
|
|
//var order: Int
|
|
var disabled: Bool = false
|
|
private(set) var courtIndex: Int?
|
|
|
|
internal init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, matchFormat: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, name: String? = nil, disabled: Bool = false, courtIndex: Int? = nil) {
|
|
self.round = round
|
|
self.groupStage = groupStage
|
|
self.startDate = startDate
|
|
self.endDate = endDate
|
|
self.index = index
|
|
self.format = matchFormat
|
|
//self.court = court
|
|
self.servingTeamId = servingTeamId
|
|
self.winningTeamId = winningTeamId
|
|
self.losingTeamId = losingTeamId
|
|
self.disabled = disabled
|
|
self.name = name
|
|
self.courtIndex = courtIndex
|
|
// self.broadcasted = broadcasted
|
|
// self.order = order
|
|
}
|
|
|
|
func indexInRound() -> Int {
|
|
if groupStage != nil {
|
|
return index
|
|
} else if let index = roundObject?.playedMatches().sorted(by: \.index).firstIndex(where: { $0.id == id }) {
|
|
return index
|
|
}
|
|
return RoundRule.matchIndexWithinRound(fromMatchIndex: index)
|
|
}
|
|
|
|
func matchWarningSubject() -> String {
|
|
[roundTitle(), matchTitle(.short)].compacted().joined(separator: " ")
|
|
}
|
|
|
|
func matchWarningMessage() -> String {
|
|
[roundTitle(), matchTitle(.short), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n")
|
|
}
|
|
|
|
func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String {
|
|
if let groupStageObject {
|
|
return groupStageObject.localizedMatchUpLabel(for: index)
|
|
}
|
|
|
|
switch displayStyle {
|
|
case .wide:
|
|
return "Match \(indexInRound() + 1)"
|
|
case .short:
|
|
return "#\(indexInRound() + 1)"
|
|
}
|
|
}
|
|
|
|
func isSeedLocked(atTeamPosition teamPosition: TeamPosition) -> Bool {
|
|
previousMatch(teamPosition)?.disabled == true
|
|
}
|
|
|
|
func unlockSeedPosition(atTeamPosition teamPosition: TeamPosition) {
|
|
previousMatch(teamPosition)?.enableMatch()
|
|
}
|
|
|
|
@discardableResult
|
|
func lockAndGetSeedPosition(atTeamPosition slot: TeamPosition?, opposingSeeding: Bool = false) -> Int {
|
|
let matchIndex = index
|
|
var teamPosition : TeamPosition {
|
|
if let slot {
|
|
return slot
|
|
} else {
|
|
let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex)
|
|
let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound)
|
|
let isUpper = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) < (numberOfMatches / 2)
|
|
var teamPosition = slot ?? (isUpper ? .one : .two)
|
|
if opposingSeeding {
|
|
teamPosition = slot ?? (isUpper ? .two : .one)
|
|
}
|
|
return teamPosition
|
|
}
|
|
}
|
|
previousMatch(teamPosition)?.disableMatch()
|
|
return matchIndex * 2 + teamPosition.rawValue
|
|
}
|
|
|
|
func isSeededBy(team: TeamRegistration, inTeamPosition teamPosition: TeamPosition) -> Bool {
|
|
guard let bracketPosition = team.bracketPosition else { return false }
|
|
return index * 2 + teamPosition.rawValue == bracketPosition
|
|
}
|
|
|
|
func estimatedEndDate(_ additionalEstimationDuration: Int) -> Date? {
|
|
let minutesToAdd = Double(matchFormat.getEstimatedDuration(additionalEstimationDuration))
|
|
return startDate?.addingTimeInterval(minutesToAdd * 60.0)
|
|
}
|
|
|
|
func winner() -> TeamRegistration? {
|
|
guard let winningTeamId else { return nil }
|
|
return Store.main.findById(winningTeamId)
|
|
}
|
|
|
|
func localizedStartDate() -> String {
|
|
if let startDate {
|
|
return startDate.formatted(date: .abbreviated, time: .shortened)
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func scoreLabel() -> String {
|
|
if hasWalkoutTeam() == true {
|
|
return "WO"
|
|
}
|
|
let scoreOne = teamScore(.one)?.score?.components(separatedBy: ",") ?? []
|
|
let scoreTwo = teamScore(.two)?.score?.components(separatedBy: ",") ?? []
|
|
let tuples = zip(scoreOne, scoreTwo).map { ($0, $1) }
|
|
let scores = tuples.map { $0 + "/" + $1 }.joined(separator: " ")
|
|
return scores
|
|
}
|
|
|
|
func cleanScheduleAndSave(_ targetStartDate: Date? = nil) {
|
|
startDate = targetStartDate
|
|
endDate = nil
|
|
followingMatch()?.cleanScheduleAndSave(nil)
|
|
_loserMatch()?.cleanScheduleAndSave(nil)
|
|
do {
|
|
try DataStore.shared.matches.addOrUpdate(instance: self)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
func resetMatch() {
|
|
losingTeamId = nil
|
|
winningTeamId = nil
|
|
endDate = nil
|
|
removeCourt()
|
|
servingTeamId = nil
|
|
}
|
|
|
|
func teamWillBeWalkOut(_ team: TeamRegistration) {
|
|
resetMatch()
|
|
let previousScores = teamScores.filter({ $0.luckyLoser != nil })
|
|
try? DataStore.shared.teamScores.delete(contentOfs: previousScores)
|
|
|
|
if let existingTeamScore = teamScore(ofTeam: team) {
|
|
try? DataStore.shared.teamScores.delete(instance: existingTeamScore)
|
|
}
|
|
|
|
let teamScoreWalkout = TeamScore(match: id, team: team)
|
|
teamScoreWalkout.walkOut = 1
|
|
try? DataStore.shared.teamScores.addOrUpdate(instance: teamScoreWalkout)
|
|
}
|
|
|
|
func luckyLosers() -> [TeamRegistration] {
|
|
roundObject?.previousRound()?.losers() ?? []
|
|
}
|
|
|
|
func isWalkOutSpot(_ teamPosition: TeamPosition) -> Bool {
|
|
teamScore(teamPosition)?.walkOut == 1
|
|
}
|
|
|
|
func setLuckyLoser(team: TeamRegistration, teamPosition: TeamPosition) {
|
|
resetMatch()
|
|
let previousScores = teamScores.filter({ $0.luckyLoser != nil })
|
|
try? DataStore.shared.teamScores.delete(contentOfs: previousScores)
|
|
|
|
if let existingTeamScore = teamScore(ofTeam: team) {
|
|
try? DataStore.shared.teamScores.delete(instance: existingTeamScore)
|
|
}
|
|
|
|
let matchIndex = index
|
|
let position = matchIndex * 2 + teamPosition.rawValue
|
|
let teamScoreLuckyLoser = TeamScore(match: id, team: team)
|
|
teamScoreLuckyLoser.luckyLoser = position
|
|
try? DataStore.shared.teamScores.addOrUpdate(instance: teamScoreLuckyLoser)
|
|
}
|
|
|
|
func disableMatch() {
|
|
_toggleMatchDisableState(true)
|
|
}
|
|
|
|
func enableMatch() {
|
|
_toggleMatchDisableState(false)
|
|
}
|
|
|
|
private func _loserMatch() -> Match? {
|
|
let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: index)
|
|
return roundObject?.loserRounds().first?.getMatch(atMatchIndexInRound: indexInRound / 2)
|
|
}
|
|
|
|
func _toggleLoserMatchDisableState(_ state: Bool) {
|
|
guard let loserMatch = _loserMatch() else { return }
|
|
guard let next = _otherMatch() else { return }
|
|
loserMatch.byeState = true
|
|
if next.disabled {
|
|
loserMatch.byeState = false
|
|
}
|
|
loserMatch._toggleMatchDisableState(state, forward: true)
|
|
}
|
|
|
|
fileprivate func _otherMatch() -> Match? {
|
|
guard let round else { return nil }
|
|
guard index > 0 else { return nil }
|
|
let nextIndex = (index - 1) / 2
|
|
let topMatchIndex = (nextIndex * 2) + 1
|
|
let bottomMatchIndex = (nextIndex + 1) * 2
|
|
let isTopMatch = topMatchIndex + 1 == index
|
|
let lookingForIndex = isTopMatch ? topMatchIndex : bottomMatchIndex
|
|
return Store.main.filter(isIncluded: { $0.round == round && $0.index == lookingForIndex }).first
|
|
}
|
|
|
|
private func _forwardMatch(inRound round: Round) -> Match? {
|
|
guard let roundObjectNextRound = round.nextRound() else { return nil }
|
|
let nextIndex = (index - 1) / 2
|
|
return Store.main.filter(isIncluded: { $0.round == roundObjectNextRound.id && $0.index == nextIndex }).first
|
|
}
|
|
|
|
func _toggleForwardMatchDisableState(_ state: Bool) {
|
|
guard let roundObject else { return }
|
|
guard roundObject.parent != nil else { return }
|
|
guard let forwardMatch = _forwardMatch(inRound: roundObject) else { return }
|
|
guard let next = _otherMatch() else { return }
|
|
if next.disabled && byeState == false && next.byeState == false {
|
|
forwardMatch.byeState = false
|
|
forwardMatch._toggleMatchDisableState(state, forward: true)
|
|
} else if byeState && next.byeState {
|
|
print("don't disable forward match")
|
|
forwardMatch.byeState = false
|
|
forwardMatch._toggleMatchDisableState(false, forward: true)
|
|
} else {
|
|
forwardMatch.byeState = true
|
|
forwardMatch._toggleMatchDisableState(state, forward: true)
|
|
}
|
|
|
|
// if next.disabled == false {
|
|
// forwardMatch.byeState = state
|
|
// }
|
|
|
|
|
|
|
|
//
|
|
// if next.disabled == state {
|
|
// if next.byeState != byeState {
|
|
// //forwardMatch.byeState = state
|
|
// forwardMatch._toggleMatchDisableState(state)
|
|
// } else {
|
|
// forwardMatch._toggleByeState(state)
|
|
// }
|
|
// } else {
|
|
// }
|
|
// forwardMatch._toggleByeState(state)
|
|
}
|
|
|
|
func _toggleMatchDisableState(_ state: Bool, forward: Bool = false) {
|
|
//if disabled == state { return }
|
|
disabled = state
|
|
//byeState = false
|
|
//try? DataStore.shared.matches.addOrUpdate(instance: self)
|
|
|
|
_toggleLoserMatchDisableState(state)
|
|
if forward {
|
|
_toggleForwardMatchDisableState(state)
|
|
} else {
|
|
topPreviousRoundMatch()?._toggleMatchDisableState(state)
|
|
bottomPreviousRoundMatch()?._toggleMatchDisableState(state)
|
|
}
|
|
}
|
|
|
|
func next() -> Match? {
|
|
Store.main.filter(isIncluded: { $0.round == round && $0.index > index }).sorted(by: \.index).first
|
|
}
|
|
|
|
func followingMatch() -> Match? {
|
|
guard let nextRoundId = roundObject?.nextRound()?.id else { return nil }
|
|
return Store.main.filter(isIncluded: { $0.round == nextRoundId && $0.index == index / 2 }).first
|
|
}
|
|
|
|
func getDuration() -> Int {
|
|
if let tournament = currentTournament() {
|
|
matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
|
|
} else {
|
|
matchFormat.getEstimatedDuration()
|
|
}
|
|
}
|
|
|
|
func roundTitle() -> String? {
|
|
if groupStage != nil { return groupStageObject?.groupStageTitle() }
|
|
else if let roundObject { return roundObject.roundTitle() }
|
|
else { return nil }
|
|
}
|
|
|
|
func topPreviousRoundMatchIndex() -> Int {
|
|
index * 2 + 1
|
|
}
|
|
|
|
func bottomPreviousRoundMatchIndex() -> Int {
|
|
(index + 1) * 2
|
|
}
|
|
|
|
func topPreviousRoundMatch() -> Match? {
|
|
guard let roundObject else { return nil }
|
|
return Store.main.filter { match in
|
|
match.index == topPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id
|
|
}.sorted(by: \.index).first
|
|
}
|
|
|
|
func bottomPreviousRoundMatch() -> Match? {
|
|
guard let roundObject else { return nil }
|
|
return Store.main.filter { match in
|
|
match.index == bottomPreviousRoundMatchIndex() && match.round != nil && match.round == roundObject.previousRound()?.id
|
|
}.sorted(by: \.index).first
|
|
}
|
|
|
|
func upperBracketMatch(_ teamPosition: TeamPosition) -> Match? {
|
|
if teamPosition == .one {
|
|
return roundObject?.upperBracketTopMatch(ofMatchIndex: index)
|
|
} else {
|
|
return roundObject?.upperBracketBottomMatch(ofMatchIndex: index)
|
|
}
|
|
}
|
|
|
|
func previousMatch(_ teamPosition: TeamPosition) -> Match? {
|
|
if teamPosition == .one {
|
|
return topPreviousRoundMatch()
|
|
} else {
|
|
return bottomPreviousRoundMatch()
|
|
}
|
|
}
|
|
|
|
func upperMatches() -> [Match] {
|
|
guard let roundObject else { return [] }
|
|
return [roundObject.upperBracketTopMatch(ofMatchIndex: index), roundObject.upperBracketBottomMatch(ofMatchIndex: index)].compactMap({ $0 })
|
|
}
|
|
|
|
var computedOrder: Int {
|
|
guard let roundObject else { return index }
|
|
return roundObject.isLoserBracket() ? roundObject.index * 100 + indexInRound() : roundObject.index * 1000 + indexInRound()
|
|
}
|
|
|
|
func previousMatches() -> [Match] {
|
|
guard let roundObject else { return [] }
|
|
return Store.main.filter { match in
|
|
(match.index == topPreviousRoundMatchIndex() || match.index == bottomPreviousRoundMatchIndex())
|
|
&& match.round == roundObject.previousRound()?.id
|
|
}.sorted(by: \.index)
|
|
}
|
|
|
|
var matchFormat: MatchFormat {
|
|
get {
|
|
format ?? .defaultFormatForMatchType(.groupStage)
|
|
}
|
|
set {
|
|
format = newValue
|
|
}
|
|
}
|
|
|
|
func setWalkOut(_ teamPosition: TeamPosition) {
|
|
let teamScoreWalkout = teamScore(teamPosition) ?? TeamScore(match: id, team: team(teamPosition))
|
|
teamScoreWalkout.walkOut = 0
|
|
let teamScoreWinning = teamScore(teamPosition.otherTeam) ?? TeamScore(match: id, team: team(teamPosition.otherTeam))
|
|
teamScoreWinning.walkOut = nil
|
|
try? DataStore.shared.teamScores.addOrUpdate(contentOfs: [teamScoreWalkout, teamScoreWinning])
|
|
|
|
if endDate == nil {
|
|
endDate = Date()
|
|
}
|
|
|
|
winningTeamId = teamScoreWinning.teamRegistration
|
|
losingTeamId = teamScoreWalkout.teamRegistration
|
|
groupStageObject?.updateGroupStageState()
|
|
roundObject?.updateTournamentState()
|
|
updateFollowingMatchTeamScore()
|
|
}
|
|
|
|
func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
|
|
updateScore(fromMatchDescriptor: matchDescriptor)
|
|
if endDate == nil {
|
|
endDate = Date()
|
|
}
|
|
winningTeamId = team(matchDescriptor.winner)?.id
|
|
losingTeamId = team(matchDescriptor.winner.otherTeam)?.id
|
|
groupStageObject?.updateGroupStageState()
|
|
roundObject?.updateTournamentState()
|
|
updateFollowingMatchTeamScore()
|
|
}
|
|
|
|
func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
|
|
let teamScoreOne = teamScore(.one) ?? TeamScore(match: id, team: team(.one))
|
|
teamScoreOne.score = matchDescriptor.teamOneScores.joined(separator: ",")
|
|
let teamScoreTwo = teamScore(.two) ?? TeamScore(match: id, team: team(.two))
|
|
teamScoreTwo.score = matchDescriptor.teamTwoScores.joined(separator: ",")
|
|
try? DataStore.shared.teamScores.addOrUpdate(contentOfs: [teamScoreOne, teamScoreTwo])
|
|
matchFormat = matchDescriptor.matchFormat
|
|
}
|
|
|
|
func updateFollowingMatchTeamScore() {
|
|
followingMatch()?.updateTeamScores()
|
|
_loserMatch()?.updateTeamScores()
|
|
}
|
|
|
|
func resetTeamScores() {
|
|
let teamScores = teamScores
|
|
if teamScores.isEmpty == false {
|
|
do {
|
|
try DataStore.shared.teamScores.delete(contentOfs: teamScores)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
followingMatch()?.resetTeamScores()
|
|
_loserMatch()?.resetTeamScores()
|
|
}
|
|
|
|
func updateTeamScores() {
|
|
resetTeamScores()
|
|
let teamOne = team(.one)
|
|
let teamTwo = team(.two)
|
|
let teams = [teamOne, teamTwo].compactMap({ $0 }).map { TeamScore(match: id, team: $0) }
|
|
do {
|
|
try DataStore.shared.teamScores.addOrUpdate(contentOfs: teams)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
if teams.isEmpty == false {
|
|
updateFollowingMatchTeamScore()
|
|
}
|
|
}
|
|
|
|
func validateMatch(fromStartDate: Date, toEndDate: Date, fieldSetup: MatchFieldSetup) {
|
|
if hasEnded() == false {
|
|
startDate = fromStartDate
|
|
|
|
switch fieldSetup {
|
|
case .random:
|
|
if let _courtIndex = availableCourts().randomElement() {
|
|
setCourt(_courtIndex)
|
|
}
|
|
case .field(let _courtIndex):
|
|
setCourt(_courtIndex)
|
|
}
|
|
|
|
} else {
|
|
startDate = fromStartDate
|
|
endDate = toEndDate
|
|
}
|
|
}
|
|
|
|
func courtName() -> String? {
|
|
guard let courtIndex else { return nil }
|
|
if let courtName = currentTournament()?.courtName(atIndex: courtIndex) {
|
|
return courtName
|
|
} else {
|
|
return Court.courtIndexedTitle(atIndex: courtIndex)
|
|
}
|
|
}
|
|
|
|
func courtCount() -> Int {
|
|
currentTournament()?.courtCount ?? 1
|
|
}
|
|
|
|
func courtIsAvailable(_ courtIndex: Int) -> Bool {
|
|
let courtUsed = currentTournament()?.courtUsed() ?? []
|
|
return courtUsed.contains(courtIndex) == false
|
|
// return Set(availableCourts().map { String($0) }).subtracting(Set(courtUsed))
|
|
}
|
|
|
|
func courtIsPreferred(_ courtIndex: Int) -> Bool {
|
|
false
|
|
}
|
|
|
|
func availableCourts() -> [Int] {
|
|
let courtUsed = currentTournament()?.courtUsed() ?? []
|
|
let availableCourts = Array(0..<courtCount())
|
|
return Array(Set(availableCourts.map { $0 }).subtracting(Set(courtUsed)))
|
|
}
|
|
|
|
func removeCourt() {
|
|
courtIndex = nil
|
|
}
|
|
|
|
func setCourt(_ courtIndex: Int) {
|
|
self.courtIndex = courtIndex
|
|
}
|
|
|
|
func canBeStarted(inMatches matches: [Match]) -> Bool {
|
|
let teams = teams()
|
|
guard teams.count == 2 else { return false }
|
|
guard hasEnded() == false else { return false }
|
|
guard hasStarted() == false else { return false }
|
|
return teams.allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false })
|
|
}
|
|
|
|
func isTeamPlaying(_ team: TeamRegistration, inMatches matches: [Match]) -> Bool {
|
|
matches.filter({ $0.teams().contains(team) }).isEmpty == false
|
|
}
|
|
|
|
var computedStartDateForSorting: Date {
|
|
startDate ?? .distantFuture
|
|
}
|
|
|
|
var computedEndDateForSorting: Date {
|
|
endDate ?? .distantFuture
|
|
}
|
|
|
|
func hasSpaceLeft() -> Bool {
|
|
teams().count == 1
|
|
}
|
|
|
|
func isReady() -> Bool {
|
|
teams().count == 2
|
|
}
|
|
|
|
func isEmpty() -> Bool {
|
|
teams().isEmpty
|
|
}
|
|
|
|
func hasEnded() -> Bool {
|
|
endDate != nil || hasWalkoutTeam() || winningTeamId != nil
|
|
}
|
|
|
|
func isGroupStage() -> Bool {
|
|
groupStage != nil
|
|
}
|
|
|
|
func isBracket() -> Bool {
|
|
round != nil
|
|
}
|
|
|
|
func walkoutTeam() -> [TeamRegistration] {
|
|
scores().filter({ $0.walkOut != nil }).compactMap { $0.team }
|
|
}
|
|
|
|
func hasWalkoutTeam() -> Bool {
|
|
walkoutTeam().isEmpty == false
|
|
}
|
|
|
|
func currentTournament() -> Tournament? {
|
|
groupStageObject?.tournamentObject() ?? roundObject?.tournamentObject()
|
|
}
|
|
|
|
func tournamentId() -> String? {
|
|
groupStageObject?.tournament ?? roundObject?.tournament
|
|
}
|
|
|
|
func scores() -> [TeamScore] {
|
|
Store.main.filter(isIncluded: { $0.match == id })
|
|
}
|
|
|
|
func teams() -> [TeamRegistration] {
|
|
if groupStage != nil {
|
|
return [groupStageProjectedTeam(.one), groupStageProjectedTeam(.two)].compactMap { $0 }
|
|
}
|
|
return [roundProjectedTeam(.one), roundProjectedTeam(.two)].compactMap { $0 }
|
|
}
|
|
|
|
func scoreDifference(_ teamPosition: Int) -> (set: Int, game: Int)? {
|
|
guard let teamScoreTeam = teamScore(.one), let teamScoreOtherTeam = teamScore(.two) else { return nil }
|
|
var reverseValue = 1
|
|
if teamPosition == team(.two)?.groupStagePosition {
|
|
reverseValue = -1
|
|
}
|
|
let endedSetsOne = teamScoreTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreTeam.isWalkOut())
|
|
let endedSetsTwo = teamScoreOtherTeam.score?.components(separatedBy: ",").compactMap({ Int($0) }) ?? matchFormat.defaultWalkOutScore(teamScoreOtherTeam.isWalkOut())
|
|
var setDifference : Int = 0
|
|
let zip = zip(endedSetsOne, endedSetsTwo)
|
|
if matchFormat.setsToWin == 1 {
|
|
setDifference = endedSetsOne[0] - endedSetsTwo[0]
|
|
} else {
|
|
setDifference = zip.filter { $0 > $1 }.count - zip.filter { $1 > $0 }.count
|
|
}
|
|
let gameDifference = zip.map { ($0, $1) }.map { $0.0 - $0.1 }.reduce(0,+)
|
|
return (setDifference * reverseValue, gameDifference * reverseValue)
|
|
}
|
|
|
|
func groupStageProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
|
|
guard let groupStageObject else { return nil }
|
|
return groupStageObject.team(teamPosition: team, inMatchIndex: index)
|
|
}
|
|
|
|
func roundProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
|
|
guard let roundObject else { return nil }
|
|
return roundObject.roundProjectedTeam(team, inMatch: self)
|
|
}
|
|
|
|
func teamWon(_ team: TeamRegistration?) -> Bool {
|
|
guard let winningTeamId else { return false }
|
|
return winningTeamId == team?.id
|
|
}
|
|
|
|
func teamWon(atPosition teamPosition: TeamPosition) -> Bool {
|
|
guard let winningTeamId else { return false }
|
|
return winningTeamId == team(teamPosition)?.id
|
|
}
|
|
|
|
func team(_ team: TeamPosition) -> TeamRegistration? {
|
|
if groupStage != nil {
|
|
switch team {
|
|
case .one:
|
|
return groupStageProjectedTeam(.one)
|
|
case .two:
|
|
return groupStageProjectedTeam(.two)
|
|
}
|
|
} else {
|
|
switch team {
|
|
case .one:
|
|
return roundProjectedTeam(.one)
|
|
case .two:
|
|
return roundProjectedTeam(.two)
|
|
}
|
|
}
|
|
}
|
|
|
|
func teamNames(_ team: TeamRegistration?) -> [String]? {
|
|
team?.players().map { $0.playerLabel() }
|
|
}
|
|
|
|
func teamWalkOut(_ team: TeamRegistration?) -> Bool {
|
|
teamScore(ofTeam: team)?.isWalkOut() == true
|
|
}
|
|
|
|
func teamScore(_ team: TeamPosition) -> TeamScore? {
|
|
teamScore(ofTeam: self.team(team))
|
|
}
|
|
|
|
func teamScore(ofTeam team: TeamRegistration?) -> TeamScore? {
|
|
scores().first(where: { $0.teamRegistration == team?.id })
|
|
}
|
|
|
|
func isRunning() -> Bool { // at least a match has started
|
|
hasStarted() && hasEnded() == false
|
|
}
|
|
|
|
func hasStarted() -> Bool { // meaning at least one match is over
|
|
if let startDate {
|
|
return startDate.timeIntervalSinceNow < 0
|
|
}
|
|
if hasEnded() {
|
|
return true
|
|
}
|
|
return false
|
|
|
|
//todo scores
|
|
// if let score {
|
|
// return score.hasEnded == false && score.sets.isEmpty == false
|
|
// } else {
|
|
// return false
|
|
// }
|
|
}
|
|
|
|
var roundObject: Round? {
|
|
guard let round else { return nil }
|
|
return Store.main.findById(round)
|
|
}
|
|
|
|
var groupStageObject: GroupStage? {
|
|
guard let groupStage else { return nil }
|
|
return Store.main.findById(groupStage)
|
|
}
|
|
|
|
var isLoserBracket: Bool {
|
|
roundObject?.parent != nil
|
|
}
|
|
|
|
var teamScores: [TeamScore] {
|
|
Store.main.filter { $0.match == self.id }
|
|
}
|
|
|
|
override func deleteDependencies() throws {
|
|
try Store.main.deleteDependencies(items: self.teamScores)
|
|
}
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case _id = "id"
|
|
case _round = "round"
|
|
case _groupStage = "groupStage"
|
|
case _startDate = "startDate"
|
|
case _endDate = "endDate"
|
|
case _index = "index"
|
|
case _format = "format"
|
|
// case _court = "court"
|
|
case _courtIndex = "courtIndex"
|
|
case _servingTeamId = "servingTeamId"
|
|
case _winningTeamId = "winningTeamId"
|
|
case _losingTeamId = "losingTeamId"
|
|
// case _broadcasted = "broadcasted"
|
|
case _name = "name"
|
|
// case _order = "order"
|
|
case _disabled = "disabled"
|
|
}
|
|
}
|
|
|
|
enum MatchDateSetup: Hashable, Identifiable {
|
|
case inMinutes(Int)
|
|
case now
|
|
case customDate
|
|
|
|
var id: Int { hashValue }
|
|
}
|
|
|
|
|
|
enum MatchFieldSetup: Hashable, Identifiable {
|
|
case random
|
|
// case firstAvailable
|
|
case field(Int)
|
|
|
|
var id: Int { hashValue }
|
|
}
|
|
|