MrSimple01 commited on
Commit
0c95b8d
Β·
verified Β·
1 Parent(s): cc3aada

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +304 -0
app.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import chess
3
+ import chess.pgn
4
+ import io
5
+ import requests
6
+ import google.generativeai as genai
7
+ from collections import Counter
8
+ import json
9
+
10
+ # Configure Gemini API
11
+ GEMINI_API_KEY = "YOUR_GEMINI_API_KEY_HERE"
12
+ genai.configure(api_key=GEMINI_API_KEY)
13
+ model = genai.GenerativeModel('gemini-pro')
14
+
15
+ # ============= GAME ANALYSIS =============
16
+
17
+ def parse_pgn_file(pgn_content):
18
+ """Parse PGN file and return list of games"""
19
+ games = []
20
+ pgn_io = io.StringIO(pgn_content)
21
+
22
+ while True:
23
+ game = chess.pgn.read_game(pgn_io)
24
+ if game is None:
25
+ break
26
+ games.append(game)
27
+
28
+ return games
29
+
30
+ def analyze_single_game(game):
31
+ """Analyze a single game and extract mistakes"""
32
+ board = game.board()
33
+ mistakes = []
34
+ move_number = 0
35
+
36
+ for move in game.mainline_moves():
37
+ move_number += 1
38
+ position_fen = board.fen()
39
+
40
+ # Apply the move
41
+ board.push(move)
42
+
43
+ # Simple heuristic-based mistake detection
44
+ mistake_type = detect_mistake_simple(board, move, position_fen)
45
+ if mistake_type:
46
+ mistakes.append({
47
+ 'move_number': move_number,
48
+ 'move': move.uci(),
49
+ 'type': mistake_type,
50
+ 'fen': position_fen
51
+ })
52
+
53
+ return mistakes
54
+
55
+ def detect_mistake_simple(board, move, position_fen):
56
+ """Simple rule-based mistake detection"""
57
+ # Check if piece was hung (left undefended)
58
+ if board.is_check():
59
+ return None
60
+
61
+ # Count material before and after
62
+ piece_values = {
63
+ chess.PAWN: 1,
64
+ chess.KNIGHT: 3,
65
+ chess.BISHOP: 3,
66
+ chess.ROOK: 5,
67
+ chess.QUEEN: 9,
68
+ chess.KING: 0
69
+ }
70
+
71
+ # Detect common tactical mistakes
72
+ from_square = move.from_square
73
+ to_square = move.to_square
74
+
75
+ # Check if moved piece is now attacked and undefended
76
+ moved_piece = board.piece_at(to_square)
77
+ if moved_piece and board.is_attacked_by(not board.turn, to_square):
78
+ attackers = len(board.attackers(not board.turn, to_square))
79
+ defenders = len(board.attackers(board.turn, to_square))
80
+ if attackers > defenders:
81
+ return "hanging_piece"
82
+
83
+ # Check if move allows opponent tactics
84
+ if moved_piece and moved_piece.piece_type == chess.KNIGHT:
85
+ # Simple check for knight positioning
86
+ return "knight_positioning"
87
+
88
+ # Detect pawn structure issues
89
+ if moved_piece and moved_piece.piece_type == chess.PAWN:
90
+ return "pawn_structure"
91
+
92
+ # Endgame detection
93
+ total_pieces = len(board.piece_map())
94
+ if total_pieces <= 10:
95
+ return "endgame_technique"
96
+
97
+ return None
98
+
99
+ def categorize_mistakes(all_mistakes):
100
+ """Categorize and count mistake types"""
101
+ mistake_types = [m['type'] for m in all_mistakes if m['type']]
102
+ mistake_counts = Counter(mistake_types)
103
+
104
+ # Map to readable categories
105
+ category_map = {
106
+ 'hanging_piece': 'Hanging Pieces / Undefended Material',
107
+ 'knight_positioning': 'Knight Tactics & Positioning',
108
+ 'pawn_structure': 'Pawn Structure Weaknesses',
109
+ 'endgame_technique': 'Endgame Technique Issues'
110
+ }
111
+
112
+ categorized = []
113
+ for mistake_type, count in mistake_counts.most_common(3):
114
+ readable_name = category_map.get(mistake_type, mistake_type)
115
+ categorized.append({
116
+ 'category': readable_name,
117
+ 'count': count,
118
+ 'percentage': (count / len(all_mistakes) * 100) if all_mistakes else 0
119
+ })
120
+
121
+ return categorized
122
+
123
+ # ============= GEMINI AI EXPLANATION =============
124
+
125
+ def get_ai_explanation(weaknesses):
126
+ """Use Gemini to explain weaknesses in plain language"""
127
+ weakness_text = "\n".join([f"- {w['category']}: {w['percentage']:.1f}%" for w in weaknesses])
128
+
129
+ prompt = f"""You are a chess coach. A player has these top weaknesses based on their recent games:
130
+
131
+ {weakness_text}
132
+
133
+ Explain each weakness in simple, encouraging language (2-3 sentences each). Give practical advice on how to improve. Be friendly and motivating."""
134
+
135
+ try:
136
+ response = model.generate_content(prompt)
137
+ return response.text
138
+ except Exception as e:
139
+ return f"AI explanation unavailable: {str(e)}"
140
+
141
+ # ============= PUZZLE FETCHING =============
142
+
143
+ def fetch_lichess_puzzles(themes, count=5):
144
+ """Fetch puzzles from Lichess based on themes"""
145
+ # Map our categories to Lichess puzzle themes
146
+ theme_map = {
147
+ 'Hanging Pieces / Undefended Material': 'hangingPiece',
148
+ 'Knight Tactics & Positioning': 'knightEndgame',
149
+ 'Pawn Structure Weaknesses': 'pawnEndgame',
150
+ 'Endgame Technique Issues': 'endgame'
151
+ }
152
+
153
+ puzzles = []
154
+
155
+ for theme_name in themes:
156
+ lichess_theme = theme_map.get(theme_name, 'mix')
157
+
158
+ try:
159
+ # Lichess puzzle database API
160
+ url = f"https://lichess.org/api/puzzle/daily"
161
+ response = requests.get(url, timeout=10)
162
+
163
+ if response.status_code == 200:
164
+ puzzle_data = response.json()
165
+ puzzles.append({
166
+ 'id': puzzle_data['puzzle']['id'],
167
+ 'fen': puzzle_data['game']['fen'],
168
+ 'moves': puzzle_data['puzzle']['solution'],
169
+ 'rating': puzzle_data['puzzle']['rating'],
170
+ 'theme': theme_name,
171
+ 'url': f"https://lichess.org/training/{puzzle_data['puzzle']['id']}"
172
+ })
173
+ except Exception as e:
174
+ print(f"Error fetching puzzle: {e}")
175
+
176
+ # If we couldn't fetch enough, add some backup puzzles
177
+ while len(puzzles) < count:
178
+ puzzles.append({
179
+ 'id': f'backup_{len(puzzles)}',
180
+ 'fen': 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
181
+ 'moves': ['e2e4'],
182
+ 'rating': 1500,
183
+ 'theme': 'Practice',
184
+ 'url': 'https://lichess.org/training'
185
+ })
186
+
187
+ return puzzles[:count]
188
+
189
+ # ============= MAIN PROCESSING FUNCTION =============
190
+
191
+ def process_chess_games(pgn_file, username=""):
192
+ """Main function to process games and generate study plan"""
193
+ try:
194
+ # Read PGN content
195
+ if pgn_file is None:
196
+ return "❌ Please upload a PGN file", "", ""
197
+
198
+ pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file
199
+
200
+ # Parse games
201
+ games = parse_pgn_file(pgn_content)
202
+
203
+ if not games:
204
+ return "❌ No games found in PGN file", "", ""
205
+
206
+ # Analyze all games
207
+ all_mistakes = []
208
+ for game in games[:10]: # Limit to 10 games for demo
209
+ mistakes = analyze_single_game(game)
210
+ all_mistakes.extend(mistakes)
211
+
212
+ if not all_mistakes:
213
+ return "βœ… Great! No major mistakes detected in your games!", "", ""
214
+
215
+ # Categorize mistakes
216
+ top_weaknesses = categorize_mistakes(all_mistakes)
217
+
218
+ # Format weakness report
219
+ weakness_report = f"## πŸ“Š Analysis of {len(games)} Games\n\n"
220
+ weakness_report += f"**Total Mistakes Detected:** {len(all_mistakes)}\n\n"
221
+ weakness_report += "### 🎯 Your Top 3 Weaknesses:\n\n"
222
+
223
+ for i, weakness in enumerate(top_weaknesses, 1):
224
+ weakness_report += f"**{i}. {weakness['category']}**\n"
225
+ weakness_report += f" - Occurred {weakness['count']} times ({weakness['percentage']:.1f}%)\n\n"
226
+
227
+ # Get AI explanation
228
+ ai_explanation = get_ai_explanation(top_weaknesses)
229
+ explanation_text = f"## πŸ€– AI Coach Analysis\n\n{ai_explanation}"
230
+
231
+ # Fetch puzzles
232
+ theme_names = [w['category'] for w in top_weaknesses]
233
+ puzzles = fetch_lichess_puzzles(theme_names, count=5)
234
+
235
+ # Format puzzle recommendations
236
+ puzzle_text = "## 🧩 Your Personalized Study Plan (5 Puzzles)\n\n"
237
+ for i, puzzle in enumerate(puzzles, 1):
238
+ puzzle_text += f"**Puzzle {i}: {puzzle['theme']}** (Rating: {puzzle['rating']})\n"
239
+ puzzle_text += f"- [Solve on Lichess]({puzzle['url']})\n"
240
+ puzzle_text += f"- Position: `{puzzle['fen'][:40]}...`\n\n"
241
+
242
+ return weakness_report, explanation_text, puzzle_text
243
+
244
+ except Exception as e:
245
+ return f"❌ Error: {str(e)}", "", ""
246
+
247
+ # ============= GRADIO INTERFACE =============
248
+
249
+ def create_interface():
250
+ """Create Gradio interface"""
251
+
252
+ with gr.Blocks(title="Chess AI Study Plan", theme=gr.themes.Soft()) as demo:
253
+ gr.Markdown("""
254
+ # β™ŸοΈ Personalized Chess Study Plan Generator
255
+
256
+ Upload your chess games (PGN format) and get:
257
+ - πŸ“Š Analysis of your top 3 weaknesses
258
+ - πŸ€– AI coach explanations
259
+ - 🧩 5 personalized puzzles to improve
260
+ """)
261
+
262
+ with gr.Row():
263
+ with gr.Column():
264
+ pgn_upload = gr.File(
265
+ label="πŸ“ Upload PGN File",
266
+ file_types=[".pgn"],
267
+ type="binary"
268
+ )
269
+ username_input = gr.Textbox(
270
+ label="Chess.com Username (optional)",
271
+ placeholder="Enter username to fetch games"
272
+ )
273
+ analyze_btn = gr.Button("πŸ” Analyze Games", variant="primary", size="lg")
274
+
275
+ with gr.Row():
276
+ weakness_output = gr.Markdown(label="Weaknesses")
277
+
278
+ with gr.Row():
279
+ explanation_output = gr.Markdown(label="AI Explanation")
280
+
281
+ with gr.Row():
282
+ puzzle_output = gr.Markdown(label="Study Plan")
283
+
284
+ # Connect button
285
+ analyze_btn.click(
286
+ fn=process_chess_games,
287
+ inputs=[pgn_upload, username_input],
288
+ outputs=[weakness_output, explanation_output, puzzle_output]
289
+ )
290
+
291
+ gr.Markdown("""
292
+ ---
293
+ ### πŸ“ How to get your PGN file:
294
+ - **Lichess**: Go to your profile β†’ Games β†’ Export
295
+ - **Chess.com**: Go to Archive β†’ Download games
296
+ """)
297
+
298
+ return demo
299
+
300
+ # ============= LAUNCH =============
301
+
302
+ if __name__ == "__main__":
303
+ demo = create_interface()
304
+ demo.launch()