Files
servesync-demo/backend/app/bracket.py
Nemo 4421f61c24 feat: Rebuild as Court Manager Dashboard v2
- Complete rewrite for single Court Manager user
- Feature 1: Player Management (CRUD, ELO, skill level)
- Feature 2: Court Reservation (timeline, override)
- Feature 3: Match Event Builder (3-step wizard)
  - Step 1: Event setup with player selection + stage calculator
  - Step 2: Stage configurator (Open/Skill/RR/Tournament)
  - Step 3: Auto bracket generation
- Feature 4: Live Court View (main dashboard, 2x2 grid)
- Event Control: score entry, bracket advancement, court assignment
- Screen view: TV display per court (/screen/:id)
- Seed: 8 players, 4 courts, 1 completed + 1 active event
- Routes: / players courts events events/new events/:id screen/:id
2026-03-19 07:35:51 +08:00

149 lines
4.8 KiB
Python

"""
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