Fu01978 commited on
Commit
65cacea
·
verified ·
1 Parent(s): a8fad5f

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -247
app.py DELETED
@@ -1,247 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Chess Analyzer — Flask server
4
- Stockfish-powered analysis with Chess.com-style move classification.
5
- """
6
-
7
- from flask import Flask, render_template, request, jsonify, Response
8
- import chess
9
- import chess.pgn
10
- import json
11
- import io
12
- import threading
13
- import time
14
- import uuid
15
- import os
16
- import sys
17
-
18
- sys.path.insert(0, os.path.dirname(__file__))
19
- from chess_analyzer import (
20
- ChessAnalyzer, classify_move, Classification,
21
- MULTIPV, DEFAULT_DEPTH, score_to_cp, pv_to_san,
22
- wdl_to_expected, cp_to_expected,
23
- )
24
-
25
- app = Flask(__name__,
26
- template_folder=os.path.join(os.path.dirname(__file__), 'templates'))
27
- app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5 MB
28
-
29
- STOCKFISH_PATH = os.environ.get('STOCKFISH_PATH', 'stockfish')
30
- DEPTH = int(os.environ.get('ANALYSIS_DEPTH', str(DEFAULT_DEPTH)))
31
-
32
- # In-memory stores — fine for a single-user local/Colab session
33
- games = {} # game_id → full game dict
34
- progress = {} # game_id → progress dict
35
-
36
-
37
- # ─── Helpers ──────────────────────────────────────────────────────────────────
38
-
39
- def eval_from_white(lines_after: list, board_turn: chess.Color) -> float:
40
- """Convert side-to-move cp to always-positive-for-White."""
41
- if not lines_after:
42
- return 0.0
43
- cp = lines_after[0].cp
44
- return cp if board_turn == chess.WHITE else -cp
45
-
46
-
47
- def format_eval(cp: float) -> str:
48
- if cp >= 9000: return f"M{int(10000 - cp)}"
49
- if cp <= -9000: return f"-M{int(10000 + cp)}"
50
- return f"{cp / 100:+.2f}"
51
-
52
-
53
- # ─── Background analysis thread ───────────────────────────────────────────────
54
-
55
- def analyze_game_thread(game_id: str, pgn_text: str,
56
- depth: int = DEFAULT_DEPTH,
57
- time_ms: int = None):
58
- try:
59
- game = chess.pgn.read_game(io.StringIO(pgn_text))
60
- if game is None:
61
- progress[game_id].update({'error': 'Could not parse PGN.', 'complete': True})
62
- return
63
-
64
- moves = list(game.mainline_moves())
65
- progress[game_id]['total'] = len(moves)
66
-
67
- board = game.board()
68
- positions = []
69
-
70
- with ChessAnalyzer(STOCKFISH_PATH, depth, MULTIPV, time_ms=time_ms) as analyzer:
71
-
72
- # ── Starting position ─────────────────────────────────────────────
73
- init_lines = analyzer._top_moves(board)
74
- init_cp = init_lines[0].cp if init_lines else 0.0
75
- positions.append({
76
- 'fen': board.fen(),
77
- 'move_uci': None,
78
- 'san': None,
79
- 'move_number': 0,
80
- 'color': None,
81
- 'classification': None,
82
- 'classification_label': None,
83
- 'eval_white': init_cp,
84
- 'eval_display': format_eval(init_cp),
85
- 'cp_loss': 0,
86
- 'e_loss': 0.0,
87
- 'best_move': init_lines[0].move.uci() if init_lines else None,
88
- 'best_san': None,
89
- 'best_pv': [],
90
- 'player_pv': [],
91
- 'notes': [],
92
- 'from_sq': None,
93
- 'to_sq': None,
94
- })
95
-
96
- # ── Each move ─────────────────────────────────────────────────────
97
- lines_before = init_lines
98
- prev_move = None
99
-
100
- for i, move in enumerate(moves):
101
- best_move_uci = lines_before[0].move.uci() if lines_before else None
102
-
103
- # Best-move SAN and its continuation (PV after the best move)
104
- best_san = None
105
- best_pv_san = []
106
- if lines_before and lines_before[0].move != move:
107
- best_san = board.san(lines_before[0].move)
108
- best_pv_san = pv_to_san(board, lines_before[0].pv[1:], max_moves=5)
109
-
110
- # Analyse position after the played move
111
- board.push(move)
112
- lines_after = analyzer._top_moves(board, multipv=1)
113
-
114
- # Continuation after the played move
115
- player_pv_san = []
116
- if lines_after and lines_after[0].pv:
117
- player_pv_san = pv_to_san(board, lines_after[0].pv, max_moves=5)
118
-
119
- white_eval = eval_from_white(lines_after, board.turn)
120
- board.pop()
121
-
122
- # Classify (board is back to pre-move state)
123
- analysis = classify_move(board, move, lines_before, lines_after,
124
- half_move_idx=i, prev_move=prev_move)
125
- board.push(move)
126
-
127
- positions.append({
128
- 'fen': board.fen(),
129
- 'move_uci': move.uci(),
130
- 'san': analysis.san,
131
- 'move_number': (i // 2) + 1,
132
- 'color': 'white' if i % 2 == 0 else 'black',
133
- 'classification': analysis.classification.name,
134
- 'classification_label': analysis.classification.value,
135
- 'eval_white': white_eval,
136
- 'eval_display': format_eval(white_eval),
137
- 'cp_loss': round(analysis.cp_loss, 1),
138
- 'e_loss': round(analysis.e_loss, 4),
139
- 'best_move': best_move_uci,
140
- 'best_san': best_san,
141
- 'best_pv': best_pv_san,
142
- 'player_pv': player_pv_san,
143
- 'notes': analysis.notes,
144
- 'from_sq': chess.square_name(move.from_square),
145
- 'to_sq': chess.square_name(move.to_square),
146
- })
147
-
148
- progress[game_id]['done'] = i + 1
149
- prev_move = move
150
-
151
- if i + 1 < len(moves):
152
- lines_before = analyzer._top_moves(board)
153
-
154
- games[game_id] = {
155
- 'positions': positions,
156
- 'headers': {
157
- 'White': game.headers.get('White', '?'),
158
- 'Black': game.headers.get('Black', '?'),
159
- 'Event': game.headers.get('Event', '?'),
160
- 'Date': game.headers.get('Date', '?'),
161
- 'Result': game.headers.get('Result', '*'),
162
- },
163
- }
164
- progress[game_id]['complete'] = True
165
-
166
- except Exception as exc:
167
- import traceback
168
- progress[game_id].update({
169
- 'error': str(exc),
170
- 'traceback': traceback.format_exc(),
171
- 'complete': True,
172
- })
173
-
174
-
175
- # ─── Routes ───────────────────────────────────────────────────────────────────
176
-
177
- @app.route('/')
178
- def index():
179
- return render_template('index.html')
180
-
181
-
182
- @app.route('/upload', methods=['POST'])
183
- def upload():
184
- pgn_text = ''
185
- if 'pgn_text' in request.form and request.form['pgn_text'].strip():
186
- pgn_text = request.form['pgn_text']
187
- elif 'pgn_file' in request.files:
188
- pgn_text = request.files['pgn_file'].read().decode('utf-8', errors='ignore')
189
-
190
- if not pgn_text.strip():
191
- return jsonify({'error': 'No PGN provided.'}), 400
192
-
193
- game_id = str(uuid.uuid4())
194
- progress[game_id] = {'done': 0, 'total': 0, 'complete': False, 'error': None}
195
-
196
- try:
197
- depth_val = int(request.form.get('depth', DEFAULT_DEPTH))
198
- depth_val = max(6, min(30, depth_val))
199
- except (ValueError, TypeError):
200
- depth_val = DEFAULT_DEPTH
201
-
202
- try:
203
- time_val = int(request.form.get('time_ms', 0))
204
- time_val = time_val if time_val > 0 else None
205
- except (ValueError, TypeError):
206
- time_val = None
207
-
208
- t = threading.Thread(
209
- target=analyze_game_thread,
210
- args=(game_id, pgn_text, depth_val, time_val),
211
- daemon=True,
212
- )
213
- t.start()
214
- return jsonify({'game_id': game_id})
215
-
216
-
217
- @app.route('/progress/<game_id>')
218
- def get_progress(game_id):
219
- def stream():
220
- while True:
221
- p = progress.get(game_id, {'error': 'Game not found.', 'complete': True})
222
- yield f"data: {json.dumps(p)}\n\n"
223
- if p.get('complete'):
224
- break
225
- time.sleep(0.35)
226
- return Response(
227
- stream(),
228
- mimetype='text/event-stream',
229
- headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'},
230
- )
231
-
232
-
233
- @app.route('/game/<game_id>')
234
- def get_game(game_id):
235
- g = games.get(game_id)
236
- if not g:
237
- return jsonify({'error': 'Not found or still analyzing.'}), 404
238
- return jsonify(g)
239
-
240
-
241
- # ─── Entry point ──────────────────────────────────────────────────────────────
242
-
243
- if __name__ == '__main__':
244
- print(f" Stockfish: {STOCKFISH_PATH}")
245
- print(f" Depth: {DEPTH}")
246
- print(f" MultiPV: {MULTIPV}")
247
- app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)