parent
26f8a94b18
commit
970e89b2e5
@ -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() |
||||||
|
} |
||||||
@ -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…
Reference in new issue