300 lines
11 KiB
Python
300 lines
11 KiB
Python
"""
|
|
Data models and business logic for TV_APP
|
|
Handles all calculations and data structure creation
|
|
"""
|
|
|
|
from datetime import datetime
|
|
import random
|
|
|
|
NUM_CAMERAS = 6
|
|
|
|
|
|
class Tournament:
|
|
"""Tournament creation and management"""
|
|
|
|
@staticmethod
|
|
def create_league(enabled_players, tournament_type):
|
|
"""Create a new league with 5 tournaments"""
|
|
league_id = f"league_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
|
|
league_data = {
|
|
'league_id': league_id,
|
|
'created_at': datetime.now().isoformat(),
|
|
'tournament_type': tournament_type,
|
|
'total_tournaments': 5,
|
|
'current_tournament': 0,
|
|
'participants': {},
|
|
'completed_tournaments': [],
|
|
'league_finished': False
|
|
}
|
|
|
|
# Initialize participants
|
|
for player in enabled_players:
|
|
league_data['participants'][str(player['id'])] = {
|
|
'name': player['name'],
|
|
'joker_used': False,
|
|
'tournament_results': [],
|
|
'total_score': 0,
|
|
'final_score': 0,
|
|
'tournaments_participated': 0
|
|
}
|
|
|
|
return league_data
|
|
|
|
@staticmethod
|
|
def create_draft(enabled_players, tournament_type='20_targets', league_tournament_number=None):
|
|
"""Create draft groups of 6 players and organize rounds"""
|
|
if len(enabled_players) < 1:
|
|
return None
|
|
|
|
# Shuffle players for random grouping
|
|
players_copy = enabled_players.copy()
|
|
random.shuffle(players_copy)
|
|
|
|
# Create groups of up to 6
|
|
groups = []
|
|
for i in range(0, len(players_copy), NUM_CAMERAS):
|
|
group = players_copy[i:i + NUM_CAMERAS]
|
|
groups.append(group)
|
|
|
|
# Create rounds
|
|
rounds = []
|
|
for i, group in enumerate(groups):
|
|
rounds.append({
|
|
'round_number': i + 1,
|
|
'players': group,
|
|
'status': 'pending' if i == 0 else 'waiting'
|
|
})
|
|
|
|
tournament_data = {
|
|
'rounds': rounds,
|
|
'created_at': datetime.now().isoformat(),
|
|
'total_players': len(enabled_players),
|
|
'total_rounds': len(rounds),
|
|
'current_round': 1,
|
|
'tournament_type': tournament_type
|
|
}
|
|
|
|
if league_tournament_number:
|
|
tournament_data['league_tournament_number'] = league_tournament_number
|
|
|
|
return tournament_data
|
|
|
|
@staticmethod
|
|
def create_results_structure(tournament_data):
|
|
"""Create results structure for a tournament"""
|
|
if not tournament_data:
|
|
return None
|
|
|
|
tournament_type = tournament_data.get('tournament_type', '20_targets')
|
|
|
|
# Determine target count and shots per target
|
|
if tournament_type == '40_targets':
|
|
num_targets = 40
|
|
shots_per_target = 2
|
|
elif tournament_type == '4_targets':
|
|
num_targets = 4
|
|
shots_per_target = 5
|
|
else: # 20_targets (default)
|
|
num_targets = 20
|
|
shots_per_target = 2
|
|
|
|
results = {
|
|
'tournament_id': tournament_data.get('created_at', datetime.now().isoformat()),
|
|
'tournament_type': tournament_type,
|
|
'participants': {},
|
|
'tournament_finished': False,
|
|
'created_at': datetime.now().isoformat()
|
|
}
|
|
|
|
# Add league info if present
|
|
if 'league_tournament_number' in tournament_data:
|
|
results['league_tournament_number'] = tournament_data['league_tournament_number']
|
|
|
|
# Create structure for each participant
|
|
all_players = []
|
|
for round_data in tournament_data['rounds']:
|
|
all_players.extend(round_data['players'])
|
|
|
|
for player in all_players:
|
|
player_id = str(player['id'])
|
|
|
|
# Create target structure based on tournament type
|
|
targets = {}
|
|
for i in range(1, num_targets + 1):
|
|
target = {}
|
|
for j in range(1, shots_per_target + 1):
|
|
target[f'shot{j}'] = None
|
|
targets[str(i)] = target
|
|
|
|
results['participants'][player_id] = {
|
|
'name': player['name'],
|
|
'targets': targets,
|
|
'total_score': 0,
|
|
'completed': False
|
|
}
|
|
|
|
return results
|
|
|
|
|
|
class Scoring:
|
|
"""Score calculation and ranking"""
|
|
|
|
@staticmethod
|
|
def calculate_total_score(targets):
|
|
"""Calculate total score from targets, treating None as 0"""
|
|
total = 0
|
|
for target in targets.values():
|
|
for shot_key, shot_value in target.items():
|
|
if shot_key.startswith('shot') and shot_value is not None:
|
|
total += shot_value
|
|
return total
|
|
|
|
@staticmethod
|
|
def is_participant_completed(targets):
|
|
"""Check if a participant has completed all targets"""
|
|
for target in targets.values():
|
|
for shot_key, shot_value in target.items():
|
|
if shot_key.startswith('shot') and shot_value is None:
|
|
return False
|
|
return True
|
|
|
|
@staticmethod
|
|
def calculate_league_final_scores(league_data):
|
|
"""Calculate final league scores using best 4 tournaments"""
|
|
for participant_id, participant in league_data['participants'].items():
|
|
tournament_scores = []
|
|
|
|
# Get all tournament scores where player participated with tournament number
|
|
for result in participant['tournament_results']:
|
|
if result['participated']:
|
|
tournament_scores.append({
|
|
'tournament_number': result.get('tournament', result.get('league_tournament_number', 0)),
|
|
'score': result['score']
|
|
})
|
|
|
|
# Sort scores descending and take best 4
|
|
tournament_scores.sort(key=lambda x: x['score'], reverse=True)
|
|
best_scores = tournament_scores[:4] if len(tournament_scores) > 4 else tournament_scores
|
|
|
|
# Track which tournament was excluded (if any)
|
|
excluded_tournament = None
|
|
if len(tournament_scores) > 4:
|
|
excluded_tournament = tournament_scores[4]['tournament_number']
|
|
|
|
participant['final_score'] = sum(item['score'] for item in best_scores)
|
|
participant['tournaments_participated'] = len(tournament_scores)
|
|
participant['excluded_tournament'] = excluded_tournament
|
|
|
|
@staticmethod
|
|
def get_league_final_rankings(league_data):
|
|
"""Get final league rankings sorted by final score"""
|
|
participants = []
|
|
for player_id, data in league_data['participants'].items():
|
|
# Calculate total 10s across all tournaments
|
|
total_tens = sum(
|
|
result.get('tens_count', 0) for result in data['tournament_results']
|
|
if result.get('participated', False)
|
|
)
|
|
|
|
participants.append({
|
|
'id': player_id,
|
|
'name': data['name'],
|
|
'final_score': data['final_score'],
|
|
'total_score': data['total_score'],
|
|
'tournaments_participated': data['tournaments_participated'],
|
|
'joker_used': data['joker_used'],
|
|
'tournament_results': data['tournament_results'],
|
|
'total_tens': total_tens,
|
|
'excluded_tournament': data.get('excluded_tournament', None)
|
|
})
|
|
|
|
# Sort by final score (best 4 tournaments) descending, then by total 10s
|
|
participants.sort(key=lambda x: (x['final_score'], x['total_tens']), reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return participants
|
|
|
|
@staticmethod
|
|
def calculate_current_league_standings(league_data):
|
|
"""Calculate current league standings during active league"""
|
|
participants = []
|
|
for player_id, data in league_data['participants'].items():
|
|
tournament_scores = []
|
|
completed_tournaments = 0
|
|
total_tens = 0
|
|
|
|
for result in data['tournament_results']:
|
|
if result['participated']:
|
|
tournament_scores.append({
|
|
'tournament_number': result.get('tournament', result.get('league_tournament_number', 0)),
|
|
'score': result['score']
|
|
})
|
|
completed_tournaments += 1
|
|
total_tens += result.get('tens_count', 0)
|
|
|
|
# Current score is sum of all completed tournaments
|
|
current_total = sum(item['score'] for item in tournament_scores)
|
|
|
|
# For display, show what the final score would be if we took best 4 now
|
|
tournament_scores_sorted = sorted(tournament_scores, key=lambda x: x['score'], reverse=True)
|
|
projected_final = sum(item['score'] for item in tournament_scores_sorted[:4]) if len(tournament_scores_sorted) >= 4 else sum(item['score'] for item in tournament_scores_sorted)
|
|
|
|
# Track which tournament would be excluded (if we have more than 4)
|
|
excluded_tournament = None
|
|
if len(tournament_scores_sorted) > 4:
|
|
excluded_tournament = tournament_scores_sorted[4]['tournament_number']
|
|
|
|
participants.append({
|
|
'id': player_id,
|
|
'name': data['name'],
|
|
'current_total': current_total,
|
|
'projected_final': projected_final,
|
|
'tournaments_completed': completed_tournaments,
|
|
'joker_used': data['joker_used'],
|
|
'tournament_results': data['tournament_results'],
|
|
'total_tens': total_tens,
|
|
'excluded_tournament': excluded_tournament
|
|
})
|
|
|
|
# Sort by current total score (descending), then by total 10s
|
|
participants.sort(key=lambda x: (x['current_total'], x['total_tens']), reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return participants
|
|
|
|
|
|
class RoundManager:
|
|
"""Tournament round management"""
|
|
|
|
@staticmethod
|
|
def get_current_round_data(tournament_state):
|
|
"""Get current round data from tournament"""
|
|
if not tournament_state:
|
|
return None
|
|
|
|
current_round_num = tournament_state.get('current_round', 1)
|
|
|
|
# Find the current round
|
|
for round_data in tournament_state['rounds']:
|
|
if round_data['round_number'] == current_round_num:
|
|
return round_data
|
|
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_tournament_format_description(tournament_type):
|
|
"""Get tournament format description"""
|
|
formats = {
|
|
'20_targets': '20 Targets, 2 Shots Per Target',
|
|
'40_targets': '40 Targets, 2 Shots Per Target',
|
|
'4_targets': '4 Targets, 5 Shots Per Target'
|
|
}
|
|
return formats.get(tournament_type, 'Unknown Format')
|