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