2026-06-14 13:57:32 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Chess tournament Swiss system pairing calculator.
|
|
|
|
|
|
Fetches data from chess-results.com and calculates next round pairings.
|
|
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
|
python -m swiss_calc <URL>
|
|
|
|
|
|
python -m swiss_calc --standings <URL>
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
from .parser import fetch_tournament
|
|
|
|
|
|
from .swiss import calculate_next_round
|
|
|
|
|
|
from .display import format_pairings_table, format_player_standings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
|
description='Chess tournament pairing calculator for chess-results.com'
|
|
|
|
|
|
)
|
|
|
|
|
|
parser.add_argument('url', nargs='?', help='chess-results.com tournament URL')
|
|
|
|
|
|
parser.add_argument('--standings', '-s', action='store_true',
|
|
|
|
|
|
help='Show standings instead of next round')
|
|
|
|
|
|
parser.add_argument('--top', type=int, default=20,
|
|
|
|
|
|
help='Number of top players to show in standings (default: 20)')
|
|
|
|
|
|
parser.add_argument('--player', '-p', type=int, default=None,
|
|
|
|
|
|
help='Player rank to highlight')
|
|
|
|
|
|
parser.add_argument('--json', action='store_true',
|
|
|
|
|
|
help='Output JSON instead of formatted text')
|
|
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
url = args.url
|
|
|
|
|
|
if not url:
|
|
|
|
|
|
url = input('URL турнира chess-results.com: ').strip()
|
|
|
|
|
|
|
|
|
|
|
|
if not url:
|
|
|
|
|
|
print('❌ URL не указан', file=sys.stderr)
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
print(f'⏳ Загружаю: {url}', file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
tournament = fetch_tournament(url)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f'❌ Ошибка: {e}', file=sys.stderr)
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
print(f'✅ {tournament["name"]}', file=sys.stderr)
|
|
|
|
|
|
print(f'📅 Тур {tournament["current_round"]} из {tournament["num_rounds"]}', file=sys.stderr)
|
|
|
|
|
|
print(f'👥 {len(tournament["standings"])} участников', file=sys.stderr)
|
|
|
|
|
|
print(file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
if tournament['current_round'] >= tournament['num_rounds']:
|
|
|
|
|
|
if args.json:
|
|
|
|
|
|
import json
|
|
|
|
|
|
json.dump({'status': 'completed', 'round': tournament['current_round'], 'total_rounds': tournament['num_rounds']}, sys.stdout, ensure_ascii=False)
|
|
|
|
|
|
return
|
|
|
|
|
|
print('🏁 Турнир завершён!')
|
|
|
|
|
|
print()
|
|
|
|
|
|
print(format_player_standings(tournament, args.top))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if args.standings:
|
|
|
|
|
|
output = format_player_standings(tournament, args.top)
|
|
|
|
|
|
if args.json:
|
|
|
|
|
|
import json
|
|
|
|
|
|
json.dump({'type': 'standings', 'data': tournament['standings'][:args.top]}, sys.stdout, ensure_ascii=False)
|
|
|
|
|
|
return
|
|
|
|
|
|
print(output)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate next round
|
|
|
|
|
|
print('🧮 Считаю следующий тур...', file=sys.stderr)
|
|
|
|
|
|
next_round = calculate_next_round(tournament)
|
|
|
|
|
|
|
|
|
|
|
|
# Cross-check against pre-calculated if available
|
|
|
|
|
|
precalc = sum(1 for s in tournament['standings'] if s.get('next_opponent'))
|
|
|
|
|
|
if precalc > 0:
|
|
|
|
|
|
print(f'📊 На сайте уже рассчитан {precalc} пар', file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
output = format_pairings_table(next_round, tournament['name'])
|
|
|
|
|
|
|
|
|
|
|
|
if args.json:
|
|
|
|
|
|
import json
|
|
|
|
|
|
pairs = []
|
|
|
|
|
|
for pairing in next_round['pairings']:
|
|
|
|
|
|
if len(pairing) == 3:
|
|
|
|
|
|
p1, p2, color = pairing
|
2026-06-14 20:29:27 +00:00
|
|
|
|
if color == 'bye':
|
|
|
|
|
|
pairs.append({
|
|
|
|
|
|
'board': 0,
|
|
|
|
|
|
'bye': {'name': p1.name, 'rank': p1.sno, 'rating': p1.rating, 'points': p1.points},
|
|
|
|
|
|
})
|
|
|
|
|
|
elif color == 'w':
|
|
|
|
|
|
pairs.append({
|
|
|
|
|
|
'board': len(pairs) + 1,
|
|
|
|
|
|
'white': {'name': p1.name, 'rank': p1.sno, 'rating': p1.rating, 'points': p1.points},
|
|
|
|
|
|
'black': {'name': p2.name, 'rank': p2.sno, 'rating': p2.rating, 'points': p2.points},
|
|
|
|
|
|
})
|
|
|
|
|
|
else:
|
|
|
|
|
|
pairs.append({
|
|
|
|
|
|
'board': len(pairs) + 1,
|
|
|
|
|
|
'white': {'name': p2.name, 'rank': p2.sno, 'rating': p2.rating, 'points': p2.points},
|
|
|
|
|
|
'black': {'name': p1.name, 'rank': p1.sno, 'rating': p1.rating, 'points': p1.points},
|
|
|
|
|
|
})
|
2026-06-14 13:57:32 +00:00
|
|
|
|
json.dump({
|
2026-06-14 20:29:27 +00:00
|
|
|
|
'tournament': tournament['name'],
|
2026-06-14 13:57:32 +00:00
|
|
|
|
'round': next_round['round'],
|
|
|
|
|
|
'pairings': pairs,
|
|
|
|
|
|
'source': next_round.get('source', 'algorithm'),
|
|
|
|
|
|
}, sys.stdout, ensure_ascii=False)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
print(output)
|
|
|
|
|
|
|
|
|
|
|
|
# Player highlight
|
|
|
|
|
|
if args.player:
|
|
|
|
|
|
rank = args.player
|
|
|
|
|
|
for p in tournament['standings']:
|
|
|
|
|
|
if p['rank'] == rank:
|
|
|
|
|
|
# Find this player's pairing
|
|
|
|
|
|
for pairing in next_round['pairings']:
|
|
|
|
|
|
if len(pairing) == 3:
|
|
|
|
|
|
p1, p2, color = pairing
|
|
|
|
|
|
if p1.sno == rank or p2.sno == rank:
|
|
|
|
|
|
if p1.sno == rank:
|
|
|
|
|
|
opp = p2
|
|
|
|
|
|
player_color = color # color belongs to p1
|
|
|
|
|
|
else:
|
|
|
|
|
|
opp = p1
|
|
|
|
|
|
player_color = 'b' if color == 'w' else 'w' # opposite for p2
|
|
|
|
|
|
print()
|
|
|
|
|
|
print(f"🔍 **{p['name']}** (#{rank}, {p['points']} очков):")
|
|
|
|
|
|
print(f" {'🏳️' if player_color == 'w' else '🏁'} vs **{opp.name}**")
|
|
|
|
|
|
print(f" Рейтинг: {opp.rating} | Очки: {opp.points}")
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
main()
|