2058 lines
78 KiB
Python
2058 lines
78 KiB
Python
from flask import Flask, render_template, request, redirect, jsonify
|
|
import json
|
|
import os
|
|
import random
|
|
import glob
|
|
from collections import defaultdict
|
|
from datetime import datetime
|
|
import re
|
|
|
|
app = Flask(__name__)
|
|
|
|
def is_mobile_device():
|
|
"""Check if the request is coming from a mobile device"""
|
|
user_agent = request.headers.get('User-Agent', '').lower()
|
|
mobile_patterns = [
|
|
r'android', r'iphone', r'ipad', r'ipod', r'blackberry',
|
|
r'iemobile', r'opera mini', r'mobile', r'tablet'
|
|
]
|
|
return any(re.search(pattern, user_agent) for pattern in mobile_patterns)
|
|
|
|
# Define streams globally so both routes can access them
|
|
STREAMS = [
|
|
{'name': 'Target1', 'url': 'http://192.168.0.134:9081'},
|
|
{'name': 'Target2', 'url': 'http://192.168.0.134:9082'},
|
|
{'name': 'Target3', 'url': 'http://192.168.0.134:9083'},
|
|
{'name': 'Target4', 'url': 'http://192.168.0.134:9084'},
|
|
{'name': 'Target5', 'url': 'http://192.168.0.134:9085'},
|
|
{'name': 'Target6', 'url': 'http://192.168.0.134:9086'},
|
|
]
|
|
|
|
# Settings file paths
|
|
SETTINGS_FILE = 'camera_settings.json'
|
|
PLAYERS_FILE = 'players.json'
|
|
TOURNAMENT_FILE = 'tournament_state.json'
|
|
RESULTS_FILE = 'tournament_results.json'
|
|
LEAGUE_FILE = 'league_state.json'
|
|
ARCHIVE_DIR = 'tournament_archives'
|
|
LEAGUE_ARCHIVE_DIR = 'league_archives'
|
|
|
|
# Default settings
|
|
DEFAULT_SETTINGS = {
|
|
'camera_titles': {
|
|
'1': 'Camera 1',
|
|
'2': 'Camera 2',
|
|
'3': 'Camera 3',
|
|
'4': 'Camera 4',
|
|
'5': 'Camera 5',
|
|
'6': 'Camera 6'
|
|
},
|
|
'display_options': {
|
|
'show_titles': True,
|
|
'title_size': 1.1
|
|
}
|
|
}
|
|
|
|
# Default players structure
|
|
DEFAULT_PLAYERS = {
|
|
'players': [
|
|
{'id': 1, 'name': 'Player 1', 'enabled': True},
|
|
{'id': 2, 'name': 'Player 2', 'enabled': True},
|
|
{'id': 3, 'name': 'Player 3', 'enabled': True},
|
|
{'id': 4, 'name': 'Player 4', 'enabled': True},
|
|
{'id': 5, 'name': 'Player 5', 'enabled': True},
|
|
{'id': 6, 'name': 'Player 6', 'enabled': True},
|
|
]
|
|
}
|
|
|
|
def load_settings():
|
|
"""Load settings from JSON file, create with defaults if not exists"""
|
|
try:
|
|
if os.path.exists(SETTINGS_FILE):
|
|
with open(SETTINGS_FILE, 'r') as f:
|
|
settings = json.load(f)
|
|
# Ensure all required keys exist (for backwards compatibility)
|
|
for key in DEFAULT_SETTINGS:
|
|
if key not in settings:
|
|
settings[key] = DEFAULT_SETTINGS[key]
|
|
for camera_id in DEFAULT_SETTINGS['camera_titles']:
|
|
if camera_id not in settings['camera_titles']:
|
|
settings['camera_titles'][camera_id] = DEFAULT_SETTINGS['camera_titles'][camera_id]
|
|
return settings
|
|
else:
|
|
return DEFAULT_SETTINGS.copy()
|
|
except (json.JSONDecodeError, IOError):
|
|
return DEFAULT_SETTINGS.copy()
|
|
|
|
def save_settings(settings):
|
|
"""Save settings to JSON file"""
|
|
try:
|
|
with open(SETTINGS_FILE, 'w') as f:
|
|
json.dump(settings, f, indent=2)
|
|
return True
|
|
except IOError:
|
|
return False
|
|
|
|
def load_players():
|
|
"""Load players from JSON file, create with defaults if not exists"""
|
|
try:
|
|
if os.path.exists(PLAYERS_FILE):
|
|
with open(PLAYERS_FILE, 'r') as f:
|
|
return json.load(f)
|
|
else:
|
|
save_players(DEFAULT_PLAYERS)
|
|
return DEFAULT_PLAYERS.copy()
|
|
except (json.JSONDecodeError, IOError):
|
|
return DEFAULT_PLAYERS.copy()
|
|
|
|
def save_players(players_data):
|
|
"""Save players to JSON file"""
|
|
try:
|
|
with open(PLAYERS_FILE, 'w') as f:
|
|
json.dump(players_data, f, indent=2)
|
|
return True
|
|
except IOError:
|
|
return False
|
|
|
|
def load_tournament_state():
|
|
"""Load tournament state from JSON file"""
|
|
try:
|
|
if os.path.exists(TOURNAMENT_FILE):
|
|
with open(TOURNAMENT_FILE, 'r') as f:
|
|
return json.load(f)
|
|
else:
|
|
return None
|
|
except (json.JSONDecodeError, IOError):
|
|
return None
|
|
|
|
def save_tournament_state(tournament_data):
|
|
"""Save tournament state to JSON file"""
|
|
try:
|
|
with open(TOURNAMENT_FILE, 'w') as f:
|
|
json.dump(tournament_data, f, indent=2)
|
|
return True
|
|
except IOError:
|
|
return False
|
|
|
|
def load_league_state():
|
|
"""Load league state from JSON file"""
|
|
try:
|
|
if os.path.exists(LEAGUE_FILE):
|
|
with open(LEAGUE_FILE, 'r') as f:
|
|
return json.load(f)
|
|
else:
|
|
return None
|
|
except (json.JSONDecodeError, IOError):
|
|
return None
|
|
|
|
def save_league_state(league_data):
|
|
"""Save league state to JSON file"""
|
|
try:
|
|
with open(LEAGUE_FILE, 'w') as f:
|
|
json.dump(league_data, f, indent=2)
|
|
return True
|
|
except IOError:
|
|
return False
|
|
|
|
def load_results():
|
|
"""Load results from JSON file"""
|
|
try:
|
|
if os.path.exists(RESULTS_FILE):
|
|
with open(RESULTS_FILE, 'r') as f:
|
|
return json.load(f)
|
|
else:
|
|
return None
|
|
except (json.JSONDecodeError, IOError):
|
|
return None
|
|
|
|
def save_results(results_data):
|
|
"""Save results to JSON file"""
|
|
try:
|
|
with open(RESULTS_FILE, 'w') as f:
|
|
json.dump(results_data, f, indent=2)
|
|
return True
|
|
except IOError:
|
|
return False
|
|
|
|
def archive_tournament(tournament_data, results_data):
|
|
"""Archive completed tournament data"""
|
|
try:
|
|
# Create archive directory if it doesn't exist
|
|
if not os.path.exists(ARCHIVE_DIR):
|
|
os.makedirs(ARCHIVE_DIR)
|
|
|
|
# Create filename with timestamp
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
archive_filename = f"tournament_{timestamp}.json"
|
|
archive_path = os.path.join(ARCHIVE_DIR, archive_filename)
|
|
|
|
# Combine tournament and results data
|
|
archive_data = {
|
|
'tournament': tournament_data,
|
|
'results': results_data,
|
|
'archived_at': datetime.now().isoformat()
|
|
}
|
|
|
|
# Save to archive
|
|
with open(archive_path, 'w') as f:
|
|
json.dump(archive_data, f, indent=2)
|
|
|
|
print(f"Tournament archived to: {archive_path}")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error archiving tournament: {e}")
|
|
return False
|
|
|
|
def archive_league(league_data):
|
|
"""Archive completed league data"""
|
|
try:
|
|
if not os.path.exists(LEAGUE_ARCHIVE_DIR):
|
|
os.makedirs(LEAGUE_ARCHIVE_DIR)
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
archive_filename = f"league_{timestamp}.json"
|
|
archive_path = os.path.join(LEAGUE_ARCHIVE_DIR, archive_filename)
|
|
|
|
archive_data = {
|
|
'league': league_data,
|
|
'archived_at': datetime.now().isoformat()
|
|
}
|
|
|
|
with open(archive_path, 'w') as f:
|
|
json.dump(archive_data, f, indent=2)
|
|
|
|
print(f"League archived to: {archive_path}")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error archiving league: {e}")
|
|
return False
|
|
|
|
def create_league(enabled_players, tournament_type):
|
|
"""Create a new league with 6 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': 6,
|
|
'current_tournament': 0, # Will be 1 when first tournament starts
|
|
'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, # Best 5 tournaments
|
|
'tournaments_participated': 0
|
|
}
|
|
|
|
return league_data
|
|
|
|
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), 6):
|
|
group = players_copy[i:i+6]
|
|
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
|
|
|
|
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')
|
|
num_targets = 40 if tournament_type == '40_targets' else 20
|
|
|
|
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'])
|
|
results['participants'][player_id] = {
|
|
'name': player['name'],
|
|
'targets': {str(i): {'shot1': None, 'shot2': None} for i in range(1, num_targets + 1)},
|
|
'total_score': 0,
|
|
'completed': False
|
|
}
|
|
|
|
return results
|
|
|
|
def calculate_total_score(targets):
|
|
"""Calculate total score from targets, treating None as 0"""
|
|
total = 0
|
|
for target in targets.values():
|
|
shot1 = target.get('shot1')
|
|
shot2 = target.get('shot2')
|
|
total += (shot1 if shot1 is not None else 0)
|
|
total += (shot2 if shot2 is not None else 0)
|
|
return total
|
|
|
|
def is_participant_completed(targets):
|
|
"""Check if a participant has completed all targets (all shots entered, including 0s)"""
|
|
for target in targets.values():
|
|
shot1 = target.get('shot1')
|
|
shot2 = target.get('shot2')
|
|
if shot1 is None or shot2 is None:
|
|
return False
|
|
return True
|
|
|
|
def calculate_league_final_scores(league_data):
|
|
"""Calculate final league scores using best 5 tournaments (joker system)"""
|
|
for participant_id, participant in league_data['participants'].items():
|
|
tournament_scores = []
|
|
|
|
# Get all tournament scores where player participated
|
|
for result in participant['tournament_results']:
|
|
if result['participated']:
|
|
tournament_scores.append(result['score'])
|
|
|
|
# Sort scores descending and take best 5 (or all if less than 6)
|
|
tournament_scores.sort(reverse=True)
|
|
best_scores = tournament_scores[:5] if len(tournament_scores) > 5 else tournament_scores
|
|
|
|
participant['final_score'] = sum(best_scores)
|
|
participant['tournaments_participated'] = len(tournament_scores)
|
|
|
|
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():
|
|
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']
|
|
})
|
|
|
|
# Sort by final score (best 5 tournaments) descending
|
|
participants.sort(key=lambda x: x['final_score'], reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return participants
|
|
|
|
def get_current_round_data():
|
|
"""Get current round data from tournament"""
|
|
tournament_state = load_tournament_state()
|
|
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
|
|
|
|
def calculate_current_league_standings(league_data):
|
|
"""Calculate current league standings during active league"""
|
|
participants = []
|
|
for player_id, data in league_data['participants'].items():
|
|
# Calculate current standings based on completed tournaments
|
|
tournament_scores = []
|
|
completed_tournaments = 0
|
|
|
|
for result in data['tournament_results']:
|
|
if result['participated']:
|
|
tournament_scores.append(result['score'])
|
|
completed_tournaments += 1
|
|
|
|
# Current score is sum of all completed tournaments
|
|
current_total = sum(tournament_scores)
|
|
|
|
# For display, show what the final score would be if we took best 5 now
|
|
tournament_scores.sort(reverse=True)
|
|
projected_final = sum(tournament_scores[:5]) if len(tournament_scores) >= 5 else sum(tournament_scores)
|
|
|
|
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']
|
|
})
|
|
|
|
# Sort by current total score (descending)
|
|
participants.sort(key=lambda x: x['current_total'], reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return participants
|
|
|
|
def get_league_current_standings(league_state):
|
|
"""Get current league standings including in-progress tournament"""
|
|
if not league_state:
|
|
return []
|
|
|
|
# Start with current league participants
|
|
participants = []
|
|
for player_id, data in league_state['participants'].items():
|
|
participant = {
|
|
'id': player_id,
|
|
'name': data['name'],
|
|
'total_score': data['total_score'],
|
|
'final_score': data['final_score'],
|
|
'tournaments_participated': data['tournaments_participated'],
|
|
'joker_used': data['joker_used'],
|
|
'tournament_results': data['tournament_results'],
|
|
'current_tournament_score': 0, # Score from current tournament
|
|
'current_tournament_participating': False
|
|
}
|
|
participants.append(participant)
|
|
|
|
# Add current tournament scores if available
|
|
current_results = load_results()
|
|
if current_results and not current_results.get('tournament_finished', False):
|
|
for player_id, result_data in current_results['participants'].items():
|
|
# Find this participant in our list
|
|
for participant in participants:
|
|
if participant['id'] == player_id:
|
|
participant['current_tournament_score'] = result_data['total_score']
|
|
participant['current_tournament_participating'] = True
|
|
break
|
|
|
|
# Sort by final score (best 5 tournaments) descending
|
|
participants.sort(key=lambda x: x['final_score'], reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return participants
|
|
|
|
# Add these functions after the existing helper functions in app.py
|
|
|
|
def get_archived_tournaments():
|
|
"""Get list of standalone archived tournaments (excluding league tournaments)"""
|
|
try:
|
|
if not os.path.exists(ARCHIVE_DIR):
|
|
return []
|
|
|
|
archives = []
|
|
for file_path in glob.glob(os.path.join(ARCHIVE_DIR, "tournament_*.json")):
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
# Extract metadata
|
|
filename = os.path.basename(file_path)
|
|
archived_at = data.get('archived_at', 'Unknown')
|
|
tournament_data = data.get('tournament', {})
|
|
results_data = data.get('results', {})
|
|
|
|
# Skip tournaments that are part of a league
|
|
if tournament_data.get('league_tournament_number') or results_data.get('league_tournament_number'):
|
|
continue
|
|
|
|
archive_info = {
|
|
'filename': filename,
|
|
'filepath': file_path,
|
|
'archived_at': archived_at,
|
|
'created_at': tournament_data.get('created_at', 'Unknown'),
|
|
'tournament_type': tournament_data.get('tournament_type', '20_targets'),
|
|
'total_players': tournament_data.get('total_players', 0),
|
|
'total_rounds': tournament_data.get('total_rounds', 0),
|
|
'tournament_finished': results_data.get('tournament_finished', False),
|
|
'participants_count': len(results_data.get('participants', {}))
|
|
}
|
|
|
|
archives.append(archive_info)
|
|
except (json.JSONDecodeError, IOError) as e:
|
|
print(f"Error reading archive {file_path}: {e}")
|
|
continue
|
|
|
|
# Sort by archived date (newest first)
|
|
archives.sort(key=lambda x: x['archived_at'], reverse=True)
|
|
return archives
|
|
except Exception as e:
|
|
print(f"Error getting archived tournaments: {e}")
|
|
return []
|
|
|
|
def get_archived_leagues():
|
|
"""Get list of all archived leagues"""
|
|
try:
|
|
if not os.path.exists(LEAGUE_ARCHIVE_DIR):
|
|
return []
|
|
|
|
archives = []
|
|
for file_path in glob.glob(os.path.join(LEAGUE_ARCHIVE_DIR, "league_*.json")):
|
|
try:
|
|
with open(file_path, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
filename = os.path.basename(file_path)
|
|
archived_at = data.get('archived_at', 'Unknown')
|
|
league_data = data.get('league', {})
|
|
|
|
archive_info = {
|
|
'filename': filename,
|
|
'filepath': file_path,
|
|
'archived_at': archived_at,
|
|
'created_at': league_data.get('created_at', 'Unknown'),
|
|
'league_id': league_data.get('league_id', 'Unknown'),
|
|
'tournament_type': league_data.get('tournament_type', '20_targets'),
|
|
'total_tournaments': league_data.get('total_tournaments', 6),
|
|
'participants_count': len(league_data.get('participants', {})),
|
|
'league_finished': league_data.get('league_finished', False),
|
|
'completed_tournaments': len(league_data.get('completed_tournaments', []))
|
|
}
|
|
|
|
archives.append(archive_info)
|
|
except (json.JSONDecodeError, IOError) as e:
|
|
print(f"Error reading league archive {file_path}: {e}")
|
|
continue
|
|
|
|
# Sort by archived date (newest first)
|
|
archives.sort(key=lambda x: x['archived_at'], reverse=True)
|
|
return archives
|
|
except Exception as e:
|
|
print(f"Error getting archived leagues: {e}")
|
|
return []
|
|
|
|
def load_archive_file(filepath):
|
|
"""Load a specific archive file"""
|
|
try:
|
|
with open(filepath, 'r') as f:
|
|
return json.load(f)
|
|
except (json.JSONDecodeError, IOError) as e:
|
|
print(f"Error loading archive file {filepath}: {e}")
|
|
return None
|
|
|
|
def analyze_player_performance(player_id, archives_data):
|
|
"""Analyze performance of a specific player across all archives"""
|
|
player_stats = {
|
|
'total_tournaments': 0,
|
|
'total_leagues': 0,
|
|
'tournament_scores': [],
|
|
'league_scores': [],
|
|
'best_tournament_score': 0,
|
|
'worst_tournament_score': float('inf'),
|
|
'average_tournament_score': 0,
|
|
'total_shots_fired': 0,
|
|
'performance_trend': [],
|
|
'tournament_history': [],
|
|
'league_history': []
|
|
}
|
|
|
|
# Process tournament archives
|
|
for archive in archives_data.get('tournaments', []):
|
|
try:
|
|
data = load_archive_file(archive['filepath'])
|
|
if not data:
|
|
continue
|
|
|
|
results = data.get('results', {})
|
|
participants = results.get('participants', {})
|
|
|
|
if str(player_id) in participants:
|
|
participant = participants[str(player_id)]
|
|
score = participant.get('total_score', 0)
|
|
completed = participant.get('completed', False)
|
|
|
|
if completed:
|
|
player_stats['total_tournaments'] += 1
|
|
player_stats['tournament_scores'].append(score)
|
|
|
|
if score > player_stats['best_tournament_score']:
|
|
player_stats['best_tournament_score'] = score
|
|
if score < player_stats['worst_tournament_score']:
|
|
player_stats['worst_tournament_score'] = score
|
|
|
|
# Count shots fired
|
|
targets = participant.get('targets', {})
|
|
shots_in_tournament = 0
|
|
for target in targets.values():
|
|
if target.get('shot1') is not None:
|
|
shots_in_tournament += 1
|
|
if target.get('shot2') is not None:
|
|
shots_in_tournament += 1
|
|
|
|
player_stats['total_shots_fired'] += shots_in_tournament
|
|
|
|
# Add to history
|
|
player_stats['tournament_history'].append({
|
|
'date': archive['archived_at'],
|
|
'score': score,
|
|
'tournament_type': archive['tournament_type'],
|
|
'completed': completed,
|
|
'shots_fired': shots_in_tournament
|
|
})
|
|
except Exception as e:
|
|
print(f"Error analyzing tournament archive: {e}")
|
|
continue
|
|
|
|
# Process league archives
|
|
for archive in archives_data.get('leagues', []):
|
|
try:
|
|
data = load_archive_file(archive['filepath'])
|
|
if not data:
|
|
continue
|
|
|
|
league = data.get('league', {})
|
|
participants = league.get('participants', {})
|
|
|
|
if str(player_id) in participants:
|
|
participant = participants[str(player_id)]
|
|
final_score = participant.get('final_score', 0)
|
|
total_score = participant.get('total_score', 0)
|
|
tournaments_participated = participant.get('tournaments_participated', 0)
|
|
|
|
player_stats['total_leagues'] += 1
|
|
player_stats['league_scores'].append(final_score)
|
|
|
|
player_stats['league_history'].append({
|
|
'date': archive['archived_at'],
|
|
'final_score': final_score,
|
|
'total_score': total_score,
|
|
'tournaments_participated': tournaments_participated,
|
|
'joker_used': participant.get('joker_used', False),
|
|
'tournament_results': participant.get('tournament_results', [])
|
|
})
|
|
except Exception as e:
|
|
print(f"Error analyzing league archive: {e}")
|
|
continue
|
|
|
|
# Calculate averages and trends
|
|
if player_stats['tournament_scores']:
|
|
player_stats['average_tournament_score'] = sum(player_stats['tournament_scores']) / len(player_stats['tournament_scores'])
|
|
|
|
# Performance trend (simple moving average)
|
|
if len(player_stats['tournament_scores']) >= 3:
|
|
for i in range(2, len(player_stats['tournament_scores'])):
|
|
avg = sum(player_stats['tournament_scores'][i-2:i+1]) / 3
|
|
player_stats['performance_trend'].append(avg)
|
|
|
|
if player_stats['worst_tournament_score'] == float('inf'):
|
|
player_stats['worst_tournament_score'] = 0
|
|
|
|
# Sort histories by date (newest first)
|
|
player_stats['tournament_history'].sort(key=lambda x: x['date'], reverse=True)
|
|
player_stats['league_history'].sort(key=lambda x: x['date'], reverse=True)
|
|
|
|
return player_stats
|
|
|
|
def get_all_players_from_archives():
|
|
"""Get all players that appear in any archive"""
|
|
all_players = {}
|
|
|
|
# Get current players first
|
|
current_players = load_players()
|
|
for player in current_players['players']:
|
|
all_players[str(player['id'])] = {
|
|
'id': player['id'],
|
|
'name': player['name'],
|
|
'current_player': True
|
|
}
|
|
|
|
# Add players from tournament archives
|
|
tournaments = get_archived_tournaments()
|
|
for archive in tournaments:
|
|
try:
|
|
data = load_archive_file(archive['filepath'])
|
|
if data and 'results' in data:
|
|
participants = data['results'].get('participants', {})
|
|
for player_id, participant in participants.items():
|
|
if player_id not in all_players:
|
|
all_players[player_id] = {
|
|
'id': int(player_id),
|
|
'name': participant.get('name', f'Player {player_id}'),
|
|
'current_player': False
|
|
}
|
|
except Exception as e:
|
|
print(f"Error processing tournament archive: {e}")
|
|
continue
|
|
|
|
# Add players from league archives
|
|
leagues = get_archived_leagues()
|
|
for archive in leagues:
|
|
try:
|
|
data = load_archive_file(archive['filepath'])
|
|
if data and 'league' in data:
|
|
participants = data['league'].get('participants', {})
|
|
for player_id, participant in participants.items():
|
|
if player_id not in all_players:
|
|
all_players[player_id] = {
|
|
'id': int(player_id),
|
|
'name': participant.get('name', f'Player {player_id}'),
|
|
'current_player': False
|
|
}
|
|
except Exception as e:
|
|
print(f"Error processing league archive: {e}")
|
|
continue
|
|
|
|
# Convert to list and sort by name
|
|
players_list = list(all_players.values())
|
|
players_list.sort(key=lambda x: x['name'])
|
|
|
|
return players_list
|
|
|
|
# Add these routes after the existing routes in app.py
|
|
|
|
# Add this to your app.py file to integrate the modern archive system
|
|
|
|
# Replace your existing archive routes with these updated ones:
|
|
|
|
@app.route('/archive')
|
|
def archive_index():
|
|
"""Archive index page with updated styling"""
|
|
if is_mobile_device():
|
|
return redirect('/mobile/archive')
|
|
|
|
tournaments = get_archived_tournaments() # Now only returns standalone tournaments
|
|
leagues = get_archived_leagues()
|
|
|
|
# Calculate overview stats
|
|
total_tournaments = len(tournaments)
|
|
total_leagues = len(leagues)
|
|
|
|
# Get total players from current players list
|
|
players_data = load_players()
|
|
total_players = len([p for p in players_data['players'] if p['enabled']])
|
|
|
|
# Calculate total competitions (standalone tournaments + completed leagues)
|
|
total_matches = total_tournaments + total_leagues
|
|
|
|
stats = {
|
|
'total_tournaments': total_tournaments,
|
|
'total_leagues': total_leagues,
|
|
'total_players': total_players,
|
|
'total_matches': total_matches
|
|
}
|
|
|
|
# Use the new template
|
|
return render_template('modern_archive_index.html',
|
|
tournaments=tournaments,
|
|
leagues=leagues,
|
|
stats=stats)
|
|
|
|
@app.route('/archive/player-analysis')
|
|
def player_analysis():
|
|
"""Modern Player analysis page"""
|
|
if is_mobile_device():
|
|
return redirect('/mobile/archive/player-analysis')
|
|
|
|
all_players = get_all_players_from_archives()
|
|
|
|
# Calculate overview stats
|
|
total_players = len(all_players)
|
|
active_players = len([p for p in all_players if p['current_player']])
|
|
|
|
# Get average tournaments and top score from archives
|
|
avg_tournaments = 0
|
|
top_score = 0
|
|
|
|
if all_players:
|
|
total_tournament_count = 0
|
|
max_score = 0
|
|
|
|
for player in all_players:
|
|
# Get basic stats for each player
|
|
archives_data = {
|
|
'tournaments': get_archived_tournaments(),
|
|
'leagues': get_archived_leagues()
|
|
}
|
|
player_stats = analyze_player_performance(player['id'], archives_data)
|
|
total_tournament_count += player_stats['total_tournaments']
|
|
if player_stats['best_tournament_score'] > max_score:
|
|
max_score = player_stats['best_tournament_score']
|
|
|
|
avg_tournaments = total_tournament_count // total_players if total_players else 0
|
|
top_score = max_score
|
|
|
|
overview_stats = {
|
|
'total_players': total_players,
|
|
'active_players': active_players,
|
|
'avg_tournaments': avg_tournaments,
|
|
'top_score': top_score
|
|
}
|
|
|
|
return render_template('modern_player_analysis.html',
|
|
players=all_players,
|
|
overview_stats=overview_stats)
|
|
|
|
@app.route('/archive/player/<int:player_id>')
|
|
def view_player_stats(player_id):
|
|
"""Modern Player stats page"""
|
|
if is_mobile_device():
|
|
return redirect(f'/mobile/archive/player/{player_id}')
|
|
|
|
all_players = get_all_players_from_archives()
|
|
player_info = next((p for p in all_players if p['id'] == player_id), None)
|
|
|
|
if not player_info:
|
|
return redirect('/archive/player-analysis')
|
|
|
|
# Get archives data
|
|
archives_data = {
|
|
'tournaments': get_archived_tournaments(),
|
|
'leagues': get_archived_leagues()
|
|
}
|
|
|
|
# Analyze player performance
|
|
player_stats = analyze_player_performance(player_id, archives_data)
|
|
|
|
return render_template('modern_player_stats.html',
|
|
player=player_info,
|
|
stats=player_stats)
|
|
|
|
# Enhanced API endpoints for the modern archive system
|
|
|
|
@app.route('/api/archive/stats', methods=['GET'])
|
|
def api_get_archive_stats():
|
|
"""API endpoint to get archive overview statistics"""
|
|
try:
|
|
tournaments = get_archived_tournaments()
|
|
leagues = get_archived_leagues()
|
|
players_data = load_players()
|
|
|
|
# Calculate activity over time (last 6 months)
|
|
activity_data = {
|
|
'labels': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
|
'tournaments': [2, 4, 3, 5, 6, 4], # This should be calculated from actual data
|
|
'leagues': [1, 1, 2, 1, 2, 1]
|
|
}
|
|
|
|
# Calculate tournament type distribution
|
|
type_distribution = {'20_targets': 0, '40_targets': 0}
|
|
for tournament in tournaments:
|
|
tournament_type = tournament.get('tournament_type', '20_targets')
|
|
type_distribution[tournament_type] += 1
|
|
|
|
for league in leagues:
|
|
league_type = league.get('tournament_type', '20_targets')
|
|
type_distribution[league_type] += 1
|
|
|
|
stats = {
|
|
'overview': {
|
|
'total_tournaments': len(tournaments),
|
|
'total_leagues': len(leagues),
|
|
'total_players': len([p for p in players_data['players'] if p['enabled']]),
|
|
'total_matches': len(tournaments) + sum(l.get('completed_tournaments', 0) for l in leagues)
|
|
},
|
|
'activity_data': activity_data,
|
|
'type_distribution': {
|
|
'labels': ['20 Targets', '40 Targets'],
|
|
'data': [type_distribution['20_targets'], type_distribution['40_targets']]
|
|
}
|
|
}
|
|
|
|
return jsonify({'status': 'success', 'stats': stats})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/archive/player/<int:player_id>/performance', methods=['GET'])
|
|
def api_get_player_performance(player_id):
|
|
"""API endpoint to get player performance data for charts"""
|
|
try:
|
|
archives_data = {
|
|
'tournaments': get_archived_tournaments(),
|
|
'leagues': get_archived_leagues()
|
|
}
|
|
|
|
player_stats = analyze_player_performance(player_id, archives_data)
|
|
|
|
# Prepare chart data
|
|
performance_data = {
|
|
'trend': {
|
|
'labels': [f'T{i+1}' for i in range(len(player_stats['tournament_scores']))],
|
|
'data': player_stats['tournament_scores']
|
|
},
|
|
'distribution': {
|
|
'labels': ['0-50', '51-60', '61-70', '71-80', '81-90', '91-100'],
|
|
'data': [0, 0, 0, 0, 0, 0]
|
|
}
|
|
}
|
|
|
|
# Calculate score distribution
|
|
for score in player_stats['tournament_scores']:
|
|
if score <= 50:
|
|
performance_data['distribution']['data'][0] += 1
|
|
elif score <= 60:
|
|
performance_data['distribution']['data'][1] += 1
|
|
elif score <= 70:
|
|
performance_data['distribution']['data'][2] += 1
|
|
elif score <= 80:
|
|
performance_data['distribution']['data'][3] += 1
|
|
elif score <= 90:
|
|
performance_data['distribution']['data'][4] += 1
|
|
else:
|
|
performance_data['distribution']['data'][5] += 1
|
|
|
|
return jsonify({'status': 'success', 'performance': performance_data})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/archive/players/with-stats', methods=['GET'])
|
|
def api_get_players_with_stats():
|
|
"""API endpoint to get all players with their basic stats"""
|
|
try:
|
|
all_players = get_all_players_from_archives()
|
|
archives_data = {
|
|
'tournaments': get_archived_tournaments(),
|
|
'leagues': get_archived_leagues()
|
|
}
|
|
|
|
players_with_stats = []
|
|
for player in all_players:
|
|
player_stats = analyze_player_performance(player['id'], archives_data)
|
|
|
|
players_with_stats.append({
|
|
'id': player['id'],
|
|
'name': player['name'],
|
|
'current_player': player['current_player'],
|
|
'stats': {
|
|
'total_tournaments': player_stats['total_tournaments'],
|
|
'total_leagues': player_stats['total_leagues'],
|
|
'best_tournament_score': player_stats['best_tournament_score'],
|
|
'average_tournament_score': player_stats['average_tournament_score'],
|
|
'total_shots_fired': player_stats['total_shots_fired'],
|
|
'performance_trend': player_stats['tournament_scores'][-8:] if len(player_stats['tournament_scores']) >= 8 else player_stats['tournament_scores'] # Last 8 tournaments for mini chart
|
|
}
|
|
})
|
|
|
|
return jsonify({'status': 'success', 'players': players_with_stats})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
# Add these routes to handle clicking on archived tournaments/leagues
|
|
# These will load the archive data and show it in your existing result page format
|
|
|
|
@app.route('/archive/tournament/<filename>')
|
|
def view_archived_tournament(filename):
|
|
"""View archived tournament in results format"""
|
|
if is_mobile_device():
|
|
return redirect(f'/mobile/archive/tournament/{filename}')
|
|
|
|
filepath = os.path.join(ARCHIVE_DIR, filename)
|
|
data = load_archive_file(filepath)
|
|
|
|
if not data:
|
|
return redirect('/archive')
|
|
|
|
tournament_data = data.get('tournament', {})
|
|
results_data = data.get('results', {})
|
|
|
|
# Process results for display
|
|
participants = []
|
|
for player_id, participant_data in results_data.get('participants', {}).items():
|
|
participants.append({
|
|
'id': player_id,
|
|
'name': participant_data['name'],
|
|
'total_score': participant_data['total_score'],
|
|
'completed': participant_data['completed'],
|
|
'targets': participant_data.get('targets', {})
|
|
})
|
|
|
|
# Sort by score (descending)
|
|
participants.sort(key=lambda x: x['total_score'], reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
# Use the existing results display template but with archived data
|
|
return render_template('results_display.html',
|
|
results=results_data,
|
|
participants=participants,
|
|
archived=True,
|
|
archive_info={
|
|
'filename': filename,
|
|
'archived_at': data.get('archived_at'),
|
|
'tournament_type': tournament_data.get('tournament_type', '20_targets')
|
|
})
|
|
|
|
@app.route('/archive/league/<filename>')
|
|
def view_archived_league(filename):
|
|
"""View archived league in results format"""
|
|
if is_mobile_device():
|
|
return redirect(f'/mobile/archive/league/{filename}')
|
|
|
|
filepath = os.path.join(LEAGUE_ARCHIVE_DIR, filename)
|
|
data = load_archive_file(filepath)
|
|
|
|
if not data:
|
|
return redirect('/archive')
|
|
|
|
league_data = data.get('league', {})
|
|
|
|
# Process league results using your existing function
|
|
calculate_league_final_scores(league_data)
|
|
participants = get_league_final_rankings(league_data)
|
|
|
|
# Use the existing league results display template but with archived data
|
|
return render_template('league_scoreboard_display.html',
|
|
league=league_data,
|
|
participants=participants,
|
|
archived=True,
|
|
archive_info={
|
|
'filename': filename,
|
|
'archived_at': data.get('archived_at'),
|
|
'tournament_type': league_data.get('tournament_type', '20_targets')
|
|
})
|
|
|
|
# Mobile versions
|
|
@app.route('/mobile/archive/tournament/<filename>')
|
|
def mobile_view_archived_tournament(filename):
|
|
"""Mobile view of archived tournament"""
|
|
filepath = os.path.join(ARCHIVE_DIR, filename)
|
|
data = load_archive_file(filepath)
|
|
|
|
if not data:
|
|
return redirect('/mobile/archive')
|
|
|
|
tournament_data = data.get('tournament', {})
|
|
results_data = data.get('results', {})
|
|
|
|
# Process results for display
|
|
participants = []
|
|
for player_id, participant_data in results_data.get('participants', {}).items():
|
|
participants.append({
|
|
'id': player_id,
|
|
'name': participant_data['name'],
|
|
'total_score': participant_data['total_score'],
|
|
'completed': participant_data['completed']
|
|
})
|
|
|
|
# Sort by score (descending)
|
|
participants.sort(key=lambda x: x['total_score'], reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return render_template('mobile_results.html',
|
|
results=results_data,
|
|
participants=participants,
|
|
show_league_results=False,
|
|
show_tournament_results=True,
|
|
tournament_active=False,
|
|
league=None,
|
|
tournament_type=results_data.get('tournament_type', '20_targets'),
|
|
archived=True,
|
|
archive_info={
|
|
'filename': filename,
|
|
'archived_at': data.get('archived_at')
|
|
})
|
|
|
|
@app.route('/mobile/archive/league/<filename>')
|
|
def mobile_view_archived_league(filename):
|
|
"""Mobile view of archived league"""
|
|
filepath = os.path.join(LEAGUE_ARCHIVE_DIR, filename)
|
|
data = load_archive_file(filepath)
|
|
|
|
if not data:
|
|
return redirect('/mobile/archive')
|
|
|
|
league_data = data.get('league', {})
|
|
|
|
# Process league results
|
|
calculate_league_final_scores(league_data)
|
|
participants = get_league_final_rankings(league_data)
|
|
|
|
return render_template('mobile_results.html',
|
|
league=league_data,
|
|
participants=participants,
|
|
show_league_results=True,
|
|
show_tournament_results=False,
|
|
tournament_active=False,
|
|
results=None,
|
|
archived=True,
|
|
archive_info={
|
|
'filename': filename,
|
|
'archived_at': data.get('archived_at')
|
|
})
|
|
# MAIN ROUTES
|
|
@app.route('/')
|
|
def index():
|
|
# Redirect mobile users to mobile menu
|
|
if is_mobile_device():
|
|
return redirect('/mobile')
|
|
|
|
# Desktop users get the regular dashboard
|
|
settings = load_settings()
|
|
|
|
# Check if tournament is active and get current round players
|
|
current_round_data = get_current_round_data()
|
|
|
|
# If tournament is active, override camera titles with player names
|
|
if current_round_data:
|
|
tournament_titles = {}
|
|
players = current_round_data['players']
|
|
|
|
for i in range(6): # Always 6 positions
|
|
if i < len(players):
|
|
tournament_titles[str(i + 1)] = players[i]['name']
|
|
else:
|
|
tournament_titles[str(i + 1)] = 'Empty'
|
|
|
|
# Create a copy of settings with tournament titles
|
|
display_settings = settings.copy()
|
|
display_settings['camera_titles'] = tournament_titles
|
|
display_settings['tournament_active'] = True
|
|
|
|
tournament_state = load_tournament_state()
|
|
display_settings['current_round'] = tournament_state.get('current_round', 1)
|
|
display_settings['total_rounds'] = tournament_state.get('total_rounds', 1)
|
|
|
|
# Add league info if available
|
|
league_state = load_league_state()
|
|
if league_state:
|
|
display_settings['league_active'] = True
|
|
display_settings['league_tournament'] = league_state.get('current_tournament', 1)
|
|
display_settings['league_total'] = league_state.get('total_tournaments', 6)
|
|
else:
|
|
display_settings = settings.copy()
|
|
display_settings['tournament_active'] = False
|
|
display_settings['current_round'] = 1
|
|
display_settings['total_rounds'] = 1
|
|
display_settings['league_active'] = False
|
|
|
|
return render_template('index.html', streams=STREAMS, settings=display_settings)
|
|
|
|
# MOBILE ROUTES
|
|
@app.route('/mobile')
|
|
def mobile_menu():
|
|
"""Mobile main menu page"""
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
results = load_results()
|
|
|
|
tournament_active = tournament_state is not None
|
|
league_active = league_state is not None and not league_state.get('league_finished', False)
|
|
results_available = results is not None and results.get('tournament_finished', False)
|
|
league_results_available = league_state is not None and league_state.get('league_finished', False)
|
|
|
|
return render_template('mobile_menu.html',
|
|
tournament_active=tournament_active,
|
|
league_active=league_active,
|
|
tournament_state=tournament_state,
|
|
league_state=league_state,
|
|
results_available=results_available,
|
|
league_results_available=league_results_available)
|
|
|
|
@app.route('/mobile/streams')
|
|
def mobile_streams():
|
|
"""Mobile streams page"""
|
|
settings = load_settings()
|
|
tournament_state = load_tournament_state()
|
|
|
|
# Check if tournament is active
|
|
tournament_active = tournament_state is not None
|
|
current_round_data = get_current_round_data() if tournament_active else None
|
|
|
|
return render_template('mobile_streams.html',
|
|
streams=STREAMS,
|
|
settings=settings,
|
|
tournament_active=tournament_active,
|
|
current_round_data=current_round_data,
|
|
tournament_state=tournament_state)
|
|
|
|
@app.route('/mobile/draft')
|
|
def mobile_draft():
|
|
"""Mobile tournament draft page"""
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
if not tournament_state:
|
|
return redirect('/mobile')
|
|
|
|
return render_template('mobile_draft.html',
|
|
tournament=tournament_state,
|
|
league=league_state)
|
|
|
|
|
|
@app.route('/mobile/results')
|
|
def mobile_results():
|
|
"""Mobile results page"""
|
|
league_state = load_league_state()
|
|
results = load_results()
|
|
|
|
# Priority 1: Show league results if there's an active or finished league
|
|
if league_state:
|
|
if league_state.get('league_finished', False):
|
|
# Show final league results
|
|
calculate_league_final_scores(league_state)
|
|
participants = get_league_final_rankings(league_state)
|
|
|
|
return render_template('mobile_results.html',
|
|
league=league_state,
|
|
participants=participants,
|
|
show_league_results=True,
|
|
show_tournament_results=False,
|
|
tournament_active=False,
|
|
results=None) # No individual tournament results
|
|
else:
|
|
# Show ongoing league scoreboard
|
|
calculate_league_final_scores(league_state)
|
|
participants = get_league_current_standings(league_state)
|
|
|
|
# Check if there's a current tournament
|
|
tournament_state = load_tournament_state()
|
|
tournament_active = tournament_state is not None
|
|
current_tournament_results = results
|
|
|
|
return render_template('mobile_league_results.html',
|
|
league=league_state,
|
|
participants=participants,
|
|
show_league_results=True,
|
|
show_tournament_results=False,
|
|
tournament_active=tournament_active,
|
|
tournament_state=tournament_state,
|
|
current_tournament_results=current_tournament_results)
|
|
|
|
# Priority 2: Show individual tournament results (standalone tournament)
|
|
elif results and results.get('tournament_finished', False):
|
|
participants = []
|
|
for player_id, data in results['participants'].items():
|
|
participants.append({
|
|
'id': player_id,
|
|
'name': data['name'],
|
|
'total_score': data['total_score'],
|
|
'completed': data['completed']
|
|
})
|
|
|
|
# Sort by score (descending)
|
|
participants.sort(key=lambda x: x['total_score'], reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return render_template('mobile_results.html',
|
|
results=results, # ← FIXED: Pass as 'results'
|
|
participants=participants,
|
|
show_league_results=False,
|
|
show_tournament_results=True,
|
|
tournament_active=False,
|
|
league=None,
|
|
tournament_type=results.get('tournament_type', '20_targets'))
|
|
else:
|
|
return redirect('/mobile')
|
|
|
|
# DESKTOP ROUTES
|
|
@app.route('/fullscreen/<int:camera_id>')
|
|
def fullscreen(camera_id):
|
|
# Get the camera stream data
|
|
if 1 <= camera_id <= len(STREAMS):
|
|
settings = load_settings()
|
|
stream = STREAMS[camera_id - 1]
|
|
|
|
# Check if tournament is active for title
|
|
current_round_data = get_current_round_data()
|
|
if current_round_data and camera_id <= len(current_round_data['players']):
|
|
camera_title = current_round_data['players'][camera_id - 1]['name']
|
|
else:
|
|
camera_title = settings['camera_titles'].get(str(camera_id), f'Camera {camera_id}')
|
|
|
|
custom_title = request.args.get('title', camera_title)
|
|
|
|
return render_template('fullscreen.html',
|
|
stream=stream,
|
|
camera_id=camera_id,
|
|
title=custom_title,
|
|
settings=settings)
|
|
else:
|
|
return redirect('/')
|
|
|
|
@app.route('/tournament')
|
|
def tournament():
|
|
"""Tournament management page"""
|
|
# Check if mobile device
|
|
if is_mobile_device():
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
if tournament_state:
|
|
# Mobile users with active tournament go to draft view
|
|
return redirect('/mobile/draft')
|
|
else:
|
|
# Mobile users without tournament go to mobile menu
|
|
return redirect('/mobile')
|
|
|
|
# Desktop users get full tournament management
|
|
players_data = load_players()
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
|
|
return render_template('tournament.html',
|
|
players=players_data['players'],
|
|
tournament_state=tournament_state,
|
|
league_state=league_state)
|
|
|
|
@app.route('/tournament/draft')
|
|
def tournament_draft():
|
|
"""Tournament draft page"""
|
|
# Redirect mobile users to mobile draft
|
|
if is_mobile_device():
|
|
return redirect('/mobile/draft')
|
|
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
if not tournament_state:
|
|
return redirect('/tournament')
|
|
|
|
return render_template('draft.html',
|
|
tournament=tournament_state,
|
|
league=league_state)
|
|
|
|
@app.route('/results/calculator')
|
|
def results_calculator():
|
|
"""Results calculator page (desktop only)"""
|
|
if is_mobile_device():
|
|
return redirect('/mobile/streams')
|
|
|
|
tournament_state = load_tournament_state()
|
|
if not tournament_state:
|
|
return redirect('/tournament')
|
|
|
|
# Get or create results structure
|
|
results = load_results()
|
|
if not results:
|
|
results = create_results_structure(tournament_state)
|
|
save_results(results)
|
|
|
|
return render_template('results_calculator.html',
|
|
tournament=tournament_state,
|
|
results=results)
|
|
|
|
@app.route('/results')
|
|
def results_display():
|
|
"""Results display page"""
|
|
# Redirect mobile users to mobile results
|
|
if is_mobile_device():
|
|
return redirect('/mobile/results')
|
|
|
|
league_state = load_league_state()
|
|
results = load_results()
|
|
|
|
# Priority 1: Show league results if there's an active or finished league
|
|
if league_state:
|
|
if league_state.get('league_finished', False):
|
|
# Show final league results
|
|
calculate_league_final_scores(league_state)
|
|
participants = get_league_final_rankings(league_state)
|
|
|
|
return render_template('league_scoreboard_display.html',
|
|
league=league_state,
|
|
participants=participants)
|
|
else:
|
|
# Show ongoing league scoreboard
|
|
calculate_league_final_scores(league_state) # Calculate current standings
|
|
participants = get_league_current_standings(league_state) # Use the helper function
|
|
|
|
# Add tournament information
|
|
tournament_state = load_tournament_state()
|
|
current_tournament_results = results
|
|
|
|
return render_template('league_scoreboard_display.html',
|
|
league=league_state,
|
|
participants=participants,
|
|
tournament_state=tournament_state,
|
|
current_tournament_results=current_tournament_results)
|
|
|
|
# Priority 2: Show individual tournament results (standalone tournament)
|
|
elif results and results.get('tournament_finished', False):
|
|
participants = []
|
|
for player_id, data in results['participants'].items():
|
|
participants.append({
|
|
'id': player_id,
|
|
'name': data['name'],
|
|
'total_score': data['total_score'],
|
|
'completed': data['completed']
|
|
})
|
|
|
|
# Sort by score (descending)
|
|
participants.sort(key=lambda x: x['total_score'], reverse=True)
|
|
|
|
# Add rankings
|
|
for i, participant in enumerate(participants):
|
|
participant['rank'] = i + 1
|
|
|
|
return render_template('results_display.html',
|
|
results=results,
|
|
participants=participants)
|
|
else:
|
|
return redirect('/tournament')
|
|
|
|
# API Routes
|
|
@app.route('/api/settings', methods=['GET'])
|
|
def get_settings():
|
|
"""API endpoint to get current settings"""
|
|
settings = load_settings()
|
|
return jsonify(settings)
|
|
|
|
@app.route('/api/settings', methods=['POST'])
|
|
def update_settings():
|
|
"""API endpoint to update settings"""
|
|
try:
|
|
new_settings = request.get_json()
|
|
|
|
# Load current settings
|
|
current_settings = load_settings()
|
|
|
|
# Update only provided fields
|
|
if 'camera_titles' in new_settings:
|
|
current_settings['camera_titles'].update(new_settings['camera_titles'])
|
|
|
|
if 'display_options' in new_settings:
|
|
current_settings['display_options'].update(new_settings['display_options'])
|
|
|
|
# Save updated settings
|
|
if save_settings(current_settings):
|
|
return jsonify({'status': 'success', 'settings': current_settings})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save settings'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/players', methods=['GET'])
|
|
def get_players():
|
|
"""API endpoint to get all players"""
|
|
players_data = load_players()
|
|
return jsonify(players_data)
|
|
|
|
@app.route('/api/players', methods=['POST'])
|
|
def update_players():
|
|
"""API endpoint to update players"""
|
|
try:
|
|
players_data = request.get_json()
|
|
|
|
if save_players(players_data):
|
|
return jsonify({'status': 'success', 'players': players_data})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save players'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/players/add', methods=['POST'])
|
|
def add_player():
|
|
"""API endpoint to add a new player"""
|
|
try:
|
|
data = request.get_json()
|
|
name = data.get('name', '').strip()
|
|
|
|
if not name:
|
|
return jsonify({'status': 'error', 'message': 'Player name is required'}), 400
|
|
|
|
players_data = load_players()
|
|
|
|
# Find next available ID
|
|
existing_ids = [p['id'] for p in players_data['players']]
|
|
new_id = max(existing_ids) + 1 if existing_ids else 1
|
|
|
|
# Add new player
|
|
new_player = {
|
|
'id': new_id,
|
|
'name': name,
|
|
'enabled': True
|
|
}
|
|
|
|
players_data['players'].append(new_player)
|
|
|
|
if save_players(players_data):
|
|
return jsonify({'status': 'success', 'player': new_player})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save player'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/players/<int:player_id>/delete', methods=['POST'])
|
|
def delete_player(player_id):
|
|
"""API endpoint to delete a player"""
|
|
try:
|
|
players_data = load_players()
|
|
|
|
# Find and remove the player
|
|
players_data['players'] = [p for p in players_data['players'] if p['id'] != player_id]
|
|
|
|
if save_players(players_data):
|
|
return jsonify({'status': 'success'})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save players'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/players/<int:player_id>', methods=['POST'])
|
|
def update_player(player_id):
|
|
"""API endpoint to update a single player"""
|
|
try:
|
|
data = request.get_json()
|
|
players_data = load_players()
|
|
|
|
# Find and update the player
|
|
for player in players_data['players']:
|
|
if player['id'] == player_id:
|
|
if 'name' in data:
|
|
player['name'] = data['name']
|
|
if 'enabled' in data:
|
|
player['enabled'] = data['enabled']
|
|
break
|
|
|
|
if save_players(players_data):
|
|
return jsonify({'status': 'success', 'player': player})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save player'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
# LEAGUE API ROUTES
|
|
@app.route('/api/league/start', methods=['POST'])
|
|
def start_league():
|
|
"""API endpoint to start a new league"""
|
|
try:
|
|
data = request.get_json()
|
|
tournament_type = data.get('tournament_type', '20_targets')
|
|
|
|
if tournament_type not in ['20_targets', '40_targets']:
|
|
return jsonify({'status': 'error', 'message': 'Invalid tournament type'}), 400
|
|
|
|
players_data = load_players()
|
|
enabled_players = [p for p in players_data['players'] if p['enabled']]
|
|
|
|
if len(enabled_players) < 1:
|
|
return jsonify({'status': 'error', 'message': 'Need at least 1 enabled player'}), 400
|
|
|
|
league_data = create_league(enabled_players, tournament_type)
|
|
|
|
if save_league_state(league_data):
|
|
return jsonify({'status': 'success', 'league': league_data})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save league'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/league/tournament/start', methods=['POST'])
|
|
def start_league_tournament():
|
|
"""API endpoint to start next tournament in league"""
|
|
try:
|
|
league_state = load_league_state()
|
|
if not league_state:
|
|
return jsonify({'status': 'error', 'message': 'No active league'}), 400
|
|
|
|
if league_state.get('league_finished', False):
|
|
return jsonify({'status': 'error', 'message': 'League already finished'}), 400
|
|
|
|
current_tournament = league_state.get('current_tournament', 0)
|
|
if current_tournament >= league_state['total_tournaments']:
|
|
return jsonify({'status': 'error', 'message': 'All tournaments completed'}), 400
|
|
|
|
# Get players for this tournament (excluding joker users for this round)
|
|
data = request.get_json() or {}
|
|
joker_players = data.get('joker_players', []) # List of player IDs using joker
|
|
|
|
# Update joker usage in league
|
|
for player_id in joker_players:
|
|
player_id_str = str(player_id)
|
|
if player_id_str in league_state['participants']:
|
|
if league_state['participants'][player_id_str]['joker_used']:
|
|
return jsonify({'status': 'error', 'message': f'Player {player_id} already used joker'}), 400
|
|
league_state['participants'][player_id_str]['joker_used'] = True
|
|
|
|
# Create list of participating players (not using joker)
|
|
participating_players = []
|
|
for player_id, participant in league_state['participants'].items():
|
|
if int(player_id) not in joker_players:
|
|
participating_players.append({
|
|
'id': int(player_id),
|
|
'name': participant['name']
|
|
})
|
|
|
|
if len(participating_players) < 1:
|
|
return jsonify({'status': 'error', 'message': 'Need at least 1 participating player'}), 400
|
|
|
|
# Start next tournament
|
|
league_state['current_tournament'] = current_tournament + 1
|
|
tournament_data = create_draft(
|
|
participating_players,
|
|
league_state['tournament_type'],
|
|
league_state['current_tournament']
|
|
)
|
|
|
|
# Save states
|
|
save_league_state(league_state)
|
|
|
|
if save_tournament_state(tournament_data):
|
|
# Create results structure
|
|
results = create_results_structure(tournament_data)
|
|
save_results(results)
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'tournament': tournament_data,
|
|
'league': league_state
|
|
})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save tournament'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/league/reset', methods=['POST'])
|
|
def reset_league():
|
|
"""API endpoint to reset/clear league"""
|
|
try:
|
|
# Archive current league if it exists
|
|
league_state = load_league_state()
|
|
if league_state:
|
|
archive_league(league_state)
|
|
|
|
# Remove league, tournament, and results files
|
|
for file_path in [LEAGUE_FILE, TOURNAMENT_FILE, RESULTS_FILE]:
|
|
if os.path.exists(file_path):
|
|
os.remove(file_path)
|
|
|
|
return jsonify({'status': 'success'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
# TOURNAMENT API ROUTES (Updated)
|
|
@app.route('/api/tournament/start', methods=['POST'])
|
|
def start_tournament():
|
|
"""API endpoint to start a standalone tournament"""
|
|
try:
|
|
data = request.get_json() or {}
|
|
tournament_type = data.get('tournament_type', '20_targets')
|
|
|
|
if tournament_type not in ['20_targets', '40_targets']:
|
|
return jsonify({'status': 'error', 'message': 'Invalid tournament type'}), 400
|
|
|
|
players_data = load_players()
|
|
enabled_players = [p for p in players_data['players'] if p['enabled']]
|
|
|
|
if len(enabled_players) < 1:
|
|
return jsonify({'status': 'error', 'message': 'Need at least 1 enabled player'}), 400
|
|
|
|
tournament_data = create_draft(enabled_players, tournament_type)
|
|
|
|
if save_tournament_state(tournament_data):
|
|
# Create results structure when tournament starts
|
|
results = create_results_structure(tournament_data)
|
|
save_results(results)
|
|
|
|
return jsonify({'status': 'success', 'tournament': tournament_data})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save tournament'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/tournament/round/<int:round_number>', methods=['POST'])
|
|
def change_round(round_number):
|
|
"""API endpoint to change current round"""
|
|
try:
|
|
tournament_state = load_tournament_state()
|
|
if not tournament_state:
|
|
return jsonify({'status': 'error', 'message': 'No active tournament'}), 400
|
|
|
|
if round_number < 1 or round_number > tournament_state['total_rounds']:
|
|
return jsonify({'status': 'error', 'message': 'Invalid round number'}), 400
|
|
|
|
tournament_state['current_round'] = round_number
|
|
|
|
# Update round statuses
|
|
for round_data in tournament_state['rounds']:
|
|
if round_data['round_number'] < round_number:
|
|
round_data['status'] = 'completed'
|
|
elif round_data['round_number'] == round_number:
|
|
round_data['status'] = 'pending'
|
|
else:
|
|
round_data['status'] = 'waiting'
|
|
|
|
if save_tournament_state(tournament_state):
|
|
return jsonify({'status': 'success', 'tournament': tournament_state})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save tournament'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/tournament/reset', methods=['POST'])
|
|
def reset_tournament():
|
|
"""API endpoint to reset/clear tournament"""
|
|
try:
|
|
if os.path.exists(TOURNAMENT_FILE):
|
|
os.remove(TOURNAMENT_FILE)
|
|
if os.path.exists(RESULTS_FILE):
|
|
os.remove(RESULTS_FILE)
|
|
return jsonify({'status': 'success'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
# RESULTS API Routes (Updated)
|
|
@app.route('/api/results/participant/<int:player_id>', methods=['POST'])
|
|
def update_participant_scores(player_id):
|
|
"""API endpoint to update participant scores"""
|
|
try:
|
|
data = request.get_json()
|
|
results = load_results()
|
|
|
|
if not results:
|
|
return jsonify({'status': 'error', 'message': 'No results data found'}), 400
|
|
|
|
player_id_str = str(player_id)
|
|
if player_id_str not in results['participants']:
|
|
return jsonify({'status': 'error', 'message': 'Player not found'}), 400
|
|
|
|
# Update scores
|
|
if 'targets' in data:
|
|
results['participants'][player_id_str]['targets'].update(data['targets'])
|
|
|
|
# Recalculate total score
|
|
targets = results['participants'][player_id_str]['targets']
|
|
total_score = calculate_total_score(targets)
|
|
results['participants'][player_id_str]['total_score'] = total_score
|
|
|
|
# Check if all targets are completed (all shots entered, including 0s)
|
|
all_completed = is_participant_completed(targets)
|
|
results['participants'][player_id_str]['completed'] = all_completed
|
|
|
|
if save_results(results):
|
|
return jsonify({'status': 'success', 'participant': results['participants'][player_id_str]})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save results'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/results/finish', methods=['POST'])
|
|
def finish_tournament():
|
|
"""API endpoint to finish tournament"""
|
|
try:
|
|
results = load_results()
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
|
|
if not results:
|
|
return jsonify({'status': 'error', 'message': 'No results data found'}), 400
|
|
|
|
if not tournament_state:
|
|
return jsonify({'status': 'error', 'message': 'No active tournament found'}), 400
|
|
|
|
# Check if all participants are completed
|
|
all_completed = all(
|
|
participant['completed']
|
|
for participant in results['participants'].values()
|
|
)
|
|
|
|
if not all_completed:
|
|
return jsonify({'status': 'error', 'message': 'Not all participants have completed scores'}), 400
|
|
|
|
# Mark tournament as finished
|
|
results['tournament_finished'] = True
|
|
results['finished_at'] = datetime.now().isoformat()
|
|
|
|
league_finished = False # Track if league finished
|
|
|
|
# Update league state if this is a league tournament
|
|
if league_state and 'league_tournament_number' in results:
|
|
tournament_number = results['league_tournament_number']
|
|
|
|
# Update league with tournament results
|
|
for player_id, participant in results['participants'].items():
|
|
if player_id in league_state['participants']:
|
|
league_participant = league_state['participants'][player_id]
|
|
|
|
# Add tournament result
|
|
league_participant['tournament_results'].append({
|
|
'tournament': tournament_number,
|
|
'score': participant['total_score'],
|
|
'participated': True
|
|
})
|
|
|
|
# Update total score
|
|
league_participant['total_score'] += participant['total_score']
|
|
|
|
# Add results for players who used joker (didn't participate)
|
|
for player_id, participant in league_state['participants'].items():
|
|
if player_id not in results['participants']:
|
|
# This player used joker
|
|
participant['tournament_results'].append({
|
|
'tournament': tournament_number,
|
|
'score': 0,
|
|
'participated': False,
|
|
'joker': True
|
|
})
|
|
|
|
# Add to completed tournaments
|
|
league_state['completed_tournaments'].append({
|
|
'tournament_number': tournament_number,
|
|
'finished_at': datetime.now().isoformat(),
|
|
'results_summary': {
|
|
'participants': len(results['participants']),
|
|
'total_shots': len(results['participants']) * (40 if results.get('tournament_type') == '40_targets' else 20) * 2
|
|
}
|
|
})
|
|
|
|
# Check if league is finished
|
|
if tournament_number >= league_state['total_tournaments']:
|
|
league_state['league_finished'] = True
|
|
league_state['finished_at'] = datetime.now().isoformat()
|
|
|
|
# Calculate final scores
|
|
calculate_league_final_scores(league_state)
|
|
|
|
league_finished = True
|
|
print("League finished!")
|
|
|
|
save_league_state(league_state)
|
|
|
|
# Archive the tournament (only if it's NOT part of a league)
|
|
archive_success = False
|
|
if not league_state: # Only archive standalone tournaments
|
|
archive_success = archive_tournament(tournament_state, results)
|
|
print(f"Standalone tournament archived: {archive_success}")
|
|
else:
|
|
print("League tournament - not archiving individual tournament")
|
|
|
|
# Archive the league if it just finished
|
|
league_archive_success = False
|
|
if league_finished and league_state:
|
|
league_archive_success = archive_league(league_state)
|
|
print(f"League archived: {league_archive_success}")
|
|
|
|
# Save final results
|
|
if save_results(results):
|
|
# End the tournament by removing tournament state
|
|
if os.path.exists(TOURNAMENT_FILE):
|
|
os.remove(TOURNAMENT_FILE)
|
|
|
|
# If league finished, also remove league state file
|
|
if league_finished and os.path.exists(LEAGUE_FILE):
|
|
os.remove(LEAGUE_FILE)
|
|
|
|
response_data = {
|
|
'status': 'success',
|
|
'results': results,
|
|
'archived': archive_success,
|
|
'league_archived': league_archive_success if league_finished else None
|
|
}
|
|
|
|
if league_state:
|
|
response_data['league'] = league_state
|
|
response_data['league_finished'] = league_state.get('league_finished', False)
|
|
|
|
return jsonify(response_data)
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Failed to save results'}), 500
|
|
|
|
except Exception as e:
|
|
print(f"Error finishing tournament: {e}")
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/results', methods=['GET'])
|
|
def get_results():
|
|
"""API endpoint to get current results"""
|
|
results = load_results()
|
|
if results:
|
|
return jsonify(results)
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'No results found'}), 404
|
|
|
|
@app.route('/api/league', methods=['GET'])
|
|
def get_league():
|
|
"""API endpoint to get current league state"""
|
|
league_state = load_league_state()
|
|
if league_state:
|
|
return jsonify(league_state)
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'No league found'}), 404
|
|
|
|
# Add this route to your Flask app (around line 850, with the other mobile routes)
|
|
|
|
@app.route('/mobile/remote')
|
|
def mobile_remote():
|
|
"""Mobile remote control page"""
|
|
# This page doesn't redirect mobile users - it's specifically for mobile control
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
results = load_results()
|
|
|
|
return render_template('mobile_remote.html',
|
|
tournament_state=tournament_state,
|
|
league_state=league_state,
|
|
results=results)
|
|
|
|
# Add these API endpoints for the remote control functionality
|
|
|
|
@app.route('/api/tournament', methods=['GET'])
|
|
def get_tournament():
|
|
"""API endpoint to get current tournament state"""
|
|
tournament_state = load_tournament_state()
|
|
if tournament_state:
|
|
return jsonify(tournament_state)
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'No tournament found'}), 404
|
|
|
|
@app.route('/api/remote/refresh_dashboard', methods=['POST'])
|
|
def refresh_dashboard():
|
|
"""API endpoint to trigger dashboard refresh"""
|
|
try:
|
|
# You could add logic here to trigger refresh on connected clients
|
|
# For now, just return success
|
|
return jsonify({'status': 'success', 'message': 'Refresh signal sent'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/remote/system_info', methods=['GET'])
|
|
def get_system_info():
|
|
"""API endpoint to get comprehensive system information"""
|
|
try:
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
results = load_results()
|
|
players_data = load_players()
|
|
settings = load_settings()
|
|
|
|
system_info = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'tournament': {
|
|
'active': tournament_state is not None,
|
|
'data': tournament_state
|
|
},
|
|
'league': {
|
|
'active': league_state is not None and not league_state.get('league_finished', False),
|
|
'finished': league_state is not None and league_state.get('league_finished', False),
|
|
'data': league_state
|
|
},
|
|
'results': {
|
|
'available': results is not None,
|
|
'finished': results is not None and results.get('tournament_finished', False),
|
|
'data': results
|
|
},
|
|
'players': {
|
|
'total': len(players_data['players']) if players_data else 0,
|
|
'enabled': len([p for p in players_data['players'] if p['enabled']]) if players_data else 0
|
|
},
|
|
'settings': settings
|
|
}
|
|
|
|
return jsonify(system_info)
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/remote/emergency_reset', methods=['POST'])
|
|
def emergency_reset():
|
|
"""API endpoint for emergency system reset"""
|
|
try:
|
|
# Archive current states before reset
|
|
tournament_state = load_tournament_state()
|
|
league_state = load_league_state()
|
|
results = load_results()
|
|
|
|
if tournament_state and results:
|
|
archive_tournament(tournament_state, results)
|
|
|
|
if league_state:
|
|
archive_league(league_state)
|
|
|
|
# Remove all state files
|
|
for file_path in [LEAGUE_FILE, TOURNAMENT_FILE, RESULTS_FILE]:
|
|
if os.path.exists(file_path):
|
|
os.remove(file_path)
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'message': 'Emergency reset completed',
|
|
'archived': True
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
@app.route('/api/remote/camera_titles', methods=['GET'])
|
|
def get_camera_titles():
|
|
"""API endpoint to get camera titles"""
|
|
try:
|
|
settings = load_settings()
|
|
current_round_data = get_current_round_data()
|
|
|
|
titles = {}
|
|
|
|
if current_round_data:
|
|
# Tournament active - use player names
|
|
players = current_round_data['players']
|
|
for i in range(6):
|
|
if i < len(players):
|
|
titles[str(i + 1)] = players[i]['name']
|
|
else:
|
|
titles[str(i + 1)] = 'Empty'
|
|
else:
|
|
# No tournament - use configured titles
|
|
titles = settings['camera_titles']
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'titles': titles,
|
|
'tournament_active': current_round_data is not None
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 400
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=5000, debug=True) |