|
|
|
|
@ -115,6 +115,25 @@ final class GroupStage: ModelObject, Storable { |
|
|
|
|
return match |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func addReturnMatches() { |
|
|
|
|
var teamScores = [TeamScore]() |
|
|
|
|
var matches = [Match]() |
|
|
|
|
|
|
|
|
|
for i in 0..<_numberOfMatchesToBuild() { |
|
|
|
|
let newMatch = self._createMatch(index: i + matchCount) |
|
|
|
|
// let newMatch = Match(groupStage: self.id, index: i, matchFormat: self.matchFormat, name: localizedMatchUpLabel(for: i)) |
|
|
|
|
teamScores.append(contentsOf: newMatch.createTeamScores()) |
|
|
|
|
matches.append(newMatch) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
try self.tournamentStore.matches.addOrUpdate(contentOfs: matches) |
|
|
|
|
try self.tournamentStore.teamScores.addOrUpdate(contentOfs: teamScores) |
|
|
|
|
} catch { |
|
|
|
|
Logger.error(error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func buildMatches(keepExistingMatches: Bool = false) { |
|
|
|
|
var teamScores = [TeamScore]() |
|
|
|
|
var matches = [Match]() |
|
|
|
|
@ -291,40 +310,58 @@ final class GroupStage: ModelObject, Storable { |
|
|
|
|
return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func isReturnMatchEnabled() -> Bool { |
|
|
|
|
_matches().count > matchCount |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _matchOrder() -> [Int] { |
|
|
|
|
var order: [Int] |
|
|
|
|
|
|
|
|
|
switch size { |
|
|
|
|
case 3: |
|
|
|
|
return [1, 2, 0] |
|
|
|
|
order = [1, 2, 0] |
|
|
|
|
case 4: |
|
|
|
|
return [2, 3, 1, 4, 5, 0] |
|
|
|
|
order = [2, 3, 1, 4, 5, 0] |
|
|
|
|
case 5: |
|
|
|
|
// return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9] |
|
|
|
|
return [3, 5, 8, 2, 6, 1, 9, 4, 7, 0] |
|
|
|
|
order = [3, 5, 8, 2, 6, 1, 9, 4, 7, 0] |
|
|
|
|
case 6: |
|
|
|
|
//return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0] |
|
|
|
|
return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] |
|
|
|
|
order = [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] |
|
|
|
|
default: |
|
|
|
|
return [] |
|
|
|
|
order = [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if isReturnMatchEnabled() { |
|
|
|
|
let arraySize = order.count |
|
|
|
|
// Duplicate and increment each value by the array size |
|
|
|
|
order += order.map { $0 + arraySize } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return order |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func indexOf(_ matchIndex: Int) -> Int { |
|
|
|
|
_matchOrder().firstIndex(of: matchIndex) ?? matchIndex |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _matchUp(for matchIndex: Int) -> [Int] { |
|
|
|
|
Array((0..<size).combinations(ofCount: 2))[safe: matchIndex] ?? [] |
|
|
|
|
let combinations = Array((0..<size).combinations(ofCount: 2)) |
|
|
|
|
return combinations[safe: matchIndex%matchCount] ?? [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func localizedMatchUpLabel(for matchIndex: Int) -> String { |
|
|
|
|
let matchUp = _matchUp(for: matchIndex) |
|
|
|
|
if let index = matchUp.first, let index2 = matchUp.last { |
|
|
|
|
return "#\(index + 1) vs #\(index2 + 1)" |
|
|
|
|
return "#\(index + 1) vs #\(index2 + 1)" + (matchIndex >= matchCount ? " - retour" : "") |
|
|
|
|
} else { |
|
|
|
|
return "--" |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var matchCount: Int { |
|
|
|
|
(size * size - 1) / 2 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? { |
|
|
|
|
let _teams = _teams(for: matchIndex) |
|
|
|
|
switch team { |
|
|
|
|
@ -337,7 +374,7 @@ final class GroupStage: ModelObject, Storable { |
|
|
|
|
|
|
|
|
|
private func _teams(for matchIndex: Int) -> [TeamRegistration?] { |
|
|
|
|
let combinations = Array(0..<size).combinations(ofCount: 2).map {$0} |
|
|
|
|
return combinations[safe: matchIndex]?.map { teamAt(groupStagePosition: $0) } ?? [] |
|
|
|
|
return combinations[safe: matchIndex%matchCount]?.map { teamAt(groupStagePosition: $0) } ?? [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func _removeMatches() { |
|
|
|
|
@ -363,11 +400,41 @@ final class GroupStage: ModelObject, Storable { |
|
|
|
|
fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool { |
|
|
|
|
let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted() |
|
|
|
|
let combos = Array((0..<size).combinations(ofCount: 2)) |
|
|
|
|
let matchIndexes = combos.enumerated().compactMap { $0.element == indexes ? $0.offset : nil } |
|
|
|
|
let matches = _matches().filter { matchIndexes.contains($0.index) } |
|
|
|
|
if matches.count > 1 { |
|
|
|
|
let scoreA = calculateScore(for: teamPosition, matches: matches, groupStagePosition: teamPosition.groupStagePosition!) |
|
|
|
|
let scoreB = calculateScore(for: otherTeam, matches: matches, groupStagePosition: otherTeam.groupStagePosition!) |
|
|
|
|
|
|
|
|
|
let teamsSorted = [scoreA, scoreB].sorted { (lhs, rhs) in |
|
|
|
|
let predicates: [TeamScoreAreInIncreasingOrder] = [ |
|
|
|
|
{ $0.wins < $1.wins }, |
|
|
|
|
{ $0.setDifference < $1.setDifference }, |
|
|
|
|
{ $0.gameDifference < $1.gameDifference}, |
|
|
|
|
{ [self] in $0.team.groupStagePositionAtStep(self.step)! > $1.team.groupStagePositionAtStep(self.step)! } |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
for predicate in predicates { |
|
|
|
|
if !predicate(lhs, rhs) && !predicate(rhs, lhs) { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return predicate(lhs, rhs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return false |
|
|
|
|
}.map({ $0.team }) |
|
|
|
|
|
|
|
|
|
return teamsSorted.first == teamPosition |
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
if let matchIndex = combos.firstIndex(of: indexes), let match = _matches().first(where: { $0.index == matchIndex }) { |
|
|
|
|
return teamPosition.id == match.losingTeamId |
|
|
|
|
} else { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func unsortedTeams() -> [TeamRegistration] { |
|
|
|
|
@ -428,16 +495,19 @@ final class GroupStage: ModelObject, Storable { |
|
|
|
|
guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } |
|
|
|
|
let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) |
|
|
|
|
if matches.isEmpty && nilIfEmpty { return nil } |
|
|
|
|
let score = calculateScore(for: team, matches: matches, groupStagePosition: groupStagePosition) |
|
|
|
|
scoreCache[groupStagePosition] = score |
|
|
|
|
return score |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func calculateScore(for team: TeamRegistration, matches: [Match], groupStagePosition: Int) -> TeamGroupStageScore { |
|
|
|
|
let wins = matches.filter { $0.winningTeamId == team.id }.count |
|
|
|
|
let loses = matches.filter { $0.losingTeamId == team.id }.count |
|
|
|
|
let differences = matches.compactMap { $0.scoreDifference(groupStagePosition, atStep: step) } |
|
|
|
|
let setDifference = differences.map { $0.set }.reduce(0,+) |
|
|
|
|
let gameDifference = differences.map { $0.game }.reduce(0,+) |
|
|
|
|
|
|
|
|
|
// Calculate the score and store it in the cache |
|
|
|
|
let score = (team, wins, loses, setDifference, gameDifference) |
|
|
|
|
scoreCache[groupStagePosition] = score |
|
|
|
|
return score |
|
|
|
|
return (team, wins, loses, setDifference, gameDifference) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Clear the cache if necessary, for example when starting a new step or when matches update |
|
|
|
|
|