""" Bracket and schedule generation logic for ServeSync """ import math from typing import List, Optional, Tuple def calculate_stages(num_players: int, format: str, courts: int) -> dict: """Calculate suggested stages and rounds for an event""" if format == "Doubles": teams = num_players // 2 else: teams = num_players matches_per_round = courts if teams <= 4: suggested_stages = 2 stage_names = ["Open Stage", "Tournament"] elif teams <= 8: suggested_stages = 2 stage_names = ["Open Stage", "Tournament"] else: suggested_stages = 3 stage_names = ["Open Stage", "Skill Stage", "Tournament"] rounds_single_elim = math.ceil(math.log2(max(teams, 2))) rounds_double_elim = rounds_single_elim * 2 return { "teams": teams, "matches_per_round": matches_per_round, "rounds_single_elim": rounds_single_elim, "rounds_double_elim": rounds_double_elim, "suggested_stages": suggested_stages, "stage_names": stage_names, } def generate_round_robin(player_ids: List[int]) -> List[Tuple[int, int]]: """Generate all round-robin pairings""" pairings = [] n = len(player_ids) players = list(player_ids) if n % 2 == 1: players.append(None) # bye for round_num in range(len(players) - 1): for i in range(len(players) // 2): p1 = players[i] p2 = players[len(players) - 1 - i] if p1 is not None and p2 is not None: pairings.append((p1, p2)) # Rotate: keep first fixed, rotate the rest players = [players[0]] + [players[-1]] + players[1:-1] return pairings def generate_single_elim_bracket(player_ids: List[int]) -> List[dict]: """ Generate single elimination bracket matches. Returns list of match dicts with round, match_number, player1, player2, next_winner_match_id """ n = len(player_ids) # Pad to power of 2 bracket_size = 2 ** math.ceil(math.log2(max(n, 2))) players = list(player_ids) + [None] * (bracket_size - n) matches = [] match_id_counter = 1 # First round round1_matches = [] for i in range(0, len(players), 2): match = { "temp_id": match_id_counter, "round_number": 1, "match_number": i // 2 + 1, "player1_id": players[i], "player2_id": players[i + 1], "next_winner_temp_id": None, "bracket_position": "winners", "status": "scheduled", } # Handle byes if players[i] is None or players[i + 1] is None: match["status"] = "bye" match["winner_temp"] = players[i] if players[i] is not None else players[i + 1] round1_matches.append(match) matches.append(match) match_id_counter += 1 # Subsequent rounds current_round_matches = round1_matches round_num = 2 while len(current_round_matches) > 1: next_round_matches = [] for i in range(0, len(current_round_matches), 2): match = { "temp_id": match_id_counter, "round_number": round_num, "match_number": i // 2 + 1, "player1_id": None, "player2_id": None, "next_winner_temp_id": None, "bracket_position": "winners", "status": "scheduled", } # Set next_winner_temp_id for previous round matches current_round_matches[i]["next_winner_temp_id"] = match_id_counter if i + 1 < len(current_round_matches): current_round_matches[i + 1]["next_winner_temp_id"] = match_id_counter next_round_matches.append(match) matches.append(match) match_id_counter += 1 current_round_matches = next_round_matches round_num += 1 return matches def generate_open_schedule(player_ids: List[int], rounds: int, courts: int) -> List[dict]: """ Generate open match schedule - round robin up to 'rounds' rounds. """ all_pairings = generate_round_robin(player_ids) matches = [] match_num = 1 round_num = 1 court_idx = 0 for round_round in range(min(rounds, math.ceil(len(all_pairings) / max(courts, 1)))): start = round_round * courts end = start + courts round_pairings = all_pairings[start:end] for i, (p1, p2) in enumerate(round_pairings): matches.append({ "round_number": round_num, "match_number": i + 1, "player1_id": p1, "player2_id": p2, "status": "scheduled", "bracket_position": "main", }) round_num += 1 return matches