in progress

multistore
Razmig Sarkissian 2 years ago
parent db9d4637d0
commit 29cc0df4a2
  1. 67
      PadelClub/Data/Match.swift
  2. 147
      PadelClub/Data/Round.swift
  3. 8
      PadelClub/Data/Tournament.swift
  4. 8
      PadelClub/ViewModel/MatchDescriptor.swift
  5. 4
      PadelClub/Views/Match/MatchSummaryView.swift
  6. 16
      PadelClub/Views/Match/PlayerBlockView.swift
  7. 2
      PadelClub/Views/Round/LoserBracketView.swift
  8. 21
      PadelClub/Views/Round/LoserRoundsView.swift
  9. 2
      PadelClub/Views/Round/RoundSettingsView.swift
  10. 2
      PadelClub/Views/Round/RoundView.swift

@ -47,7 +47,7 @@ class Match: ModelObject, Storable {
func indexInRound() -> Int {
if groupStage != nil {
return index
} else if let index = roundObject?.matches.firstIndex(where: { $0.id == id }) {
} else if let index = roundObject?.playedMatches().firstIndex(where: { $0.id == id }) {
return index
}
return RoundRule.matchIndexWithinRound(fromMatchIndex: index)
@ -317,33 +317,66 @@ class Match: ModelObject, Storable {
})
}
func isBye() -> Bool {
guard let roundObject else { return false }
return (roundObject.upperBracketMatches(ofMatch: self) + roundObject.previousRoundMatches(ofMatch: self)).anySatisfy({ $0.disabled })
}
func upperBracketTopMatch() -> Match? {
guard let roundObject else { return nil }
let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: index)
if roundObject.isLoserBracket(), roundObject.previousRound() == nil, let parentRound = roundObject.parentRound, let upperBracketTopMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2) {
return upperBracketTopMatch
}
return nil
}
func upperBracketBottomMatch() -> Match? {
guard let roundObject else { return nil }
let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: index)
if roundObject.isLoserBracket(), roundObject.previousRound() == nil, let parentRound = roundObject.parentRound, let upperBracketBottomMatch = parentRound.getMatch(atMatchIndexInRound: indexInRound * 2 + 1) {
return upperBracketBottomMatch
}
return nil
}
func roundProjectedTeam(_ team: TeamData) -> TeamRegistration? {
guard let roundObject else { return nil }
return roundObject.roundProjectedTeam(team, inMatch: self)
if roundObject.isLoserBracket() == false, let seed = seed(team) {
return seed
}
let indexInRound = indexInRound()
switch team {
case .one:
if roundObject.isLoserBracket(), roundObject.previousRound() == nil, let parentRound = roundObject.parentRound, let loser = parentRound.matches.first(where: { parentRound.indexOfMatch($0) == indexInRound * 2 })?.losingTeamId {
if let loser = upperBracketTopMatch()?.losingTeamId {
return Store.main.findById(loser)
} else if let teamId = topPreviousRoundMatch()?.winningTeamId {
return Store.main.findById(teamId)
} else if let match = topPreviousRoundMatch() {
if let teamId = match.winningTeamId {
return Store.main.findById(teamId)
} else if match.isBye() {
return match.teams().first
}
}
case .two:
if roundObject.isLoserBracket(), roundObject.previousRound() == nil, let parentRound = roundObject.parentRound, let loser = parentRound.matches.first(where: { parentRound.indexOfMatch($0) == indexInRound * 2 + 1 })?.losingTeamId {
if let loser = upperBracketBottomMatch()?.losingTeamId {
return Store.main.findById(loser)
} else if let teamId = bottomPreviousRoundMatch()?.winningTeamId {
return Store.main.findById(teamId)
} else if let match = bottomPreviousRoundMatch() {
if let teamId = match.winningTeamId {
return Store.main.findById(teamId)
} else if match.isBye() {
return match.teams().first
}
}
}
return nil
}
func teamWon(_ team: TeamData) -> Bool {
func teamWon(_ team: TeamRegistration?) -> Bool {
guard let winningTeamId else { return false }
return winningTeamId == self.team(team)?.id
return winningTeamId == team?.id
}
func team(_ team: TeamData) -> TeamRegistration? {
@ -364,18 +397,22 @@ class Match: ModelObject, Storable {
}
}
func teamNames(_ team: TeamData) -> [String]? {
self.team(team)?.players().map { $0.playerLabel() }
func teamNames(_ team: TeamRegistration?) -> [String]? {
team?.players().map { $0.playerLabel() }
}
func teamWalkOut(_ team: TeamData) -> Bool {
teamScore(team)?.isWalkOut() == true
func teamWalkOut(_ team: TeamRegistration?) -> Bool {
teamScore(ofTeam: team)?.isWalkOut() == true
}
func teamScore(_ team: TeamData) -> TeamScore? {
scores().first(where: { $0.teamRegistration == self.team(team)?.id })
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
}

@ -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,11 +201,29 @@ class Round: ModelObject, Storable {
return "à démarrer"
}
}
//
// func indexOfMatch(_ match: Match) -> Int? {
// playedMatches().firstIndex(where: { $0.id == match.id })
// }
func indexOfMatch(_ match: Match) -> Int? {
matches.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 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] {
return Store.main.filter(isIncluded: { $0.loser == id }).sorted(by: \.index).reversed()
}
@ -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
}
}
}

@ -156,11 +156,11 @@ class Tournament : ModelObject, Storable {
}
func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] {
getRound(atRoundIndex: roundIndex)?.matches.filter { $0.teams().count == 0 } ?? []
getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.teams().count == 0 } ?? []
}
func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] {
getRound(atRoundIndex: roundIndex)?.matches.filter { $0.teams().count == 1 } ?? []
getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.teams().count == 1 } ?? []
}
func availableSeedGroups() -> [SeedInterval] {
@ -613,7 +613,9 @@ class Tournament : ModelObject, Storable {
try? DataStore.shared.matches.addOrUpdate(contentOfs: matches)
buildLoserBracket()
self.rounds().forEach { round in
round.buildLoserBracket()
}
}
func deleteStructure() {

@ -27,10 +27,12 @@ class MatchDescriptor: ObservableObject {
self.matchFormat = format
self.setDescriptors = [SetDescriptor(setFormat: format.setFormat)]
}
self.teamLabelOne = match?.team(.one)?.teamLabel() ?? ""
self.teamLabelTwo = match?.team(.two)?.teamLabel() ?? ""
let teamOne = match?.team(.one)
let teamTwo = match?.team(.two)
self.teamLabelOne = teamOne?.teamLabel() ?? ""
self.teamLabelTwo = teamTwo?.teamLabel() ?? ""
if let match, let scoresTeamOne = match.teamScore(.one)?.score, let scoresTeamTwo = match.teamScore(.two)?.score {
if let match, let scoresTeamOne = match.teamScore(ofTeam: teamOne)?.score, let scoresTeamTwo = match.teamScore(ofTeam: teamTwo)?.score {
self.setDescriptors = combineArraysIntoTuples(scoresTeamOne.components(separatedBy: ","), scoresTeamTwo.components(separatedBy: ",")).map({ (a:String?, b:String?) in
SetDescriptor(valueTeamOne: a != nil ? Int(a!) : nil, valueTeamTwo: b != nil ? Int(b!) : nil, setFormat: match.matchFormat.setFormat)

@ -84,14 +84,14 @@ struct MatchSummaryView: View {
if matchViewStyle != .feedStyle {
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: matchViewStyle == .plainStyle ? 8 : 0) {
PlayerBlockView(match: match, team: .one, color: color, width: width)
PlayerBlockView(match: match, whichTeam: .one, color: color, width: width)
.padding(matchViewStyle == .plainStyle ? 0 : 8)
if width == 1 {
Divider()
} else {
Divider().frame(height: width).overlay(color)
}
PlayerBlockView(match: match, team: .two, color: color, width: width)
PlayerBlockView(match: match, whichTeam: .two, color: color, width: width)
.padding(matchViewStyle == .plainStyle ? 0 : 8)
}
}

@ -9,11 +9,19 @@ import SwiftUI
struct PlayerBlockView: View {
var match: Match
let team: TeamData
let whichTeam: TeamData
let team: TeamRegistration?
let color: Color
let width: CGFloat
init(match: Match, whichTeam: TeamData, color: Color, width: CGFloat) {
self.match = match
self.whichTeam = whichTeam
self.team = match.team(whichTeam)
self.color = color
self.width = width
}
var names: [String]? {
match.teamNames(team)
}
@ -31,11 +39,11 @@ struct PlayerBlockView: View {
}
var scores: [String] {
match.teamScore(team)?.score?.components(separatedBy: ",") ?? []
match.teamScore(ofTeam: team)?.score?.components(separatedBy: ",") ?? []
}
private func _defaultLabel() -> String {
team.localizedLabel()
whichTeam.localizedLabel()
}
var body: some View {

@ -38,7 +38,7 @@ struct LoserBracketView: View {
private func _loserRoundView(_ loserRound: Round) -> some View {
Section {
ForEach(loserRound.matches) { match in
ForEach(loserRound.playedMatches()) { match in
MatchRowView(match: match, matchViewStyle: .standardStyle)
}
} header: {

@ -7,17 +7,6 @@
import SwiftUI
extension Int: Selectable, Identifiable {
public var id: Int { self }
func selectionLabel() -> String {
"Tour #\(self + 1)"
}
func badgeValue() -> Int? {
nil
}
}
struct LoserRoundsView: View {
var upperBracketRound: Round
@State private var selectedRound: Round?
@ -52,8 +41,16 @@ struct LoserRoundView: View {
List {
ForEach(loserRounds) { loserRound in
Section {
ForEach(loserRound.matches) { match in
ForEach(loserRound.playedMatches()) { match in
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
.overlay {
if match.isBye() {
Image(systemName: "pencil.slash")
.resizable()
.scaledToFit()
.opacity(0.3)
}
}
}
} header: {
Text(loserRound.roundTitle(.wide))

@ -88,7 +88,7 @@ struct RoundSettingsView: View {
// seeds.prefix(1).first?.bracketPosition = lastIndex * 2 + 1 //TS 1 branche du bas du dernier match
// seeds.prefix(2).dropFirst().first?.bracketPosition = startIndex * 2 //TS 2 branche du haut du premier match
if let matches = tournament.getRound(atRoundIndex: roundIndex)?.matches {
if let matches = tournament.getRound(atRoundIndex: roundIndex)?.playedMatches() {
if let lastMatch = matches.last {
seeds.prefix(1).first?.setSeedPosition(inSpot: lastMatch, upperBranch: 1, opposingSeeding: false)
}

@ -24,7 +24,7 @@ struct RoundView: View {
}
}
ForEach(round.matches) { match in
ForEach(round.playedMatches()) { match in
Section {
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
} header: {

Loading…
Cancel
Save