ShubhanshuBansod commited on
Commit
943fe11
ยท
verified ยท
1 Parent(s): bdd3bc0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -206
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import streamlit as st
2
  import os
3
  from datetime import datetime
@@ -6,92 +7,28 @@ from minimax_ai import MinimaxAI
6
  from llm_explanation import MoveExplainer
7
  from report_generator import generate_match_report
8
 
9
- # Page configuration
10
- st.set_page_config(
11
- page_title="Connect Four AI Pro",
12
- layout="wide",
13
- initial_sidebar_state="expanded"
14
- )
15
 
16
- # Enhanced CSS
17
  st.markdown('''
18
  <style>
19
- .board-display {
20
- font-family: 'Courier New', monospace;
21
- white-space: pre;
22
- line-height: 1.4;
23
- font-size: 16px;
24
- padding: 20px;
25
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
26
- color: white;
27
- border-radius: 12px;
28
- box-shadow: 0 8px 16px rgba(0,0,0,0.2);
29
- margin: 0 auto;
30
- display: inline-block;
31
- }
32
- .board-container {
33
- text-align: center;
34
- }
35
- .stats-card {
36
- background: white;
37
- padding: 15px;
38
- border-radius: 10px;
39
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
40
- margin: 10px 0;
41
- border-left: 4px solid #667eea;
42
- }
43
- .stat-number {
44
- font-size: 24px;
45
- font-weight: bold;
46
- color: #667eea;
47
- }
48
- .stat-label {
49
- font-size: 12px;
50
- color: #666;
51
- text-transform: uppercase;
52
- }
53
- .move-card {
54
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
55
- color: white;
56
- padding: 20px;
57
- border-radius: 12px;
58
- margin: 10px 0;
59
- box-shadow: 0 6px 12px rgba(0,0,0,0.15);
60
- }
61
- .player-section {
62
- background: white;
63
- padding: 20px;
64
- border-radius: 12px;
65
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
66
- margin: 15px 0;
67
- }
68
- .player1-section {
69
- border-left: 5px solid #ef4444;
70
- }
71
- .player2-section {
72
- border-left: 5px solid #f59e0b;
73
- }
74
- .mode-badge {
75
- display: inline-block;
76
- padding: 4px 12px;
77
- border-radius: 20px;
78
- font-size: 12px;
79
- font-weight: bold;
80
- margin-right: 8px;
81
- }
82
- .ultra-fast { background: #10b981; color: white; }
83
- .balanced { background: #3b82f6; color: white; }
84
- .two-player { background: #f59e0b; color: white; }
85
  </style>
86
  ''', unsafe_allow_html=True)
87
 
88
- # Initialize session state
89
  if 'game' not in st.session_state:
90
  st.session_state.game = ConnectFour()
91
- st.session_state.ai_mode = "Ultra-Fast"
92
  st.session_state.game_mode = "vs AI"
93
- st.session_state.ai_ultra = MinimaxAI(st.session_state.game, depth=5)
94
- st.session_state.ai_balanced = MinimaxAI(st.session_state.game, depth=6)
95
 
96
  hf_token = os.getenv('HF_TOKEN')
97
  st.session_state.explainer = MoveExplainer(hf_token=hf_token) if hf_token else None
@@ -107,23 +44,17 @@ if 'game' not in st.session_state:
107
  st.session_state.move_count = 0
108
  st.session_state.current_depth = 5
109
 
110
- # Title
111
  col_title1, col_title2 = st.columns([3, 1])
112
  with col_title1:
113
  st.title("๐ŸŽฎ Connect Four AI Pro")
114
  with col_title2:
115
  if st.session_state.game_mode == "vs AI":
116
- mode_class = "ultra-fast" if st.session_state.ai_mode == "Ultra-Fast" else "balanced"
117
- st.markdown(f'<span class="mode-badge {mode_class}">{st.session_state.ai_mode} AI</span>',
118
- unsafe_allow_html=True)
119
  else:
120
- st.markdown('<span class="mode-badge two-player">Two Player</span>',
121
- unsafe_allow_html=True)
122
 
123
- # Sidebar
124
  st.sidebar.header("โš™๏ธ Game Settings")
125
 
126
- # Game Mode
127
  game_mode = st.sidebar.radio("๐ŸŽฎ Game Mode", ["vs AI", "Two Player"],
128
  index=0 if st.session_state.game_mode == "vs AI" else 1)
129
 
@@ -139,36 +70,15 @@ if game_mode != st.session_state.game_mode:
139
  st.session_state.move_count = 0
140
  st.rerun()
141
 
142
- # AI Mode
143
  if st.session_state.game_mode == "vs AI":
144
- st.sidebar.markdown("---")
145
- st.sidebar.subheader("๐Ÿค– AI Difficulty")
146
- ai_mode = st.sidebar.radio("Select AI Mode", ["Ultra-Fast โšก", "Balanced ๐ŸŽฏ"],
147
- index=0 if st.session_state.ai_mode == "Ultra-Fast" else 1,
148
- help="Ultra-Fast: ~200ms | Balanced: ~400ms",
149
- key="ai_mode_radio")
150
-
151
- new_mode = "Ultra-Fast" if "Ultra-Fast" in ai_mode else "Balanced"
152
- if new_mode != st.session_state.ai_mode:
153
- st.session_state.ai_mode = new_mode
154
-
155
- st.session_state.current_ai = st.session_state.ai_ultra if st.session_state.ai_mode == "Ultra-Fast" else st.session_state.ai_balanced
156
-
157
- # Depth slider
158
  st.sidebar.markdown("---")
159
  st.sidebar.subheader("๐ŸŽš๏ธ Search Depth")
160
- if st.session_state.ai_mode == "Ultra-Fast":
161
- depth = st.sidebar.slider("Depth (Ultra-Fast)", 3, 8, 5)
162
- st.session_state.ai_ultra.depth = depth
163
- st.session_state.current_depth = depth
164
- else:
165
- depth = st.sidebar.slider("Depth (Balanced)", 4, 8, 6)
166
- st.session_state.ai_balanced.depth = depth
167
- st.session_state.current_depth = depth
168
 
169
  st.sidebar.markdown("---")
170
 
171
- # Controls
172
  col1, col2 = st.sidebar.columns(2)
173
  with col1:
174
  if st.button("๐Ÿ”„ New Game", use_container_width=True):
@@ -192,34 +102,35 @@ with col2:
192
  st.session_state.game.undo_move(st.session_state.move_history[-1])
193
  st.session_state.move_history.pop()
194
  st.session_state.move_history_detailed.pop()
195
- if st.session_state.game_mode == "Two Player":
196
- if st.session_state.player1_analyses and st.session_state.player2_analyses:
197
- if st.session_state.move_count % 2 == 0:
198
- st.session_state.player2_analyses.pop()
199
- else:
200
- st.session_state.player1_analyses.pop()
201
  st.session_state.move_count -= 1
202
  st.session_state.game_over = False
203
  st.session_state.show_winner_popup = False
204
  st.rerun()
205
 
206
  st.sidebar.markdown("---")
207
-
208
- # Game Stats
209
  st.sidebar.subheader("๐Ÿ“Š Game Stats")
210
  st.sidebar.write(f"**Moves:** {st.session_state.move_count}")
211
  if st.session_state.game_mode == "Two Player":
212
  st.sidebar.write(f"**P1:** {len(st.session_state.player1_analyses)}")
213
  st.sidebar.write(f"**P2:** {len(st.session_state.player2_analyses)}")
214
 
215
- # Main area
 
 
 
 
 
216
  col1, col2 = st.columns([2, 3])
217
 
218
  with col1:
219
  st.subheader("๐ŸŽฏ Game Board")
220
  board_str = st.session_state.game.board_to_string()
221
- st.markdown(f'<div class="board-container"><div class="board-display">{board_str}</div></div>',
222
- unsafe_allow_html=True)
223
 
224
  if not st.session_state.game_over:
225
  player_name = f"Player {st.session_state.current_player}" if st.session_state.game_mode == "Two Player" else "Your"
@@ -227,20 +138,16 @@ with col1:
227
  st.markdown(f"### {player_name} Move {player_emoji}")
228
 
229
  valid_cols = st.session_state.game.get_valid_moves()
230
-
231
  if valid_cols:
232
  human_move = st.selectbox("Choose column:", valid_cols, key="move_select")
233
 
234
  if st.button("๐Ÿ“ Play Move", use_container_width=True, type="primary"):
235
  current_player = st.session_state.current_player if st.session_state.game_mode == "Two Player" else 1
236
-
237
  st.session_state.game.make_move(human_move, player=current_player)
238
  st.session_state.move_history.append(human_move)
239
  st.session_state.move_count += 1
240
 
241
- # Get AI analysis - ALWAYS use ai_balanced for analysis (consistent)
242
- ai_analysis = st.session_state.ai_balanced.get_best_move()
243
-
244
  move_detail = {
245
  'player': current_player,
246
  'player_name': f"Player {current_player}" if st.session_state.game_mode == "Two Player" else "Human",
@@ -254,7 +161,6 @@ with col1:
254
  'suggested_column': ai_analysis['move'],
255
  'score_diff': abs(ai_analysis['score'])
256
  }
257
-
258
  if current_player == 1:
259
  st.session_state.player1_analyses.append(move_detail)
260
  else:
@@ -262,7 +168,6 @@ with col1:
262
 
263
  st.session_state.move_history_detailed.append(move_detail)
264
 
265
- # Check winner
266
  if st.session_state.game.check_winner(current_player):
267
  st.session_state.game_over = True
268
  st.session_state.winner = f"Player {current_player}" if st.session_state.game_mode == "Two Player" else "Human"
@@ -273,9 +178,8 @@ with col1:
273
  st.session_state.show_winner_popup = True
274
  else:
275
  if st.session_state.game_mode == "vs AI":
276
- with st.spinner(f"๐Ÿค– {st.session_state.ai_mode} AI thinking..."):
277
- # Use current_ai for BOTH move AND analysis in vs AI mode
278
- ai_result = st.session_state.current_ai.get_best_move()
279
 
280
  st.session_state.game.make_move(ai_result['move'], player=2)
281
  st.session_state.move_history.append(ai_result['move'])
@@ -323,18 +227,12 @@ with col2:
323
  explanation = {
324
  'explanation': f"Column {ai_move['move']} scores {ai_move['score']} points.",
325
  'threat_analysis': "; ".join(ai_move['threats']) if ai_move['threats'] else "Strategic move",
326
- 'key_insight': "Good positioning",
327
  'success': False
328
  }
329
  has_llm = False
330
 
331
- st.markdown(f'''
332
- <div class="move-card">
333
- <h3>๐ŸŽฏ AI Played: Column {ai_move['move']}</h3>
334
- <p><strong>Score:</strong> {ai_move['score']} | <strong>Mode:</strong> {st.session_state.ai_mode}</p>
335
- </div>
336
- ''', unsafe_allow_html=True)
337
-
338
  st.success(f"๐Ÿ’ก **Why This Move:**\n{explanation['explanation']}")
339
 
340
  if explanation.get('threat_analysis'):
@@ -347,42 +245,17 @@ with col2:
347
 
348
  stat_col1, stat_col2, stat_col3 = st.columns(3)
349
  with stat_col1:
350
- st.markdown(f'''
351
- <div class="stats-card">
352
- <div class="stat-number">{ai_move["nodes_explored"]:,}</div>
353
- <div class="stat-label">Nodes Explored</div>
354
- </div>
355
- ''', unsafe_allow_html=True)
356
  with stat_col2:
357
- st.markdown(f'''
358
- <div class="stats-card">
359
- <div class="stat-number">{ai_move["nodes_pruned"]:,}</div>
360
- <div class="stat-label">Nodes Pruned</div>
361
- </div>
362
- ''', unsafe_allow_html=True)
363
  with stat_col3:
364
- st.markdown(f'''
365
- <div class="stats-card">
366
- <div class="stat-number">{ai_move["pruning_efficiency"]:.1f}%</div>
367
- <div class="stat-label">Efficiency</div>
368
- </div>
369
- ''', unsafe_allow_html=True)
370
 
371
  stat_col4, stat_col5 = st.columns(2)
372
  with stat_col4:
373
- st.markdown(f'''
374
- <div class="stats-card">
375
- <div class="stat-number">{ai_move["depth"]}</div>
376
- <div class="stat-label">Search Depth</div>
377
- </div>
378
- ''', unsafe_allow_html=True)
379
  with stat_col5:
380
- st.markdown(f'''
381
- <div class="stats-card">
382
- <div class="stat-number">{ai_move["time_ms"]:.0f}ms</div>
383
- <div class="stat-label">Response Time</div>
384
- </div>
385
- ''', unsafe_allow_html=True)
386
 
387
  if not has_llm:
388
  st.info("โ„น๏ธ Using fallback explanation (HF API unavailable)")
@@ -401,29 +274,13 @@ with col2:
401
  if latest_p1['analysis']['threats']:
402
  st.markdown(f"โš”๏ธ **Threats:** {', '.join(latest_p1['analysis']['threats'])}")
403
 
404
- # Show AI analysis text (explanation doesn't exist in Two Player but we can add general analysis)
405
  st.markdown(f"**Score:** {latest_p1['analysis']['score']} points")
406
-
407
- # Algorithm Performance
408
  st.markdown("**Algorithm Analysis:**")
409
  perf_col1, perf_col2 = st.columns(2)
410
-
411
  with perf_col1:
412
- st.markdown(f'''
413
- <div class="stats-card">
414
- <div class="stat-number">{latest_p1['analysis']['nodes_explored']:,}</div>
415
- <div class="stat-label">Explored</div>
416
- </div>
417
- ''', unsafe_allow_html=True)
418
-
419
  with perf_col2:
420
- st.markdown(f'''
421
- <div class="stats-card">
422
- <div class="stat-number">{latest_p1['analysis']['nodes_pruned']:,}</div>
423
- <div class="stat-label">Pruned</div>
424
- </div>
425
- ''', unsafe_allow_html=True)
426
-
427
  st.markdown('</div>', unsafe_allow_html=True)
428
 
429
  if st.session_state.player2_analyses:
@@ -439,36 +296,21 @@ with col2:
439
  if latest_p2['analysis']['threats']:
440
  st.markdown(f"โš”๏ธ **Threats:** {', '.join(latest_p2['analysis']['threats'])}")
441
 
442
- # Show AI analysis text
443
  st.markdown(f"**Score:** {latest_p2['analysis']['score']} points")
444
-
445
- # Algorithm Performance
446
  st.markdown("**Algorithm Analysis:**")
447
  perf_col1, perf_col2 = st.columns(2)
448
  with perf_col1:
449
- st.markdown(f'''
450
- <div class="stats-card">
451
- <div class="stat-number">{latest_p2['analysis']['nodes_explored']:,}</div>
452
- <div class="stat-label">Explored</div>
453
- </div>
454
- ''', unsafe_allow_html=True)
455
  with perf_col2:
456
- st.markdown(f'''
457
- <div class="stats-card">
458
- <div class="stat-number">{latest_p2['analysis']['nodes_pruned']:,}</div>
459
- <div class="stat-label">Pruned</div>
460
- </div>
461
- ''', unsafe_allow_html=True)
462
-
463
  st.markdown('</div>', unsafe_allow_html=True)
464
 
465
- # Winner popup
466
  if st.session_state.show_winner_popup and st.session_state.game_over:
467
  if st.session_state.winner == "Human":
468
  st.balloons()
469
  st.success("# ๐ŸŽ‰ YOU WON! ๐ŸŽ‰")
470
  elif st.session_state.winner == "AI":
471
- st.info(f"# ๐Ÿค– {st.session_state.ai_mode} AI WON!")
472
  elif "Player" in st.session_state.winner:
473
  st.balloons()
474
  st.success(f"# ๐ŸŽ‰ {st.session_state.winner.upper()} WON! ๐ŸŽ‰")
@@ -477,7 +319,6 @@ if st.session_state.show_winner_popup and st.session_state.game_over:
477
 
478
  st.session_state.show_winner_popup = False
479
 
480
- # Game over - report download
481
  if st.session_state.game_over:
482
  st.markdown("---")
483
  st.markdown("### ๐Ÿ“„ Download Match Report")
@@ -485,7 +326,7 @@ if st.session_state.game_over:
485
 
486
  report = generate_match_report(
487
  st.session_state.game_mode,
488
- st.session_state.ai_mode,
489
  st.session_state.winner,
490
  st.session_state.move_history_detailed,
491
  st.session_state.game.board_to_string(),
 
1
+ # app.py - FINAL ULTRA-ONLY VERSION (Single AI for Simplicity & Stability)
2
  import streamlit as st
3
  import os
4
  from datetime import datetime
 
7
  from llm_explanation import MoveExplainer
8
  from report_generator import generate_match_report
9
 
10
+ st.set_page_config(page_title="Connect Four AI Pro", layout="wide", initial_sidebar_state="expanded")
 
 
 
 
 
11
 
 
12
  st.markdown('''
13
  <style>
14
+ .board-display { font-family: 'Courier New', monospace; white-space: pre; line-height: 1.4; font-size: 16px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.2); margin: 0 auto; display: inline-block; }
15
+ .board-container { text-align: center; }
16
+ .stats-card { background: white; padding: 15px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); margin: 10px 0; border-left: 4px solid #667eea; }
17
+ .stat-number { font-size: 24px; font-weight: bold; color: #667eea; }
18
+ .stat-label { font-size: 12px; color: #666; text-transform: uppercase; }
19
+ .move-card { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 20px; border-radius: 12px; margin: 10px 0; box-shadow: 0 6px 12px rgba(0,0,0,0.15); }
20
+ .player-section { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); margin: 15px 0; }
21
+ .player1-section { border-left: 5px solid #ef4444; }
22
+ .player2-section { border-left: 5px solid #f59e0b; }
23
+ .mode-badge { display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; margin-right: 8px; background: #10b981; color: white; }
24
+ .two-player { background: #f59e0b; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  </style>
26
  ''', unsafe_allow_html=True)
27
 
 
28
  if 'game' not in st.session_state:
29
  st.session_state.game = ConnectFour()
 
30
  st.session_state.game_mode = "vs AI"
31
+ st.session_state.ai = MinimaxAI(st.session_state.game, depth=5)
 
32
 
33
  hf_token = os.getenv('HF_TOKEN')
34
  st.session_state.explainer = MoveExplainer(hf_token=hf_token) if hf_token else None
 
44
  st.session_state.move_count = 0
45
  st.session_state.current_depth = 5
46
 
 
47
  col_title1, col_title2 = st.columns([3, 1])
48
  with col_title1:
49
  st.title("๐ŸŽฎ Connect Four AI Pro")
50
  with col_title2:
51
  if st.session_state.game_mode == "vs AI":
52
+ st.markdown(f'<span class="mode-badge">Ultra AI</span>', unsafe_allow_html=True)
 
 
53
  else:
54
+ st.markdown('<span class="mode-badge two-player">Two Player</span>', unsafe_allow_html=True)
 
55
 
 
56
  st.sidebar.header("โš™๏ธ Game Settings")
57
 
 
58
  game_mode = st.sidebar.radio("๐ŸŽฎ Game Mode", ["vs AI", "Two Player"],
59
  index=0 if st.session_state.game_mode == "vs AI" else 1)
60
 
 
70
  st.session_state.move_count = 0
71
  st.rerun()
72
 
 
73
  if st.session_state.game_mode == "vs AI":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  st.sidebar.markdown("---")
75
  st.sidebar.subheader("๐ŸŽš๏ธ Search Depth")
76
+ depth = st.sidebar.slider("AI Depth (3-8)", 3, 8, 5, help="Lower = faster, Higher = stronger")
77
+ st.session_state.ai.depth = depth
78
+ st.session_state.current_depth = depth
 
 
 
 
 
79
 
80
  st.sidebar.markdown("---")
81
 
 
82
  col1, col2 = st.sidebar.columns(2)
83
  with col1:
84
  if st.button("๐Ÿ”„ New Game", use_container_width=True):
 
102
  st.session_state.game.undo_move(st.session_state.move_history[-1])
103
  st.session_state.move_history.pop()
104
  st.session_state.move_history_detailed.pop()
105
+ if st.session_state.game_mode == "Two Player" and st.session_state.player1_analyses and st.session_state.player2_analyses:
106
+ if st.session_state.move_count % 2 == 0:
107
+ st.session_state.player2_analyses.pop()
108
+ else:
109
+ st.session_state.player1_analyses.pop()
 
110
  st.session_state.move_count -= 1
111
  st.session_state.game_over = False
112
  st.session_state.show_winner_popup = False
113
  st.rerun()
114
 
115
  st.sidebar.markdown("---")
 
 
116
  st.sidebar.subheader("๐Ÿ“Š Game Stats")
117
  st.sidebar.write(f"**Moves:** {st.session_state.move_count}")
118
  if st.session_state.game_mode == "Two Player":
119
  st.sidebar.write(f"**P1:** {len(st.session_state.player1_analyses)}")
120
  st.sidebar.write(f"**P2:** {len(st.session_state.player2_analyses)}")
121
 
122
+ if st.session_state.move_history:
123
+ with st.sidebar.expander("๐Ÿ“œ Move History"):
124
+ for i, col in enumerate(st.session_state.move_history, 1):
125
+ player = "๐Ÿ”ด" if i % 2 == 1 else "๐ŸŸก"
126
+ st.write(f"Move {i}: {player} โ†’ Col {col}")
127
+
128
  col1, col2 = st.columns([2, 3])
129
 
130
  with col1:
131
  st.subheader("๐ŸŽฏ Game Board")
132
  board_str = st.session_state.game.board_to_string()
133
+ st.markdown(f'<div class="board-container"><div class="board-display">{board_str}</div></div>', unsafe_allow_html=True)
 
134
 
135
  if not st.session_state.game_over:
136
  player_name = f"Player {st.session_state.current_player}" if st.session_state.game_mode == "Two Player" else "Your"
 
138
  st.markdown(f"### {player_name} Move {player_emoji}")
139
 
140
  valid_cols = st.session_state.game.get_valid_moves()
 
141
  if valid_cols:
142
  human_move = st.selectbox("Choose column:", valid_cols, key="move_select")
143
 
144
  if st.button("๐Ÿ“ Play Move", use_container_width=True, type="primary"):
145
  current_player = st.session_state.current_player if st.session_state.game_mode == "Two Player" else 1
 
146
  st.session_state.game.make_move(human_move, player=current_player)
147
  st.session_state.move_history.append(human_move)
148
  st.session_state.move_count += 1
149
 
150
+ ai_analysis = st.session_state.ai.get_best_move()
 
 
151
  move_detail = {
152
  'player': current_player,
153
  'player_name': f"Player {current_player}" if st.session_state.game_mode == "Two Player" else "Human",
 
161
  'suggested_column': ai_analysis['move'],
162
  'score_diff': abs(ai_analysis['score'])
163
  }
 
164
  if current_player == 1:
165
  st.session_state.player1_analyses.append(move_detail)
166
  else:
 
168
 
169
  st.session_state.move_history_detailed.append(move_detail)
170
 
 
171
  if st.session_state.game.check_winner(current_player):
172
  st.session_state.game_over = True
173
  st.session_state.winner = f"Player {current_player}" if st.session_state.game_mode == "Two Player" else "Human"
 
178
  st.session_state.show_winner_popup = True
179
  else:
180
  if st.session_state.game_mode == "vs AI":
181
+ with st.spinner("๐Ÿค– AI thinking..."):
182
+ ai_result = st.session_state.ai.get_best_move()
 
183
 
184
  st.session_state.game.make_move(ai_result['move'], player=2)
185
  st.session_state.move_history.append(ai_result['move'])
 
227
  explanation = {
228
  'explanation': f"Column {ai_move['move']} scores {ai_move['score']} points.",
229
  'threat_analysis': "; ".join(ai_move['threats']) if ai_move['threats'] else "Strategic move",
230
+ 'key_insight': "Strong position",
231
  'success': False
232
  }
233
  has_llm = False
234
 
235
+ st.markdown(f'''<div class="move-card"><h3>๐ŸŽฏ AI Played: Column {ai_move['move']}</h3><p><strong>Score:</strong> {ai_move['score']} points</p></div>''', unsafe_allow_html=True)
 
 
 
 
 
 
236
  st.success(f"๐Ÿ’ก **Why This Move:**\n{explanation['explanation']}")
237
 
238
  if explanation.get('threat_analysis'):
 
245
 
246
  stat_col1, stat_col2, stat_col3 = st.columns(3)
247
  with stat_col1:
248
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{ai_move["nodes_explored"]:,}</div><div class="stat-label">Explored</div></div>', unsafe_allow_html=True)
 
 
 
 
 
249
  with stat_col2:
250
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{ai_move["nodes_pruned"]:,}</div><div class="stat-label">Pruned</div></div>', unsafe_allow_html=True)
 
 
 
 
 
251
  with stat_col3:
252
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{ai_move["pruning_efficiency"]:.1f}%</div><div class="stat-label">Efficiency</div></div>', unsafe_allow_html=True)
 
 
 
 
 
253
 
254
  stat_col4, stat_col5 = st.columns(2)
255
  with stat_col4:
256
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{ai_move["depth"]}</div><div class="stat-label">Search Depth</div></div>', unsafe_allow_html=True)
 
 
 
 
 
257
  with stat_col5:
258
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{ai_move["time_ms"]:.0f}ms</div><div class="stat-label">Response</div></div>', unsafe_allow_html=True)
 
 
 
 
 
259
 
260
  if not has_llm:
261
  st.info("โ„น๏ธ Using fallback explanation (HF API unavailable)")
 
274
  if latest_p1['analysis']['threats']:
275
  st.markdown(f"โš”๏ธ **Threats:** {', '.join(latest_p1['analysis']['threats'])}")
276
 
 
277
  st.markdown(f"**Score:** {latest_p1['analysis']['score']} points")
 
 
278
  st.markdown("**Algorithm Analysis:**")
279
  perf_col1, perf_col2 = st.columns(2)
 
280
  with perf_col1:
281
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{latest_p1["analysis"]["nodes_explored"]:,}</div><div class="stat-label">Explored</div></div>', unsafe_allow_html=True)
 
 
 
 
 
 
282
  with perf_col2:
283
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{latest_p1["analysis"]["nodes_pruned"]:,}</div><div class="stat-label">Pruned</div></div>', unsafe_allow_html=True)
 
 
 
 
 
 
284
  st.markdown('</div>', unsafe_allow_html=True)
285
 
286
  if st.session_state.player2_analyses:
 
296
  if latest_p2['analysis']['threats']:
297
  st.markdown(f"โš”๏ธ **Threats:** {', '.join(latest_p2['analysis']['threats'])}")
298
 
 
299
  st.markdown(f"**Score:** {latest_p2['analysis']['score']} points")
 
 
300
  st.markdown("**Algorithm Analysis:**")
301
  perf_col1, perf_col2 = st.columns(2)
302
  with perf_col1:
303
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{latest_p2["analysis"]["nodes_explored"]:,}</div><div class="stat-label">Explored</div></div>', unsafe_allow_html=True)
 
 
 
 
 
304
  with perf_col2:
305
+ st.markdown(f'<div class="stats-card"><div class="stat-number">{latest_p2["analysis"]["nodes_pruned"]:,}</div><div class="stat-label">Pruned</div></div>', unsafe_allow_html=True)
 
 
 
 
 
 
306
  st.markdown('</div>', unsafe_allow_html=True)
307
 
 
308
  if st.session_state.show_winner_popup and st.session_state.game_over:
309
  if st.session_state.winner == "Human":
310
  st.balloons()
311
  st.success("# ๐ŸŽ‰ YOU WON! ๐ŸŽ‰")
312
  elif st.session_state.winner == "AI":
313
+ st.info("# ๐Ÿค– AI WON!")
314
  elif "Player" in st.session_state.winner:
315
  st.balloons()
316
  st.success(f"# ๐ŸŽ‰ {st.session_state.winner.upper()} WON! ๐ŸŽ‰")
 
319
 
320
  st.session_state.show_winner_popup = False
321
 
 
322
  if st.session_state.game_over:
323
  st.markdown("---")
324
  st.markdown("### ๐Ÿ“„ Download Match Report")
 
326
 
327
  report = generate_match_report(
328
  st.session_state.game_mode,
329
+ "Ultra AI",
330
  st.session_state.winner,
331
  st.session_state.move_history_detailed,
332
  st.session_state.game.board_to_string(),