fix issue with event

sync3
Raz 6 months ago
parent 26f8a94b18
commit 970e89b2e5
  1. 3
      PadelClub/Extensions/Tournament+Extensions.swift
  2. 2
      PadelClub/Views/Cashier/Event/EventView.swift
  3. 18
      PadelClub/Views/Tournament/AnimationMeleeView.swift
  4. 10
      PadelClub/Views/Tournament/FileImportView.swift
  5. 358
      PadelClub/Views/Tournament/PlayerRotationTournamentGenerator.swift

@ -225,7 +225,8 @@ extension Tournament {
if let tournamentStore = self.tournamentStore {
tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teamsToImport)
tournamentStore.playerRegistrations.addOrUpdate(contentOfs: teams.flatMap { $0.players })
let playersToImport = teams.flatMap { $0.players }
tournamentStore.playerRegistrations.addOrUpdate(contentOfs: playersToImport)
}
if state() == .build && groupStageCount > 0 && groupStageTeams().isEmpty {

@ -95,7 +95,7 @@ struct EventView: View {
case .tournaments(let event):
EventTournamentsView(event: event)
case .cashier:
CashierDetailView(tournaments: event.tournaments.filter({ $0.isFree() == false }))
CashierDetailView(tournaments: event.paidTournaments())
}
}
}

@ -0,0 +1,18 @@
//
// AnimationMeleeView.swift
// PadelClub
//
// Created by razmig on 03/05/2025.
//
import SwiftUI
struct AnimationMeleeView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
#Preview {
AnimationMeleeView()
}

@ -192,7 +192,7 @@ struct FileImportView: View {
}
}
if let event = tournament.eventObject(), event.tournaments.count > 1, fileProvider == .frenchFederation {
if let event = tournament.eventObject(), event.federalTournaments().count > 1, fileProvider == .frenchFederation {
Section {
RowButtonView("Importer pour tous les tournois") {
multiImport = true
@ -322,7 +322,7 @@ struct FileImportView: View {
let _filteredTeams = filteredTeams
let previousTeams = tournament.sortedTeams(selectedSortedTeams: tournament.selectedSortedTeams())
if multiImport, fileProvider == .frenchFederation, let tournaments = tournament.eventObject()?.tournaments, tournaments.count > 1 {
if multiImport, fileProvider == .frenchFederation, let tournaments = tournament.eventObject()?.federalTournaments(), tournaments.count > 1 {
ForEach(tournaments) { tournament in
let tournamentFilteredTeams = self.filteredTeams(tournament: tournament)
@ -471,7 +471,7 @@ struct FileImportView: View {
ButtonValidateView(title: (multiImport ? "Tout Valider" : "Valider")) {
validationInProgress = true
Task {
if let tournaments = tournament.eventObject()?.tournaments, tournaments.count > 1, multiImport {
if let tournaments = tournament.eventObject()?.federalTournaments(), tournaments.count > 1, multiImport {
for tournament in tournaments {
if validatedTournamentIds.contains(tournament.id) == false {
await _validate(tournament: tournament)
@ -538,9 +538,9 @@ struct FileImportView: View {
}
let event: Event? = tournament.eventObject()
if let event, event.tournaments.count > 1 {
if let event, event.federalTournaments().count > 1 {
var categoriesDone: [CombinedCategory] = []
for someTournament in event.tournaments {
for someTournament in event.federalTournaments() {
let combinedCategory = CombinedCategory(tournamentCategory: someTournament.tournamentCategory, federalTournamentAge: someTournament.federalTournamentAge)
if categoriesDone.contains(combinedCategory) == false {
let _teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: someTournament, fileProvider: fileProvider, checkingCategoryDisabled: false, chunkByParameter: chunkByParameter)

@ -0,0 +1,358 @@
import Foundation
// MARK: - Player Rotation Tournament Generator
class PlayerRotationTournamentGenerator {
// MARK: - Public Function
/// Generate a round-robin tournament where each player plays with every other player exactly once
/// - Parameters:
/// - playerRegistrations: List of player registrations to schedule
/// - courtsAvailable: Number of courts available
/// - Returns: Tournament data with rounds and matches
func generateTournament(playerRegistrations: [PlayerRegistration], courtsAvailable: Int) -> [Round] {
guard playerRegistrations.count >= 4, playerRegistrations.count % 2 == 0 else {
print("Error: Player count must be even and at least 4")
return []
}
let players = playerRegistrations
let playerCount = players.count
let numberOfRounds = playerCount - 1
var rounds: [Round] = []
// Track partnerships to ensure each player partners with every other player exactly once
var usedPartnerships: Set<String> = []
// Track oppositions to balance who plays against whom
var oppositionCount: [String: Int] = [:]
// Generate all rounds using circle method
for roundNumber in 0..<numberOfRounds {
// 1. Generate partnerships for this round
let partnerships = generatePartnershipsForRound(
players: players,
roundNumber: roundNumber,
usedPartnerships: &usedPartnerships
)
// 2. Create teams from partnerships
let teams = partnerships.map { pair -> TeamRegistration in
createTemporaryTeam(player1: pair.0, player2: pair.1)
}
// 3. Create matches by pairing teams
let matchPairs = createMatchPairsForRound(
teams: teams,
oppositionCount: &oppositionCount,
courtsAvailable: courtsAvailable
)
// 4. Create matches and team scores
let matches = matchPairs.map { pair -> Match in
let match = Match()
match.court = pair.court
// Create team scores for this match
let teamScore1 = TeamScore()
teamScore1.team_registration = pair.team1
teamScore1.match = match
let teamScore2 = TeamScore()
teamScore2.team_registration = pair.team2
teamScore2.match = match
// Link team scores to match
match.team_scores = [teamScore1, teamScore2]
return match
}
// 5. Create round
let round = Round()
round.number = roundNumber + 1
round.matches = matches
rounds.append(round)
}
return rounds
}
// MARK: - Private Helper Functions
/// Generate player partnerships for a specific round using the circle method
private func generatePartnershipsForRound(
players: [PlayerRegistration],
roundNumber: Int,
usedPartnerships: inout Set<String>
) -> [(PlayerRegistration, PlayerRegistration)] {
var partnerships: [(PlayerRegistration, PlayerRegistration)] = []
let playerCount = players.count
// Create a circle of players
// Player 0 stays fixed in the center, all others rotate
// 1. Player 0 partners with a different player each round
let centerPlayer = players[0]
let partnerIndex = (roundNumber + 1) % playerCount
let partnerForCenterPlayer = players[partnerIndex]
// Check if partnership is already used (shouldn't happen with correct algorithm)
let partnershipKey = createPartnershipKey(player1: centerPlayer, player2: partnerForCenterPlayer)
if !usedPartnerships.contains(partnershipKey) {
usedPartnerships.insert(partnershipKey)
partnerships.append((centerPlayer, partnerForCenterPlayer))
}
// 2. Create a set of players already assigned
var assignedPlayers = Set<String>([centerPlayer.id, partnerForCenterPlayer.id])
// 3. Get remaining players and rotate them based on round number
var remainingPlayers = players.filter {
!assignedPlayers.contains($0.id)
}
// Apply rotation based on round number
if roundNumber > 0 {
// Rotate the players clockwise
let offset = roundNumber % remainingPlayers.count
remainingPlayers = Array(remainingPlayers.rotated(by: offset))
}
// 4. Pair players from opposite sides of the circle
let halfCount = remainingPlayers.count / 2
for i in 0..<halfCount {
let player1 = remainingPlayers[i]
let player2 = remainingPlayers[i + halfCount]
let key = createPartnershipKey(player1: player1, player2: player2)
if !usedPartnerships.contains(key) {
usedPartnerships.insert(key)
partnerships.append((player1, player2))
}
}
return partnerships
}
/// Struct to hold match pairing information
private struct MatchPair {
let team1: TeamRegistration
let team2: TeamRegistration
let court: Int
}
/// Create matches by pairing teams, optimizing for balanced opposition
private func createMatchPairsForRound(
teams: [TeamRegistration],
oppositionCount: inout [String: Int],
courtsAvailable: Int
) -> [MatchPair] {
let teamCount = teams.count
let maxMatches = min(teamCount / 2, courtsAvailable)
var matchPairs: [MatchPair] = []
// Create a copy of teams we can modify
var remainingTeams = teams
// For the first round, just create sequential matches
if oppositionCount.isEmpty {
for court in 0..<maxMatches {
guard remainingTeams.count >= 2 else { break }
let team1 = remainingTeams.removeFirst()
let team2 = remainingTeams.removeFirst()
matchPairs.append(MatchPair(team1: team1, team2: team2, court: court + 1))
// Update opposition counts
updateOppositionCounts(team1: team1, team2: team2, oppositionCount: &oppositionCount)
}
return matchPairs
}
// For subsequent rounds, optimize for balanced opposition
for court in 0..<maxMatches {
guard remainingTeams.count >= 2 else { break }
// Take the first team
let team1 = remainingTeams.removeFirst()
// Find best opponent (team that these players have faced the least)
var bestOpponentIndex = 0
var lowestOppositionScore = Int.max
for (index, potentialOpponent) in remainingTeams.enumerated() {
let oppositionScore = calculateOppositionScore(
team1: team1,
team2: potentialOpponent,
oppositionCount: oppositionCount
)
if oppositionScore < lowestOppositionScore {
lowestOppositionScore = oppositionScore
bestOpponentIndex = index
}
}
// Select the best opponent
let team2 = remainingTeams.remove(at: bestOpponentIndex)
// Create the match pair
matchPairs.append(MatchPair(team1: team1, team2: team2, court: court + 1))
// Update opposition counts
updateOppositionCounts(team1: team1, team2: team2, oppositionCount: &oppositionCount)
}
return matchPairs
}
/// Calculates an opposition score between two teams (lower is better)
private func calculateOppositionScore(
team1: TeamRegistration,
team2: TeamRegistration,
oppositionCount: [String: Int]
) -> Int {
let team1Players = team1.player_registrations
let team2Players = team2.player_registrations
var total = 0
for player1 in team1Players {
for player2 in team2Players {
let key = createOppositionKey(player1: player1, player2: player2)
total += oppositionCount[key, default: 0]
}
}
return total
}
/// Updates opposition counts after a match is scheduled
private func updateOppositionCounts(
team1: TeamRegistration,
team2: TeamRegistration,
oppositionCount: inout [String: Int]
) {
let team1Players = team1.player_registrations
let team2Players = team2.player_registrations
for player1 in team1Players {
for player2 in team2Players {
let key = createOppositionKey(player1: player1, player2: player2)
oppositionCount[key, default: 0] += 1
}
}
}
// MARK: - Helper Functions for Keys and Objects Creation
/// Create a unique key for a partnership
private func createPartnershipKey(player1: PlayerRegistration, player2: PlayerRegistration) -> String {
let ids = [player1.id, player2.id].sorted()
return ids.joined(separator: "-")
}
/// Create a unique key for opposition tracking
private func createOppositionKey(player1: PlayerRegistration, player2: PlayerRegistration) -> String {
let ids = [player1.id, player2.id].sorted()
return ids.joined(separator: "-")
}
/// Create a temporary team from two players
private func createTemporaryTeam(player1: PlayerRegistration, player2: PlayerRegistration) -> TeamRegistration {
let team = TeamRegistration()
// Set the team's player registrations
player1.team_registration = team
player2.team_registration = team
// For convenience during algorithm
team.player_registrations = [player1, player2]
return team
}
}
// MARK: - Helper Models (Temporary)
// Using these for convenience in the algorithm
extension TeamRegistration {
var player_registrations: [PlayerRegistration] {
get {
return objc_getAssociatedObject(self, &playerRegistrationsKey) as? [PlayerRegistration] ?? []
}
set {
objc_setAssociatedObject(self, &playerRegistrationsKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
private var playerRegistrationsKey: UInt8 = 0
extension Round {
var matches: [Match] {
get {
return objc_getAssociatedObject(self, &matchesKey) as? [Match] ?? []
}
set {
objc_setAssociatedObject(self, &matchesKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
private var matchesKey: UInt8 = 0
extension Match {
var team_scores: [TeamScore] {
get {
return objc_getAssociatedObject(self, &teamScoresKey) as? [TeamScore] ?? []
}
set {
objc_setAssociatedObject(self, &teamScoresKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
var court: Int {
get {
return objc_getAssociatedObject(self, &courtKey) as? Int ?? 0
}
set {
objc_setAssociatedObject(self, &courtKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
private var teamScoresKey: UInt8 = 0
private var courtKey: UInt8 = 0
// MARK: - Array Extension
extension Array {
func rotated(by positions: Int) -> Array {
let normalizedPositions = positions % count
return Array(self[normalizedPositions..<count] + self[0..<normalizedPositions])
}
}
// MARK: - TeamScore
class TeamScore {
var team_registration: TeamRegistration?
var match: Match?
}
// MARK: - Final Solution Function
/// Create a player rotation tournament where each player partners with every other player exactly once
/// - Parameters:
/// - players: List of player registrations to schedule
/// - courtsAvailable: Number of courts available
/// - Returns: List of rounds, each containing matches
func createPlayerRotationTournament(players: [PlayerRegistration], courtsAvailable: Int) -> [Round] {
let generator = PlayerRotationTournamentGenerator()
return generator.generateTournament(playerRegistrations: players, courtsAvailable: courtsAvailable)
}
Loading…
Cancel
Save