|
|
|
|
@ -36,18 +36,113 @@ class Round: ModelObject, Storable { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func hasStarted() -> Bool { |
|
|
|
|
matches.anySatisfy({ $0.hasStarted() }) |
|
|
|
|
playedMatches().anySatisfy({ $0.hasStarted() }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func hasEnded() -> Bool { |
|
|
|
|
matches.allSatisfy({ $0.hasEnded() }) |
|
|
|
|
playedMatches().allSatisfy({ $0.hasEnded() }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func tournamentObject() -> Tournament? { |
|
|
|
|
Store.main.findById(tournament) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var matches: [Match] { |
|
|
|
|
private func _matches() -> [Match] { |
|
|
|
|
Store.main.filter { $0.round == self.id } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func team(_ team: TeamData, inMatch match: Match) -> TeamRegistration? { |
|
|
|
|
switch team { |
|
|
|
|
case .one: |
|
|
|
|
return roundProjectedTeam(.one, inMatch: match) |
|
|
|
|
case .two: |
|
|
|
|
return roundProjectedTeam(.two, inMatch: match) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func seed(_ team: TeamData, inMatchIndex matchIndex: Int) -> TeamRegistration? { |
|
|
|
|
return Store.main.filter(isIncluded: { |
|
|
|
|
$0.tournament == tournament && $0.bracketPosition != nil |
|
|
|
|
}).first(where: { |
|
|
|
|
($0.bracketPosition! / 2) == matchIndex |
|
|
|
|
&& ($0.bracketPosition! % 2) == team.rawValue |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func roundProjectedTeam(_ team: TeamData, inMatch match: Match) -> TeamRegistration? { |
|
|
|
|
if isLoserBracket() == false, let seed = seed(team, inMatchIndex: match.index) { |
|
|
|
|
return seed |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
switch team { |
|
|
|
|
case .one: |
|
|
|
|
if let loser = upperBracketTopMatch(ofMatchIndex: match.index)?.losingTeamId { |
|
|
|
|
return Store.main.findById(loser) |
|
|
|
|
} else if let previousMatch = topPreviousRoundMatch(ofMatch: match) { |
|
|
|
|
if let teamId = previousMatch.winningTeamId { |
|
|
|
|
return Store.main.findById(teamId) |
|
|
|
|
} else if previousMatch.isBye() { |
|
|
|
|
return previousMatch.teams().first |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
case .two: |
|
|
|
|
if let loser = upperBracketBottomMatch(ofMatchIndex: match.index)?.losingTeamId { |
|
|
|
|
return Store.main.findById(loser) |
|
|
|
|
} else if let previousMatch = bottomPreviousRoundMatch(ofMatch: match) { |
|
|
|
|
if let teamId = previousMatch.winningTeamId { |
|
|
|
|
return Store.main.findById(teamId) |
|
|
|
|
} else if previousMatch.isBye() { |
|
|
|
|
return previousMatch.teams().first |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// func isMatchBye(_ match: Match) -> Bool { |
|
|
|
|
// return (upperBracketMatches(ofMatch: match) + previousRoundMatches(ofMatch: match)).anySatisfy({ $0.disabled }) |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
func upperBracketTopMatch(ofMatchIndex matchIndex: Int) -> Match? { |
|
|
|
|
let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) |
|
|
|
|
if isLoserBracket(), previousRound() == nil, let parentRound = parentRound, let upperBracketTopMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2) { |
|
|
|
|
return upperBracketTopMatch |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func upperBracketBottomMatch(ofMatchIndex matchIndex: Int) -> Match? { |
|
|
|
|
let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) |
|
|
|
|
if isLoserBracket(), previousRound() == nil, let parentRound = parentRound, let upperBracketBottomMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2 + 1) { |
|
|
|
|
return upperBracketBottomMatch |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func topPreviousRoundMatch(ofMatch match: Match) -> Match? { |
|
|
|
|
guard let previousRound = previousRound() else { return nil } |
|
|
|
|
return Store.main.filter { |
|
|
|
|
$0.index == match.topPreviousRoundMatchIndex() && $0.round == previousRound.id |
|
|
|
|
}.sorted(by: \.index).first |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func bottomPreviousRoundMatch(ofMatch match: Match) -> Match? { |
|
|
|
|
guard let previousRound = previousRound() else { return nil } |
|
|
|
|
return Store.main.filter { |
|
|
|
|
$0.index == match.bottomPreviousRoundMatchIndex() && $0.round == previousRound.id |
|
|
|
|
}.sorted(by: \.index).first |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func getMatch(atMatchIndexInRound matchIndexInRound: Int) -> Match? { |
|
|
|
|
_matches().first(where: { |
|
|
|
|
let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index) |
|
|
|
|
return index == matchIndexInRound |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func playedMatches() -> [Match] { |
|
|
|
|
Store.main.filter { $0.round == self.id && $0.disabled == false } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -63,13 +158,17 @@ class Round: ModelObject, Storable { |
|
|
|
|
return loserRoundsAndChildren().filter({ $0.index == roundIndex }).sorted(by: \.cumulativeMatchCount) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func isDisabled() -> Bool { |
|
|
|
|
playedMatches().allSatisfy({ $0.disabled || $0.isBye() }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getActiveLoserRound() -> Round? { |
|
|
|
|
let rounds = loserRounds() |
|
|
|
|
return rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.first |
|
|
|
|
return rounds.filter({ $0.hasStarted() && $0.hasEnded() == false && $0.isDisabled() == false }).sorted(by: \.index).reversed().first ?? rounds.first(where: { $0.isDisabled() == false }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var cumulativeMatchCount: Int { |
|
|
|
|
var totalMatches = matches.count |
|
|
|
|
var totalMatches = playedMatches().count |
|
|
|
|
if let parent = parentRound { |
|
|
|
|
totalMatches += parent.cumulativeMatchCount |
|
|
|
|
} |
|
|
|
|
@ -86,10 +185,10 @@ class Round: ModelObject, Storable { |
|
|
|
|
|
|
|
|
|
func roundTitle(_ displayStyle: DisplayStyle = .wide) -> String { |
|
|
|
|
if let parentRound, let initialRound = parentRound.initialRound() { |
|
|
|
|
let parentMatchCount = parentRound.cumulativeMatchCount - initialRound.matches.count |
|
|
|
|
let parentMatchCount = parentRound.cumulativeMatchCount - initialRound.playedMatches().count |
|
|
|
|
print("initialRound", initialRound.roundTitle()) |
|
|
|
|
if let initialRoundNextRound = initialRound.nextRound()?.matches { |
|
|
|
|
return SeedInterval(first: parentMatchCount + initialRoundNextRound.count * 2 + 1, last: parentMatchCount + initialRoundNextRound.count * 2 + matches.count * 2).localizedLabel(displayStyle) |
|
|
|
|
if let initialRoundNextRound = initialRound.nextRound()?.playedMatches() { |
|
|
|
|
return SeedInterval(first: parentMatchCount + initialRoundNextRound.count * 2 + 1, last: parentMatchCount + initialRoundNextRound.count * 2 + playedMatches().count * 2).localizedLabel(displayStyle) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return RoundRule.roundName(fromRoundIndex: index) |
|
|
|
|
@ -102,9 +201,27 @@ class Round: ModelObject, Storable { |
|
|
|
|
return "à démarrer" |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// |
|
|
|
|
// func indexOfMatch(_ match: Match) -> Int? { |
|
|
|
|
// playedMatches().firstIndex(where: { $0.id == match.id }) |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
func previousRoundMatches(ofMatch match: Match) -> [Match] { |
|
|
|
|
return Store.main.filter { |
|
|
|
|
$0.round == previousRound()?.id && ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex()) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func indexOfMatch(_ match: Match) -> Int? { |
|
|
|
|
matches.firstIndex(where: { $0.id == match.id }) |
|
|
|
|
func upperBracketMatches(ofMatch match: Match) -> [Match] { |
|
|
|
|
let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: match.index) |
|
|
|
|
if isLoserBracket(), previousRound() == nil, let parentRound { |
|
|
|
|
let upperBracketMatches = parentRound._matches().filter({ |
|
|
|
|
let index = RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index) |
|
|
|
|
return index == indexInRound * 2 || index == indexInRound * 2 + 1 |
|
|
|
|
}) |
|
|
|
|
return upperBracketMatches |
|
|
|
|
} |
|
|
|
|
return [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func loserRounds() -> [Round] { |
|
|
|
|
@ -122,7 +239,7 @@ class Round: ModelObject, Storable { |
|
|
|
|
|
|
|
|
|
func buildLoserBracket() { |
|
|
|
|
guard loserRounds().isEmpty else { return } |
|
|
|
|
let currentRoundMatchCount = matches.count |
|
|
|
|
let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index) |
|
|
|
|
guard currentRoundMatchCount > 1 else { return } |
|
|
|
|
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount) |
|
|
|
|
let loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat |
|
|
|
|
@ -159,7 +276,7 @@ class Round: ModelObject, Storable { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override func deleteDependencies() throws { |
|
|
|
|
try Store.main.deleteDependencies(items: self.matches) |
|
|
|
|
try Store.main.deleteDependencies(items: _matches()) |
|
|
|
|
try Store.main.deleteDependencies(items: loserRoundsAndChildren()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -183,9 +300,9 @@ extension Round: Selectable { |
|
|
|
|
|
|
|
|
|
func badgeValue() -> Int? { |
|
|
|
|
if let parentRound { |
|
|
|
|
return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.matches }.filter({ $0.isRunning() }).count |
|
|
|
|
return parentRound.loserRounds(forRoundIndex: index).flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count |
|
|
|
|
} else { |
|
|
|
|
return matches.filter({ $0.isRunning() }).count |
|
|
|
|
return playedMatches().filter({ $0.isRunning() }).count |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|