ShubhanshuBansod commited on
Commit
b016056
ยท
verified ยท
1 Parent(s): c7186df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +300 -87
app.py CHANGED
@@ -2,16 +2,16 @@ import streamlit as st
2
  import os
3
  from connect_four_game import ConnectFour
4
  from minimax_ai import MinimaxAI
5
- from llm_explanation import MoveExplainer
6
 
7
  # Page configuration
8
  st.set_page_config(
9
- page_title="Connect Four AI",
10
  layout="wide",
11
  initial_sidebar_state="expanded"
12
  )
13
 
14
- # Custom CSS with monospace for board
15
  st.markdown('''
16
  <style>
17
  .board-display {
@@ -20,41 +20,132 @@ st.markdown('''
20
  line-height: 1.4;
21
  font-size: 16px;
22
  padding: 20px;
23
- background-color: #f8f9fa;
 
 
 
 
 
 
 
24
  border-radius: 10px;
25
- border: 2px solid #667eea;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
- .stMetric { background-color: #f0f2f6; padding: 10px; border-radius: 8px; }
28
- .move-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
29
- color: white; padding: 20px; border-radius: 10px; margin: 10px 0; }
30
- .success-box { background-color: #d4edda; padding: 15px; border-radius: 8px; }
31
- .info-box { background-color: #d1ecf1; padding: 15px; border-radius: 8px; }
32
  </style>
33
  ''', unsafe_allow_html=True)
34
 
35
  # Initialize session state
36
  if 'game' not in st.session_state:
37
  st.session_state.game = ConnectFour()
38
- st.session_state.ai = MinimaxAI(st.session_state.game, depth=5)
 
 
 
39
 
40
  hf_token = os.getenv('HF_TOKEN')
41
- if hf_token:
42
- st.session_state.explainer = MoveExplainer(hf_token=hf_token)
43
- else:
44
- st.session_state.explainer = None
45
 
46
  st.session_state.move_history = []
47
  st.session_state.game_over = False
48
  st.session_state.winner = None
 
49
  st.session_state.hf_token_missing = hf_token is None
50
 
51
- # Title
52
- st.title("๐ŸŽฎ Connect Four AI with Explainability")
53
- st.markdown("Play against an intelligent AI powered by **Minimax Algorithm** โšก")
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  # Sidebar
56
- st.sidebar.header("โš™๏ธ Game Controls")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
 
 
 
58
  col1, col2 = st.sidebar.columns(2)
59
  with col1:
60
  if st.button("๐Ÿ”„ New Game", use_container_width=True):
@@ -62,20 +153,24 @@ with col1:
62
  st.session_state.move_history = []
63
  st.session_state.game_over = False
64
  st.session_state.winner = None
 
65
  st.rerun()
66
 
67
  with col2:
68
  if st.button("โ†ฉ๏ธ Undo", use_container_width=True,
69
- disabled=len(st.session_state.move_history) < 2):
70
- if len(st.session_state.move_history) >= 2:
71
- st.session_state.game.undo_move(st.session_state.move_history[-1])
72
- st.session_state.game.undo_move(st.session_state.move_history[-2])
73
- st.session_state.move_history = st.session_state.move_history[:-2]
74
- st.session_state.game_over = False
75
- st.rerun()
76
 
77
- search_depth = st.sidebar.slider("๐Ÿง  AI Search Depth", 3, 7, 5)
78
- st.session_state.ai.depth = search_depth
 
 
 
 
 
79
 
80
  st.sidebar.markdown("---")
81
 
@@ -84,34 +179,43 @@ with st.sidebar.expander("๐Ÿ“‹ Game Rules"):
84
  st.markdown('''
85
  **Objective:** Get 4 pieces in a row!
86
 
87
- **Rules:**
88
- - Drop your piece in any column
89
- - Pieces fall to the lowest spot
90
  - First to 4 in a row wins
91
 
92
  **Winning Patterns:**
93
  - Horizontal โ†โ†’
94
  - Vertical โ†•๏ธ
95
  - Diagonal โ†—๏ธ โ†–๏ธ
 
 
 
 
96
  ''')
97
 
98
- # Concepts
99
  with st.sidebar.expander("๐Ÿง  AI Concepts"):
100
  st.markdown('''
101
- **Minimax:** AI evaluates all moves recursively
102
-
103
- **Alpha-Beta:** Prunes bad branches (saves 80%+ time)
104
-
105
- **Heuristic:** Scores positions without playing to end
106
-
107
- **Search Depth:** Moves ahead AI thinks (5 = 3 moves)
 
 
 
 
 
 
 
 
108
  ''')
109
 
110
  st.sidebar.markdown("---")
111
- st.sidebar.write(f"**Moves:** {len(st.session_state.move_history)}")
112
-
113
- if st.session_state.hf_token_missing:
114
- st.warning("โš ๏ธ HF Token not set. Using fallback explanations.")
115
 
116
  # Main area
117
  col1, col2 = st.columns([2, 3])
@@ -119,84 +223,193 @@ col1, col2 = st.columns([2, 3])
119
  with col1:
120
  st.subheader("๐ŸŽฏ Game Board")
121
 
122
- # Display board with monospace formatting
123
  board_str = st.session_state.game.board_to_string()
124
  st.markdown(f'<div class="board-display">{board_str}</div>', unsafe_allow_html=True)
125
 
126
  if not st.session_state.game_over:
127
- st.markdown("### Your Move")
 
 
 
 
 
128
  valid_cols = st.session_state.game.get_valid_moves()
129
 
130
  if valid_cols:
131
- human_move = st.selectbox("Column:", valid_cols)
132
 
133
  if st.button("๐Ÿ“ Play", use_container_width=True):
134
- st.session_state.game.make_move(human_move, player=1)
 
 
135
  st.session_state.move_history.append(human_move)
136
 
137
- if st.session_state.game.check_winner(1):
 
 
 
 
 
 
 
 
 
 
138
  st.session_state.game_over = True
139
- st.session_state.winner = "Human"
 
 
 
140
  elif st.session_state.game.is_board_full():
141
  st.session_state.game_over = True
142
  st.session_state.winner = "Draw"
143
  else:
144
- with st.spinner("๐Ÿค– AI thinking..."):
145
- ai_result = st.session_state.ai.get_best_move()
146
-
147
- st.session_state.game.make_move(ai_result['move'], player=2)
148
- st.session_state.move_history.append(ai_result['move'])
149
- st.session_state.latest_ai_move = ai_result
150
-
151
- if st.session_state.game.check_winner(2):
152
- st.session_state.game_over = True
153
- st.session_state.winner = "AI"
154
- elif st.session_state.game.is_board_full():
155
- st.session_state.game_over = True
156
- st.session_state.winner = "Draw"
 
 
 
 
 
157
 
158
  st.rerun()
159
 
160
  with col2:
161
  st.subheader("๐Ÿค– AI Analysis")
162
 
163
- if hasattr(st.session_state, 'latest_ai_move'):
 
164
  ai_move = st.session_state.latest_ai_move
165
 
 
166
  if st.session_state.explainer:
167
  explanation = st.session_state.explainer.explain_move(ai_move)
168
  else:
169
  explanation = {
170
  'explanation': f"Column {ai_move['move']} scores {ai_move['score']} points.",
171
- 'threat_analysis': "; ".join(ai_move['threats']) if ai_move['threats'] else "Good move",
172
- 'key_insight': "Strategic move",
173
  'success': False
174
  }
175
 
176
- st.markdown(f"**Move:** Column {ai_move['move']} | **Score:** {ai_move['score']}")
177
- st.success(explanation['explanation'])
178
- st.warning(f"**Threats:** {explanation['threat_analysis']}")
179
-
180
- with st.expander("๐Ÿ“Š Algorithm Stats"):
181
- col1, col2, col3 = st.columns(3)
182
- with col1:
183
- st.metric("Explored", f"{ai_move['nodes_explored']:,}")
184
- with col2:
185
- st.metric("Pruned", f"{ai_move['nodes_pruned']:,}")
186
- with col3:
187
- st.metric("Efficiency", f"{ai_move['pruning_efficiency']:.0f}%")
188
-
189
- col4, col5 = st.columns(2)
190
- with col4:
191
- st.metric("Depth", ai_move['depth'])
192
- with col5:
193
- st.metric("Time", f"{ai_move['time_ms']:.0f}ms")
194
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  if st.session_state.game_over:
196
  st.markdown("---")
197
  if st.session_state.winner == "Human":
198
- st.success("๐ŸŽ‰ You won!")
199
  elif st.session_state.winner == "AI":
200
- st.info("๐Ÿค– AI won!")
 
 
201
  else:
202
- st.info("๐Ÿค Draw!")
 
2
  import os
3
  from connect_four_game import ConnectFour
4
  from minimax_ai import MinimaxAI
5
+ from llm_explanation_hf import MoveExplainer
6
 
7
  # Page configuration
8
  st.set_page_config(
9
+ page_title="Connect Four AI Pro",
10
  layout="wide",
11
  initial_sidebar_state="expanded"
12
  )
13
 
14
+ # Enhanced CSS with modern design
15
  st.markdown('''
16
  <style>
17
  .board-display {
 
20
  line-height: 1.4;
21
  font-size: 16px;
22
  padding: 20px;
23
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
24
+ color: white;
25
+ border-radius: 12px;
26
+ box-shadow: 0 8px 16px rgba(0,0,0,0.2);
27
+ }
28
+ .stats-card {
29
+ background: white;
30
+ padding: 15px;
31
  border-radius: 10px;
32
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
33
+ margin: 10px 0;
34
+ border-left: 4px solid #667eea;
35
+ }
36
+ .stat-number {
37
+ font-size: 24px;
38
+ font-weight: bold;
39
+ color: #667eea;
40
+ }
41
+ .stat-label {
42
+ font-size: 12px;
43
+ color: #666;
44
+ text-transform: uppercase;
45
+ }
46
+ .move-card {
47
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
48
+ color: white;
49
+ padding: 20px;
50
+ border-radius: 12px;
51
+ margin: 10px 0;
52
+ box-shadow: 0 6px 12px rgba(0,0,0,0.15);
53
+ }
54
+ .mode-badge {
55
+ display: inline-block;
56
+ padding: 4px 12px;
57
+ border-radius: 20px;
58
+ font-size: 12px;
59
+ font-weight: bold;
60
+ margin-right: 8px;
61
+ }
62
+ .ultra-fast {
63
+ background: #10b981;
64
+ color: white;
65
+ }
66
+ .balanced {
67
+ background: #3b82f6;
68
+ color: white;
69
+ }
70
+ .two-player {
71
+ background: #f59e0b;
72
+ color: white;
73
  }
 
 
 
 
 
74
  </style>
75
  ''', unsafe_allow_html=True)
76
 
77
  # Initialize session state
78
  if 'game' not in st.session_state:
79
  st.session_state.game = ConnectFour()
80
+ st.session_state.ai_mode = "Ultra-Fast"
81
+ st.session_state.game_mode = "vs AI"
82
+ st.session_state.ai_ultra = MinimaxAI(st.session_state.game, depth=5)
83
+ st.session_state.ai_balanced = MinimaxAI(st.session_state.game, depth=6)
84
 
85
  hf_token = os.getenv('HF_TOKEN')
86
+ st.session_state.explainer = MoveExplainer(hf_token=hf_token) if hf_token else None
 
 
 
87
 
88
  st.session_state.move_history = []
89
  st.session_state.game_over = False
90
  st.session_state.winner = None
91
+ st.session_state.current_player = 1
92
  st.session_state.hf_token_missing = hf_token is None
93
 
94
+ # Title with mode badge
95
+ col_title1, col_title2 = st.columns([3, 1])
96
+ with col_title1:
97
+ st.title("๐ŸŽฎ Connect Four AI Pro")
98
+ with col_title2:
99
+ if st.session_state.game_mode == "vs AI":
100
+ mode_class = "ultra-fast" if st.session_state.ai_mode == "Ultra-Fast" else "balanced"
101
+ st.markdown(f'<span class="mode-badge {mode_class}">{st.session_state.ai_mode} AI</span>',
102
+ unsafe_allow_html=True)
103
+ else:
104
+ st.markdown('<span class="mode-badge two-player">Two Player</span>',
105
+ unsafe_allow_html=True)
106
+
107
+ st.markdown("**Advanced Connect Four with AI Analysis & Multiple Modes**")
108
 
109
  # Sidebar
110
+ st.sidebar.header("โš™๏ธ Game Settings")
111
+
112
+ # Game Mode Selection
113
+ game_mode = st.sidebar.radio(
114
+ "๐ŸŽฎ Game Mode",
115
+ ["vs AI", "Two Player"],
116
+ index=0 if st.session_state.game_mode == "vs AI" else 1
117
+ )
118
+
119
+ if game_mode != st.session_state.game_mode:
120
+ st.session_state.game_mode = game_mode
121
+ st.session_state.game.reset()
122
+ st.session_state.move_history = []
123
+ st.session_state.game_over = False
124
+ st.session_state.current_player = 1
125
+ st.rerun()
126
+
127
+ # AI Mode Selection (only for vs AI)
128
+ if st.session_state.game_mode == "vs AI":
129
+ st.sidebar.markdown("---")
130
+ st.sidebar.subheader("๐Ÿค– AI Difficulty")
131
+
132
+ ai_mode = st.sidebar.radio(
133
+ "Select AI Mode",
134
+ ["Ultra-Fast โšก", "Balanced ๐ŸŽฏ"],
135
+ index=0 if st.session_state.ai_mode == "Ultra-Fast" else 1,
136
+ help="Ultra-Fast: ~200ms, Depth 5\nBalanced: ~400ms, Depth 6, Better analysis"
137
+ )
138
+
139
+ if "Ultra-Fast" in ai_mode:
140
+ st.session_state.ai_mode = "Ultra-Fast"
141
+ st.session_state.current_ai = st.session_state.ai_ultra
142
+ else:
143
+ st.session_state.ai_mode = "Balanced"
144
+ st.session_state.current_ai = st.session_state.ai_balanced
145
 
146
+ st.sidebar.markdown("---")
147
+
148
+ # Game Controls
149
  col1, col2 = st.sidebar.columns(2)
150
  with col1:
151
  if st.button("๐Ÿ”„ New Game", use_container_width=True):
 
153
  st.session_state.move_history = []
154
  st.session_state.game_over = False
155
  st.session_state.winner = None
156
+ st.session_state.current_player = 1
157
  st.rerun()
158
 
159
  with col2:
160
  if st.button("โ†ฉ๏ธ Undo", use_container_width=True,
161
+ disabled=len(st.session_state.move_history) == 0):
162
+ if st.session_state.game_mode == "vs AI":
163
+ undo_count = min(2, len(st.session_state.move_history))
164
+ else:
165
+ undo_count = 1
 
 
166
 
167
+ for _ in range(undo_count):
168
+ if st.session_state.move_history:
169
+ st.session_state.game.undo_move(st.session_state.move_history[-1])
170
+ st.session_state.move_history.pop()
171
+
172
+ st.session_state.game_over = False
173
+ st.rerun()
174
 
175
  st.sidebar.markdown("---")
176
 
 
179
  st.markdown('''
180
  **Objective:** Get 4 pieces in a row!
181
 
182
+ **How to Play:**
183
+ - Select a column (0-6)
184
+ - Piece falls to lowest spot
185
  - First to 4 in a row wins
186
 
187
  **Winning Patterns:**
188
  - Horizontal โ†โ†’
189
  - Vertical โ†•๏ธ
190
  - Diagonal โ†—๏ธ โ†–๏ธ
191
+
192
+ **Game Modes:**
193
+ - **vs AI:** Play against intelligent AI
194
+ - **Two Player:** Play with a friend
195
  ''')
196
 
197
+ # AI Concepts
198
  with st.sidebar.expander("๐Ÿง  AI Concepts"):
199
  st.markdown('''
200
+ **Ultra-Fast AI โšก**
201
+ - Speed: ~200ms
202
+ - Depth: 5 (analyzes 3 moves ahead)
203
+ - Best for quick games
204
+
205
+ **Balanced AI ๐ŸŽฏ**
206
+ - Speed: ~400ms
207
+ - Depth: 6 (analyzes 4 moves ahead)
208
+ - Better strategic analysis
209
+ - Richer explanations
210
+
211
+ **Two Player Mode**
212
+ - AI analyzes each move
213
+ - Shows strategy for both players
214
+ - Learn from AI insights
215
  ''')
216
 
217
  st.sidebar.markdown("---")
218
+ st.sidebar.write(f"**Moves Played:** {len(st.session_state.move_history)}")
 
 
 
219
 
220
  # Main area
221
  col1, col2 = st.columns([2, 3])
 
223
  with col1:
224
  st.subheader("๐ŸŽฏ Game Board")
225
 
226
+ # Display board
227
  board_str = st.session_state.game.board_to_string()
228
  st.markdown(f'<div class="board-display">{board_str}</div>', unsafe_allow_html=True)
229
 
230
  if not st.session_state.game_over:
231
+ if st.session_state.game_mode == "Two Player":
232
+ player_name = "Player 1 (๐Ÿ”ด)" if st.session_state.current_player == 1 else "Player 2 (๐ŸŸก)"
233
+ st.markdown(f"### {player_name}'s Turn")
234
+ else:
235
+ st.markdown("### Your Move (๐Ÿ”ด)")
236
+
237
  valid_cols = st.session_state.game.get_valid_moves()
238
 
239
  if valid_cols:
240
+ human_move = st.selectbox("Choose column:", valid_cols, key="move_select")
241
 
242
  if st.button("๐Ÿ“ Play", use_container_width=True):
243
+ # Make move
244
+ current_player = st.session_state.current_player if st.session_state.game_mode == "Two Player" else 1
245
+ st.session_state.game.make_move(human_move, player=current_player)
246
  st.session_state.move_history.append(human_move)
247
 
248
+ # Analyze move with AI
249
+ if st.session_state.game_mode == "Two Player":
250
+ ai_analysis = st.session_state.ai_balanced.get_best_move()
251
+ st.session_state.latest_move_analysis = {
252
+ 'player': current_player,
253
+ 'move': human_move,
254
+ 'analysis': ai_analysis
255
+ }
256
+
257
+ # Check winner
258
+ if st.session_state.game.check_winner(current_player):
259
  st.session_state.game_over = True
260
+ if st.session_state.game_mode == "Two Player":
261
+ st.session_state.winner = f"Player {current_player}"
262
+ else:
263
+ st.session_state.winner = "Human"
264
  elif st.session_state.game.is_board_full():
265
  st.session_state.game_over = True
266
  st.session_state.winner = "Draw"
267
  else:
268
+ # AI move (only in vs AI mode)
269
+ if st.session_state.game_mode == "vs AI":
270
+ with st.spinner(f"๐Ÿค– {st.session_state.ai_mode} AI thinking..."):
271
+ ai_result = st.session_state.current_ai.get_best_move()
272
+
273
+ st.session_state.game.make_move(ai_result['move'], player=2)
274
+ st.session_state.move_history.append(ai_result['move'])
275
+ st.session_state.latest_ai_move = ai_result
276
+
277
+ if st.session_state.game.check_winner(2):
278
+ st.session_state.game_over = True
279
+ st.session_state.winner = "AI"
280
+ elif st.session_state.game.is_board_full():
281
+ st.session_state.game_over = True
282
+ st.session_state.winner = "Draw"
283
+ else:
284
+ # Switch player in Two Player mode
285
+ st.session_state.current_player = 2 if current_player == 1 else 1
286
 
287
  st.rerun()
288
 
289
  with col2:
290
  st.subheader("๐Ÿค– AI Analysis")
291
 
292
+ # Display analysis based on mode
293
+ if st.session_state.game_mode == "vs AI" and hasattr(st.session_state, 'latest_ai_move'):
294
  ai_move = st.session_state.latest_ai_move
295
 
296
+ # Get explanation
297
  if st.session_state.explainer:
298
  explanation = st.session_state.explainer.explain_move(ai_move)
299
  else:
300
  explanation = {
301
  'explanation': f"Column {ai_move['move']} scores {ai_move['score']} points.",
302
+ 'threat_analysis': "; ".join(ai_move['threats']) if ai_move['threats'] else "Strategic move",
303
+ 'key_insight': "Good positioning",
304
  'success': False
305
  }
306
 
307
+ # Move card
308
+ st.markdown(f'''
309
+ <div class="move-card">
310
+ <h3>๐ŸŽฏ AI Played: Column {ai_move['move']}</h3>
311
+ <p><strong>Evaluation Score:</strong> {ai_move['score']} points</p>
312
+ <p><strong>Mode:</strong> {st.session_state.ai_mode}</p>
313
+ </div>
314
+ ''', unsafe_allow_html=True)
315
+
316
+ st.success(f"๐Ÿ’ก **Why This Move:**\n{explanation['explanation']}")
317
+ st.warning(f"โš”๏ธ **Strategic Analysis:**\n{explanation['threat_analysis']}")
318
+ st.info(f"๐Ÿ”‘ **Key Insight:**\n{explanation['key_insight']}")
319
+
320
+ # Professional stats UI
321
+ st.markdown("### ๐Ÿ“Š Algorithm Performance")
322
+
323
+ stat_col1, stat_col2, stat_col3 = st.columns(3)
324
+ with stat_col1:
325
+ st.markdown(f'''
326
+ <div class="stats-card">
327
+ <div class="stat-number">{ai_move['nodes_explored']:,}</div>
328
+ <div class="stat-label">Nodes Explored</div>
329
+ </div>
330
+ ''', unsafe_allow_html=True)
331
+ with stat_col2:
332
+ st.markdown(f'''
333
+ <div class="stats-card">
334
+ <div class="stat-number">{ai_move['nodes_pruned']:,}</div>
335
+ <div class="stat-label">Nodes Pruned</div>
336
+ </div>
337
+ ''', unsafe_allow_html=True)
338
+ with stat_col3:
339
+ st.markdown(f'''
340
+ <div class="stats-card">
341
+ <div class="stat-number">{ai_move['pruning_efficiency']:.1f}%</div>
342
+ <div class="stat-label">Efficiency</div>
343
+ </div>
344
+ ''', unsafe_allow_html=True)
345
+
346
+ stat_col4, stat_col5 = st.columns(2)
347
+ with stat_col4:
348
+ st.markdown(f'''
349
+ <div class="stats-card">
350
+ <div class="stat-number">{ai_move['depth']}</div>
351
+ <div class="stat-label">Search Depth</div>
352
+ </div>
353
+ ''', unsafe_allow_html=True)
354
+ with stat_col5:
355
+ st.markdown(f'''
356
+ <div class="stats-card">
357
+ <div class="stat-number">{ai_move['time_ms']:.0f}ms</div>
358
+ <div class="stat-label">Response Time</div>
359
+ </div>
360
+ ''', unsafe_allow_html=True)
361
+
362
+ elif st.session_state.game_mode == "Two Player" and hasattr(st.session_state, 'latest_move_analysis'):
363
+ analysis = st.session_state.latest_move_analysis
364
+ player = analysis['player']
365
+ player_name = f"Player {player} ({'๐Ÿ”ด' if player == 1 else '๐ŸŸก'})"
366
+
367
+ st.markdown(f'''
368
+ <div class="move-card">
369
+ <h3>๐Ÿ“Š {player_name} Move Analysis</h3>
370
+ <p><strong>Column Played:</strong> {analysis['move']}</p>
371
+ </div>
372
+ ''', unsafe_allow_html=True)
373
+
374
+ ai_data = analysis['analysis']
375
+
376
+ # AI's recommended move vs actual
377
+ if ai_data['move'] == analysis['move']:
378
+ st.success(f"โœ… **Excellent Move!** AI would play the same (Column {ai_data['move']})")
379
+ else:
380
+ st.warning(f"๐Ÿค” **Alternative:** AI suggests Column {ai_data['move']} (Score: {ai_data['score']})")
381
+
382
+ # Threats
383
+ if ai_data['threats']:
384
+ st.info(f"โš”๏ธ **Strategic Info:** {'; '.join(ai_data['threats'])}")
385
+
386
+ # Stats
387
+ st.markdown("### ๐Ÿ“Š Position Analysis")
388
+
389
+ stat_col1, stat_col2 = st.columns(2)
390
+ with stat_col1:
391
+ st.markdown(f'''
392
+ <div class="stats-card">
393
+ <div class="stat-number">{ai_data['nodes_explored']:,}</div>
394
+ <div class="stat-label">Positions Analyzed</div>
395
+ </div>
396
+ ''', unsafe_allow_html=True)
397
+ with stat_col2:
398
+ st.markdown(f'''
399
+ <div class="stats-card">
400
+ <div class="stat-number">{ai_data['time_ms']:.0f}ms</div>
401
+ <div class="stat-label">Analysis Time</div>
402
+ </div>
403
+ ''', unsafe_allow_html=True)
404
+
405
+ # Game outcome
406
  if st.session_state.game_over:
407
  st.markdown("---")
408
  if st.session_state.winner == "Human":
409
+ st.success("๐ŸŽ‰ **You Won! Congratulations!**")
410
  elif st.session_state.winner == "AI":
411
+ st.info(f"๐Ÿค– **{st.session_state.ai_mode} AI Won! Well played!**")
412
+ elif "Player" in st.session_state.winner:
413
+ st.success(f"๐ŸŽ‰ **{st.session_state.winner} Won!**")
414
  else:
415
+ st.info("๐Ÿค **It's a Draw! Board is full.**")