ShubhanshuBansod commited on
Commit
7249378
ยท
verified ยท
1 Parent(s): 94f519c

Upload 5 files

Browse files
Files changed (4) hide show
  1. app.py +484 -486
  2. connect_four_game.py +29 -29
  3. minimax_ai.py +2 -8
  4. report_generator.py +24 -29
app.py CHANGED
@@ -6,334 +6,359 @@ from minimax_ai import MinimaxAI
6
  from llm_explanation import MoveExplainer
7
  from report_generator import generate_match_report
8
 
9
- st.set_page_config(page_title="Connect Four AI Pro", layout="wide", initial_sidebar_state="expanded")
 
 
 
 
10
 
 
11
  st.markdown('''
12
  <style>
13
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
 
14
 
 
15
  * {
16
- font-family: 'Inter', sans-serif;
17
  }
18
 
 
19
  .stApp {
20
- background: #0f172a;
 
21
  }
22
 
23
  /* Sidebar Styling */
24
  [data-testid="stSidebar"] {
25
- background: #1e293b;
26
- border-right: 1px solid #334155;
27
  }
28
 
29
- [data-testid="stSidebar"] * {
30
- color: #e2e8f0 !important;
 
 
 
 
31
  }
32
 
33
- [data-testid="stSidebar"] .stRadio > label {
34
- color: #f1f5f9 !important;
35
- font-weight: 600;
 
36
  }
37
 
38
- [data-testid="stSidebar"] h3 {
39
- color: #f8fafc !important;
40
- font-size: 16px !important;
41
- font-weight: 700 !important;
42
- margin-bottom: 16px !important;
43
- }
44
-
45
- /* Main Content */
46
  h1 {
47
- color: #f8fafc !important;
48
- font-weight: 700 !important;
49
- font-size: 36px !important;
50
- margin-bottom: 8px !important;
 
 
 
 
 
 
51
  }
52
 
53
- h2 {
54
- color: #f1f5f9 !important;
55
- font-weight: 600 !important;
56
- font-size: 24px !important;
57
- margin-top: 24px !important;
58
- margin-bottom: 16px !important;
 
59
  }
60
 
61
- h3 {
62
- color: #e2e8f0 !important;
63
- font-weight: 600 !important;
64
- font-size: 18px !important;
65
- margin-bottom: 12px !important;
 
 
 
 
66
  }
67
 
68
- /* Board Container */
69
  .board-container {
70
- background: #1e293b;
71
- border-radius: 16px;
72
- padding: 24px;
73
- margin-bottom: 24px;
74
- border: 1px solid #334155;
75
- }
76
-
77
- .board-display {
78
- font-family: 'Courier New', monospace;
79
- white-space: pre;
80
- line-height: 1.5;
81
- font-size: 20px;
82
- padding: 24px;
83
- background: #0f172a;
84
- color: #f8fafc;
85
- border-radius: 12px;
86
- border: 2px solid #475569;
87
- display: inline-block;
88
- margin: 0 auto;
89
- }
90
-
91
- /* Stats Cards */
92
- .stats-card {
93
- background: #1e293b;
94
- padding: 20px;
95
- border-radius: 12px;
96
- border: 1px solid #334155;
97
  text-align: center;
98
- margin: 8px 0;
99
  }
100
 
101
- .stat-number {
102
- font-size: 28px;
103
- font-weight: 700;
104
- color: #60a5fa;
105
- display: block;
106
- margin-bottom: 4px;
107
- }
108
-
109
- .stat-label {
110
- font-size: 11px;
111
- font-weight: 600;
112
- color: #94a3b8;
113
  text-transform: uppercase;
114
- letter-spacing: 0.5px;
115
- }
116
-
117
- /* Player Sections */
118
- .player-section {
119
- background: #1e293b;
120
- padding: 24px;
121
- border-radius: 12px;
122
- border: 1px solid #334155;
123
- margin-bottom: 20px;
124
  }
125
 
126
- .human-section {
127
- border-left: 4px solid #3b82f6;
 
 
 
128
  }
129
 
130
- .ai-section {
131
- border-left: 4px solid #f59e0b;
 
 
 
132
  }
133
 
134
- .player1-section {
135
- border-left: 4px solid #ef4444;
 
 
 
 
 
136
  }
137
 
138
- .player2-section {
139
- border-left: 4px solid #f59e0b;
 
140
  }
141
 
142
- .section-header {
143
- font-size: 16px;
144
- font-weight: 700;
145
- color: #f1f5f9;
146
- margin-bottom: 16px;
147
- display: flex;
148
- align-items: center;
149
- gap: 8px;
150
  }
151
 
152
- .info-row {
153
- color: #cbd5e1;
154
- font-size: 14px;
155
- margin: 8px 0;
156
- line-height: 1.6;
157
- }
158
-
159
- .info-row strong {
160
- color: #f1f5f9;
161
- font-weight: 600;
162
  }
163
 
164
- /* Badges */
165
- .badge {
166
  display: inline-block;
167
- padding: 4px 12px;
168
- border-radius: 20px;
169
- font-size: 12px;
170
- font-weight: 600;
171
- margin: 4px 4px 4px 0;
172
- }
173
-
174
- .badge-success {
175
- background: #10b981;
176
  color: white;
 
 
 
 
 
 
 
 
 
 
177
  }
178
 
179
- .badge-warning {
180
- background: #f59e0b;
181
- color: white;
 
 
 
 
182
  }
183
 
184
- .badge-info {
185
- background: #3b82f6;
 
 
186
  color: white;
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
- .badge-threat {
190
- background: #1e293b;
191
- color: #94a3b8;
192
- border: 1px solid #334155;
193
- }
194
-
195
- /* Move Card */
196
- .move-card {
197
- background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
198
- color: white;
199
- padding: 20px;
200
- border-radius: 12px;
201
- margin-bottom: 16px;
202
  text-align: center;
 
203
  }
204
 
205
- .move-card-title {
206
- font-size: 18px;
207
- font-weight: 700;
208
- margin-bottom: 8px;
 
 
 
 
 
209
  }
210
 
211
- .move-card-score {
212
- font-size: 14px;
213
- opacity: 0.9;
214
- }
215
-
216
- /* Winner Banner */
217
- .winner-banner {
218
- background: linear-gradient(135deg, #10b981 0%, #059669 100%);
219
- color: white;
220
- padding: 32px;
221
- border-radius: 16px;
222
- text-align: center;
223
- font-size: 32px;
224
- font-weight: 700;
225
- margin: 24px 0;
226
- box-shadow: 0 10px 30px rgba(16, 185, 129, 0.3);
227
  }
228
 
229
- .winner-banner.ai {
230
- background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
231
- box-shadow: 0 10px 30px rgba(245, 158, 11, 0.3);
 
 
232
  }
233
 
234
- .winner-banner.draw {
235
- background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
236
- box-shadow: 0 10px 30px rgba(99, 102, 241, 0.3);
237
  }
238
 
239
- /* Buttons */
240
- .stButton > button {
241
- background: #3b82f6;
242
- color: white;
243
- border: none;
244
- border-radius: 8px;
245
- font-weight: 600;
246
- padding: 8px 16px;
247
- transition: all 0.2s;
248
  }
249
 
250
- .stButton > button:hover {
251
- background: #2563eb;
252
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
 
253
  }
254
 
255
- .stButton > button[kind="primary"] {
256
- background: #10b981;
 
 
 
 
257
  }
258
 
259
- .stButton > button[kind="primary"]:hover {
260
- background: #059669;
261
- box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
 
262
  }
263
 
264
- /* Select Box */
265
- .stSelectbox > div > div {
266
- background: #1e293b;
267
- border: 1px solid #334155;
268
- color: #f1f5f9;
269
- border-radius: 8px;
270
  }
271
 
272
- /* Metric boxes */
273
- [data-testid="stMetricValue"] {
274
- color: #60a5fa !important;
275
- font-size: 24px !important;
276
- font-weight: 700 !important;
277
  }
278
 
279
- [data-testid="stMetricLabel"] {
280
- color: #94a3b8 !important;
281
- font-size: 13px !important;
282
  }
283
 
284
- /* Streamlit native elements */
285
- .stAlert {
286
- background: #1e293b;
287
- border: 1px solid #334155;
288
- border-radius: 8px;
289
- color: #e2e8f0;
290
  }
291
 
292
- div[data-baseweb="notification"] {
293
- background: #1e293b;
294
- border: 1px solid #334155;
295
  }
296
 
297
- /* Success/Warning/Info boxes */
298
- .stSuccess {
299
- background: rgba(16, 185, 129, 0.1) !important;
300
- border-left: 4px solid #10b981 !important;
301
- color: #a7f3d0 !important;
 
 
 
 
 
 
302
  }
303
 
304
- .stWarning {
305
- background: rgba(245, 158, 11, 0.1) !important;
306
- border-left: 4px solid #f59e0b !important;
307
- color: #fde68a !important;
308
  }
309
 
310
- .stInfo {
311
- background: rgba(59, 130, 246, 0.1) !important;
312
- border-left: 4px solid #3b82f6 !important;
313
- color: #bfdbfe !important;
 
 
 
 
314
  }
315
 
316
- /* Hide Streamlit elements */
317
- #MainMenu {visibility: hidden;}
318
- footer {visibility: hidden;}
319
-
320
- /* Expander */
321
- .streamlit-expanderHeader {
322
- background: #1e293b;
323
- color: #f1f5f9 !important;
324
- border-radius: 8px;
325
  }
326
  </style>
327
  ''', unsafe_allow_html=True)
328
 
 
329
  if 'game' not in st.session_state:
330
  st.session_state.game = ConnectFour()
331
  st.session_state.game_mode = "vs AI"
332
  st.session_state.ai = MinimaxAI(st.session_state.game, depth=5)
333
-
334
  hf_token = os.getenv('HF_TOKEN')
335
  st.session_state.explainer = MoveExplainer(hf_token=hf_token) if hf_token else None
336
-
337
  st.session_state.move_history = []
338
  st.session_state.move_history_detailed = []
339
  st.session_state.player1_analyses = []
@@ -348,20 +373,24 @@ if 'game' not in st.session_state:
348
  st.session_state.last_ai_move_analysis = None
349
 
350
  # Header
351
- col_title1, col_title2 = st.columns([4, 1])
352
  with col_title1:
353
- st.title("๐ŸŽฎ Connect Four AI Pro")
 
354
  with col_title2:
355
  if st.session_state.game_mode == "vs AI":
356
- st.markdown('<span class="badge badge-info">๐Ÿค– VS AI</span>', unsafe_allow_html=True)
357
  else:
358
- st.markdown('<span class="badge badge-warning">๐Ÿ‘ฅ Two Player</span>', unsafe_allow_html=True)
359
 
360
- # Sidebar
361
- st.sidebar.markdown("### โš™๏ธ Game Settings")
362
 
363
- game_mode = st.sidebar.radio("๐ŸŽฎ Game Mode", ["vs AI", "Two Player"],
364
- index=0 if st.session_state.game_mode == "vs AI" else 1)
 
 
 
365
 
366
  if game_mode != st.session_state.game_mode:
367
  st.session_state.game_mode = game_mode
@@ -377,13 +406,14 @@ if game_mode != st.session_state.game_mode:
377
 
378
  if st.session_state.game_mode == "vs AI":
379
  st.sidebar.markdown("---")
380
- st.sidebar.markdown("### ๐ŸŽš๏ธ AI Difficulty")
381
- depth = st.sidebar.slider("Search Depth", 3, 8, 5)
382
  st.session_state.ai.depth = depth
383
  st.session_state.current_depth = depth
384
 
385
  st.sidebar.markdown("---")
386
 
 
387
  col1, col2 = st.sidebar.columns(2)
388
  with col1:
389
  if st.button("๐Ÿ”„ New Game", use_container_width=True):
@@ -419,325 +449,293 @@ with col2:
419
  st.session_state.show_winner_popup = False
420
  st.rerun()
421
 
 
422
  st.sidebar.markdown("---")
423
- st.sidebar.markdown("### ๐Ÿ“Š Game Stats")
424
- st.sidebar.metric("Total Moves", st.session_state.move_count)
 
425
  if st.session_state.game_mode == "Two Player":
426
- col1, col2 = st.sidebar.columns(2)
427
- with col1:
428
- st.metric("๐Ÿ”ด P1", len(st.session_state.player1_analyses))
429
- with col2:
430
- st.metric("๐ŸŸก P2", len(st.session_state.player2_analyses))
431
 
 
432
  if st.session_state.move_history:
433
- with st.sidebar.expander("๐Ÿ“œ Move History"):
434
  for i, col in enumerate(st.session_state.move_history, 1):
435
  player = "๐Ÿ”ด" if i % 2 == 1 else "๐ŸŸก"
436
- st.write(f"**Move {i}:** {player} โ†’ Col {col}")
437
 
438
- # Main layout
439
  if st.session_state.game_mode == "vs AI":
440
  col1, col2 = st.columns([2, 3])
441
 
442
  with col1:
443
- st.markdown('<div class="board-container" style="text-align: center;">', unsafe_allow_html=True)
444
- st.markdown("### ๐ŸŽฏ Game Board")
445
  board_str = st.session_state.game.board_to_string()
446
- st.markdown(f'<div class="board-display">{board_str}</div>', unsafe_allow_html=True)
447
- st.markdown('</div>', unsafe_allow_html=True)
448
 
449
- if not st.session_state.game_over:
450
- st.markdown("### ๐Ÿ”ด Your Turn")
451
- valid_cols = st.session_state.game.get_valid_moves()
452
- if valid_cols:
453
- human_move = st.selectbox("Choose column:", valid_cols, key="move_select")
454
-
455
- if st.button("๐ŸŽฏ Play Move", use_container_width=True, type="primary"):
456
- current_player = 1
457
- st.session_state.game.make_move(human_move, player=current_player)
458
- st.session_state.move_history.append(human_move)
459
  st.session_state.move_count += 1
460
 
461
- ai_analysis = st.session_state.ai.get_best_move()
462
- st.session_state.last_human_move_analysis = ai_analysis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
 
464
  move_detail = {
465
- 'player': current_player,
466
- 'player_name': "Human",
467
- 'column': human_move,
468
- 'analysis': ai_analysis
 
 
469
  }
470
  st.session_state.move_history_detailed.append(move_detail)
471
 
472
- if st.session_state.game.check_winner(current_player):
 
473
  st.session_state.game_over = True
474
  st.session_state.winner = "Human"
475
  st.session_state.show_winner_popup = True
476
- elif st.session_state.game.is_board_full():
 
 
477
  st.session_state.game_over = True
478
  st.session_state.winner = "Draw"
479
  st.session_state.show_winner_popup = True
480
- else:
481
- with st.spinner("๐Ÿค– AI is thinking..."):
482
- ai_result = st.session_state.ai.get_best_move()
483
-
484
- st.session_state.game.make_move(ai_result['move'], player=2)
485
- st.session_state.move_history.append(ai_result['move'])
 
486
  st.session_state.move_count += 1
487
 
 
 
488
  if st.session_state.explainer:
489
- explanation = st.session_state.explainer.explain_move(ai_result)
490
- explanation_text = explanation.get('explanation', '')
491
- else:
492
- explanation = {
493
- 'explanation': f"Column {ai_result['move']} scores {ai_result['score']} points.",
494
- 'threat_analysis': "; ".join(ai_result['threats']) if ai_result['threats'] else "Strategic move",
495
- 'key_insight': "Strong position",
496
- 'success': False
497
  }
498
- explanation_text = explanation['explanation']
 
 
 
499
 
500
  ai_move_detail = {
501
  'player': 2,
502
- 'player_name': 'AI',
503
- 'column': ai_result['move'],
504
- 'analysis': ai_result,
505
- 'explanation': explanation_text
506
  }
507
  st.session_state.move_history_detailed.append(ai_move_detail)
508
- st.session_state.last_ai_move_analysis = ai_result
509
- st.session_state.last_explanation = explanation
510
 
 
511
  if st.session_state.game.check_winner(2):
512
  st.session_state.game_over = True
513
  st.session_state.winner = "AI"
514
  st.session_state.show_winner_popup = True
515
- elif st.session_state.game.is_board_full():
 
 
516
  st.session_state.game_over = True
517
  st.session_state.winner = "Draw"
518
  st.session_state.show_winner_popup = True
 
519
 
520
  st.rerun()
521
 
522
  with col2:
523
- st.markdown("## ๐Ÿ“Š Move Analysis")
524
 
525
- # Human Move Analysis
526
  if st.session_state.last_human_move_analysis:
527
- human_analysis = st.session_state.last_human_move_analysis
528
- st.markdown('<div class="player-section human-section">', unsafe_allow_html=True)
529
- st.markdown('<div class="section-header">๐Ÿ”ด Your Last Move</div>', unsafe_allow_html=True)
530
-
531
- st.markdown(f'<div class="info-row"><strong>Column:</strong> {st.session_state.move_history[-2] if len(st.session_state.move_history) >= 2 else "?"}</div>', unsafe_allow_html=True)
532
- st.markdown(f'<div class="info-row"><strong>Score:</strong> {human_analysis["score"]} points</div>', unsafe_allow_html=True)
533
-
534
- if human_analysis['threats']:
535
- st.markdown('<div class="info-row"><strong>Threats:</strong></div>', unsafe_allow_html=True)
536
- for threat in human_analysis['threats']:
537
- st.markdown(f'<span class="badge badge-threat">{threat}</span>', unsafe_allow_html=True)
538
-
539
- perf_col1, perf_col2, perf_col3 = st.columns(3)
540
- with perf_col1:
541
- st.markdown(f'<div class="stats-card"><span class="stat-number">{human_analysis["nodes_explored"]:,}</span><span class="stat-label">Explored</span></div>', unsafe_allow_html=True)
542
- with perf_col2:
543
- st.markdown(f'<div class="stats-card"><span class="stat-number">{human_analysis["nodes_pruned"]:,}</span><span class="stat-label">Pruned</span></div>', unsafe_allow_html=True)
544
- with perf_col3:
545
- st.markdown(f'<div class="stats-card"><span class="stat-number">{human_analysis["pruning_efficiency"]:.1f}%</span><span class="stat-label">Efficiency</span></div>', unsafe_allow_html=True)
546
-
547
  st.markdown('</div>', unsafe_allow_html=True)
548
 
549
- # AI Move Analysis
550
- if hasattr(st.session_state, 'last_ai_move_analysis') and st.session_state.last_ai_move_analysis:
551
- ai_move = st.session_state.last_ai_move_analysis
552
- explanation = getattr(st.session_state, 'last_explanation', {
553
- 'explanation': f"Column {ai_move['move']} scores {ai_move['score']} points.",
554
- 'threat_analysis': "; ".join(ai_move['threats']) if ai_move['threats'] else "Strategic move",
555
- 'key_insight': "Strong position",
556
- 'success': False
557
- })
558
-
559
- st.markdown('<div class="player-section ai-section">', unsafe_allow_html=True)
560
- st.markdown('<div class="section-header">๐ŸŸก AI Move Analysis</div>', unsafe_allow_html=True)
561
-
562
- st.markdown(f'<div class="move-card"><div class="move-card-title">Column {ai_move["move"]}</div><div class="move-card-score">Score: {ai_move["score"]} points</div></div>', unsafe_allow_html=True)
563
-
564
- st.success(f"๐Ÿ’ก **Strategy:** {explanation['explanation']}")
565
-
566
- if explanation.get('threat_analysis'):
567
- st.warning(f"โš”๏ธ **Threats:** {explanation['threat_analysis']}")
568
-
569
- if explanation.get('key_insight'):
570
- st.info(f"๐Ÿ”‘ **Insight:** {explanation['key_insight']}")
571
-
572
- perf_col1, perf_col2, perf_col3 = st.columns(3)
573
- with perf_col1:
574
- st.markdown(f'<div class="stats-card"><span class="stat-number">{ai_move["nodes_explored"]:,}</span><span class="stat-label">Explored</span></div>', unsafe_allow_html=True)
575
- with perf_col2:
576
- st.markdown(f'<div class="stats-card"><span class="stat-number">{ai_move["nodes_pruned"]:,}</span><span class="stat-label">Pruned</span></div>', unsafe_allow_html=True)
577
- with perf_col3:
578
- st.markdown(f'<div class="stats-card"><span class="stat-number">{ai_move["pruning_efficiency"]:.1f}%</span><span class="stat-label">Efficiency</span></div>', unsafe_allow_html=True)
579
-
580
- stat_col4, stat_col5 = st.columns(2)
581
- with stat_col4:
582
- st.markdown(f'<div class="stats-card"><span class="stat-number">{ai_move["depth"]}</span><span class="stat-label">Depth</span></div>', unsafe_allow_html=True)
583
- with stat_col5:
584
- st.markdown(f'<div class="stats-card"><span class="stat-number">{ai_move["time_ms"]:.0f}ms</span><span class="stat-label">Time</span></div>', unsafe_allow_html=True)
585
-
586
  st.markdown('</div>', unsafe_allow_html=True)
 
 
 
587
 
588
  else: # Two Player Mode
589
- st.markdown('<div class="board-container" style="text-align: center;">', unsafe_allow_html=True)
590
- st.markdown("### ๐ŸŽฏ Game Board")
591
- board_str = st.session_state.game.board_to_string()
592
- st.markdown(f'<div class="board-display">{board_str}</div>', unsafe_allow_html=True)
593
- st.markdown('</div>', unsafe_allow_html=True)
594
-
595
- if not st.session_state.game_over:
596
- player_name = f"Player {st.session_state.current_player}"
597
- player_emoji = "๐Ÿ”ด" if st.session_state.current_player == 1 else "๐ŸŸก"
598
- st.markdown(f"### {player_emoji} {player_name}'s Turn")
599
-
600
- valid_cols = st.session_state.game.get_valid_moves()
601
- if valid_cols:
602
- human_move = st.selectbox("Choose column:", valid_cols, key="move_select")
603
-
604
- if st.button("๐ŸŽฏ Play Move", use_container_width=True, type="primary"):
605
- current_player = st.session_state.current_player
606
- st.session_state.game.make_move(human_move, player=current_player)
607
- st.session_state.move_history.append(human_move)
608
- st.session_state.move_count += 1
609
-
610
- ai_analysis = st.session_state.ai.get_best_move()
611
- move_detail = {
612
- 'player': current_player,
613
- 'player_name': f"Player {current_player}",
614
- 'column': human_move,
615
- 'analysis': ai_analysis
616
- }
617
-
618
- move_detail['recommendation'] = {
619
- 'is_optimal': ai_analysis['move'] == human_move,
620
- 'suggested_column': ai_analysis['move'],
621
- 'score_diff': abs(ai_analysis['score'])
622
- }
623
-
624
- if current_player == 1:
625
- st.session_state.player1_analyses.append(move_detail)
626
- else:
627
- st.session_state.player2_analyses.append(move_detail)
628
-
629
- st.session_state.move_history_detailed.append(move_detail)
630
-
631
- if st.session_state.game.check_winner(current_player):
632
- st.session_state.game_over = True
633
- st.session_state.winner = f"Player {current_player}"
634
- st.session_state.show_winner_popup = True
635
- elif st.session_state.game.is_board_full():
636
- st.session_state.game_over = True
637
- st.session_state.winner = "Draw"
638
- st.session_state.show_winner_popup = True
639
- else:
640
- st.session_state.current_player = 2 if current_player == 1 else 1
641
-
642
- st.rerun()
643
-
644
- # Player Analysis
645
- st.markdown("---")
646
- st.markdown("## ๐Ÿ“Š Player Analysis")
647
-
648
- col1, col2 = st.columns(2)
649
 
650
  with col1:
651
- if st.session_state.player1_analyses:
652
- st.markdown('<div class="player-section player1-section">', unsafe_allow_html=True)
653
- st.markdown('<div class="section-header">๐Ÿ”ด Player 1</div>', unsafe_allow_html=True)
654
- latest_p1 = st.session_state.player1_analyses[-1]
655
-
656
- st.markdown(f'<div class="info-row"><strong>Last Move:</strong> Column {latest_p1["column"]}</div>', unsafe_allow_html=True)
657
-
658
- if latest_p1['recommendation']['is_optimal']:
659
- st.markdown('<span class="badge badge-success">โœ… Optimal Move!</span>', unsafe_allow_html=True)
660
- else:
661
- st.markdown(f'<span class="badge badge-warning">๐Ÿ’ก AI suggests Column {latest_p1["recommendation"]["suggested_column"]}</span>', unsafe_allow_html=True)
662
-
663
- if latest_p1['analysis']['threats']:
664
- st.markdown('<div class="info-row" style="margin-top: 12px;"><strong>Threats:</strong></div>', unsafe_allow_html=True)
665
- for threat in latest_p1['analysis']['threats']:
666
- st.markdown(f'<span class="badge badge-threat">{threat}</span>', unsafe_allow_html=True)
667
-
668
- st.markdown(f'<div class="info-row" style="margin-top: 12px;"><strong>Score:</strong> {latest_p1["analysis"]["score"]} points</div>', unsafe_allow_html=True)
669
-
670
- perf_col1, perf_col2 = st.columns(2)
671
- with perf_col1:
672
- st.markdown(f'<div class="stats-card"><span class="stat-number">{latest_p1["analysis"]["nodes_explored"]:,}</span><span class="stat-label">Nodes Explored</span></div>', unsafe_allow_html=True)
673
- with perf_col2:
674
- st.markdown(f'<div class="stats-card"><span class="stat-number">{latest_p1["analysis"]["pruning_efficiency"]:.1f}%</span><span class="stat-label">Efficiency</span></div>', unsafe_allow_html=True)
675
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
676
 
677
  with col2:
 
 
 
 
 
 
 
 
 
 
 
678
  if st.session_state.player2_analyses:
679
- st.markdown('<div class="player-section player2-section">', unsafe_allow_html=True)
680
- st.markdown('<div class="section-header">๐ŸŸก Player 2</div>', unsafe_allow_html=True)
681
- latest_p2 = st.session_state.player2_analyses[-1]
682
-
683
- st.markdown(f'<div class="info-row"><strong>Last Move:</strong> Column {latest_p2["column"]}</div>', unsafe_allow_html=True)
684
-
685
- if latest_p2['recommendation']['is_optimal']:
686
- st.markdown('<span class="badge badge-success">โœ… Optimal Move!</span>', unsafe_allow_html=True)
687
- else:
688
- st.markdown(f'<span class="badge badge-warning">๐Ÿ’ก AI suggests Column {latest_p2["recommendation"]["suggested_column"]}</span>', unsafe_allow_html=True)
689
-
690
- if latest_p2['analysis']['threats']:
691
- st.markdown('<div class="info-row" style="margin-top: 12px;"><strong>Threats:</strong></div>', unsafe_allow_html=True)
692
- for threat in latest_p2['analysis']['threats']:
693
- st.markdown(f'<span class="badge badge-threat">{threat}</span>', unsafe_allow_html=True)
694
-
695
- st.markdown(f'<div class="info-row" style="margin-top: 12px;"><strong>Score:</strong> {latest_p2["analysis"]["score"]} points</div>', unsafe_allow_html=True)
696
-
697
- perf_col1, perf_col2 = st.columns(2)
698
- with perf_col1:
699
- st.markdown(f'<div class="stats-card"><span class="stat-number">{latest_p2["analysis"]["nodes_explored"]:,}</span><span class="stat-label">Nodes Explored</span></div>', unsafe_allow_html=True)
700
- with perf_col2:
701
- st.markdown(f'<div class="stats-card"><span class="stat-number">{latest_p2["analysis"]["pruning_efficiency"]:.1f}%</span><span class="stat-label">Efficiency</span></div>', unsafe_allow_html=True)
702
  st.markdown('</div>', unsafe_allow_html=True)
 
 
 
703
 
704
  # Winner Display
705
- if st.session_state.show_winner_popup and st.session_state.game_over:
706
- if st.session_state.winner == "Human":
707
  st.balloons()
708
- st.markdown('<div class="winner-banner">๐ŸŽ‰ YOU WON! ๐ŸŽ‰</div>', unsafe_allow_html=True)
709
- elif st.session_state.winner == "AI":
710
- st.markdown('<div class="winner-banner ai">๐Ÿค– AI WINS!</div>', unsafe_allow_html=True)
711
- elif "Player" in st.session_state.winner:
712
  st.balloons()
713
- st.markdown(f'<div class="winner-banner">๐ŸŽ‰ {st.session_state.winner.upper()} WINS! ๐ŸŽ‰</div>', unsafe_allow_html=True)
 
 
714
  else:
715
- st.markdown('<div class="winner-banner draw">๐Ÿค IT\'S A DRAW!</div>', unsafe_allow_html=True)
716
-
717
- st.session_state.show_winner_popup = False
718
 
719
- # Download Report
720
- if st.session_state.game_over:
721
  st.markdown("---")
722
- st.markdown("## ๐Ÿ“„ Match Report")
723
 
724
  report = generate_match_report(
725
- st.session_state.game_mode,
726
- "Ultra AI",
727
- st.session_state.winner,
728
- st.session_state.move_history_detailed,
729
- st.session_state.game.board_to_string(),
730
  search_depth=st.session_state.current_depth if st.session_state.game_mode == "vs AI" else None
731
  )
732
 
733
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
734
- filename = f"connect_four_report_{timestamp}.txt"
735
-
736
  st.download_button(
737
- label="๐Ÿ“ฅ Download Full Match Report",
738
  data=report,
739
- file_name=filename,
740
  mime="text/plain",
741
- use_container_width=True,
742
- type="primary"
743
- )
 
6
  from llm_explanation import MoveExplainer
7
  from report_generator import generate_match_report
8
 
9
+ st.set_page_config(
10
+ page_title="Connect Four AI Pro",
11
+ layout="wide",
12
+ initial_sidebar_state="expanded"
13
+ )
14
 
15
+ # Modern Gaming UI Theme with Connect Four Colors
16
  st.markdown('''
17
  <style>
18
+ /* Import Google Fonts */
19
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Rajdhani:wght@300;400;500;600;700&display=swap');
20
 
21
+ /* Global Styles */
22
  * {
23
+ font-family: 'Rajdhani', sans-serif !important;
24
  }
25
 
26
+ /* Main Background with Gaming Gradient */
27
  .stApp {
28
+ background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
29
+ background-attachment: fixed;
30
  }
31
 
32
  /* Sidebar Styling */
33
  [data-testid="stSidebar"] {
34
+ background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
35
+ border-right: 2px solid rgba(255, 215, 0, 0.3);
36
  }
37
 
38
+ [data-testid="stSidebar"] h1,
39
+ [data-testid="stSidebar"] h2,
40
+ [data-testid="stSidebar"] h3 {
41
+ color: #FFD700 !important;
42
+ font-family: 'Orbitron', sans-serif !important;
43
+ text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
44
  }
45
 
46
+ [data-testid="stSidebar"] p,
47
+ [data-testid="stSidebar"] label {
48
+ color: #E0E0E0 !important;
49
+ font-weight: 500;
50
  }
51
 
52
+ /* Main Title */
 
 
 
 
 
 
 
53
  h1 {
54
+ font-family: 'Orbitron', sans-serif !important;
55
+ font-weight: 900 !important;
56
+ font-size: 3rem !important;
57
+ background: linear-gradient(90deg, #FFD700, #FFA500, #FF6347);
58
+ -webkit-background-clip: text;
59
+ -webkit-text-fill-color: transparent;
60
+ text-align: center;
61
+ text-shadow: 0 0 30px rgba(255, 215, 0, 0.5);
62
+ margin-bottom: 1rem !important;
63
+ animation: glow 2s ease-in-out infinite alternate;
64
  }
65
 
66
+ @keyframes glow {
67
+ from {
68
+ filter: drop-shadow(0 0 5px #FFD700);
69
+ }
70
+ to {
71
+ filter: drop-shadow(0 0 20px #FFD700);
72
+ }
73
  }
74
 
75
+ /* Subheaders */
76
+ h2, h3 {
77
+ font-family: 'Orbitron', sans-serif !important;
78
+ color: #FFD700 !important;
79
+ font-weight: 700 !important;
80
+ text-shadow: 0 0 10px rgba(255, 215, 0, 0.3);
81
+ border-bottom: 2px solid rgba(255, 215, 0, 0.3);
82
+ padding-bottom: 0.5rem;
83
+ margin-top: 1.5rem !important;
84
  }
85
 
86
+ /* Game Board Container */
87
  .board-container {
88
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
89
+ border-radius: 20px;
90
+ padding: 2rem;
91
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5),
92
+ inset 0 0 30px rgba(255, 215, 0, 0.1);
93
+ border: 3px solid rgba(255, 215, 0, 0.3);
94
+ margin: 1rem 0;
95
+ }
96
+
97
+ /* Board Display */
98
+ pre {
99
+ background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%) !important;
100
+ border-radius: 15px !important;
101
+ padding: 2rem !important;
102
+ font-size: 2rem !important;
103
+ line-height: 2.5rem !important;
104
+ border: 3px solid rgba(255, 215, 0, 0.4) !important;
105
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6),
106
+ inset 0 0 20px rgba(255, 215, 0, 0.05) !important;
107
+ font-family: 'Courier New', monospace !important;
 
 
 
 
 
 
 
108
  text-align: center;
109
+ overflow-x: auto;
110
  }
111
 
112
+ /* Buttons - Gaming Style */
113
+ .stButton button {
114
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
115
+ color: white !important;
116
+ font-family: 'Orbitron', sans-serif !important;
117
+ font-weight: 700 !important;
118
+ font-size: 1.1rem !important;
119
+ border: 2px solid rgba(255, 215, 0, 0.5) !important;
120
+ border-radius: 12px !important;
121
+ padding: 0.75rem 1.5rem !important;
122
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4) !important;
123
+ transition: all 0.3s ease !important;
124
  text-transform: uppercase;
125
+ letter-spacing: 1px;
 
 
 
 
 
 
 
 
 
126
  }
127
 
128
+ .stButton button:hover {
129
+ background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important;
130
+ transform: translateY(-3px);
131
+ box-shadow: 0 12px 30px rgba(102, 126, 234, 0.6) !important;
132
+ border-color: #FFD700 !important;
133
  }
134
 
135
+ .stButton button:disabled {
136
+ background: linear-gradient(135deg, #4a4a4a 0%, #2d2d2d 100%) !important;
137
+ opacity: 0.5 !important;
138
+ cursor: not-allowed !important;
139
+ box-shadow: none !important;
140
  }
141
 
142
+ /* Column Buttons - Special Styling */
143
+ div[data-testid="column"] .stButton button {
144
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%) !important;
145
+ width: 100%;
146
+ height: 60px;
147
+ font-size: 1.3rem !important;
148
+ margin: 0.2rem 0;
149
  }
150
 
151
+ div[data-testid="column"] .stButton button:hover {
152
+ background: linear-gradient(135deg, #fa709a 0%, #fee140 100%) !important;
153
+ transform: scale(1.05);
154
  }
155
 
156
+ /* Info Cards */
157
+ .info-card {
158
+ background: linear-gradient(135deg, #232526 0%, #414345 100%);
159
+ border-radius: 15px;
160
+ padding: 1.5rem;
161
+ margin: 1rem 0;
162
+ border: 2px solid rgba(255, 215, 0, 0.3);
163
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
164
  }
165
 
166
+ /* Stats Display */
167
+ .metric-container {
168
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
169
+ border-radius: 12px;
170
+ padding: 1rem;
171
+ margin: 0.5rem 0;
172
+ border-left: 4px solid #FFD700;
173
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
 
 
174
  }
175
 
176
+ /* AI Badge */
177
+ .ai-badge {
178
  display: inline-block;
179
+ background: linear-gradient(135deg, #FF6347, #FF4500);
 
 
 
 
 
 
 
 
180
  color: white;
181
+ padding: 0.5rem 1.5rem;
182
+ border-radius: 25px;
183
+ font-family: 'Orbitron', sans-serif;
184
+ font-weight: 700;
185
+ font-size: 1.2rem;
186
+ border: 2px solid rgba(255, 215, 0, 0.5);
187
+ box-shadow: 0 5px 15px rgba(255, 99, 71, 0.4);
188
+ text-transform: uppercase;
189
+ letter-spacing: 2px;
190
+ animation: pulse 2s infinite;
191
  }
192
 
193
+ @keyframes pulse {
194
+ 0%, 100% {
195
+ transform: scale(1);
196
+ }
197
+ 50% {
198
+ transform: scale(1.05);
199
+ }
200
  }
201
 
202
+ /* Two Player Badge */
203
+ .two-player-badge {
204
+ display: inline-block;
205
+ background: linear-gradient(135deg, #4facfe, #00f2fe);
206
  color: white;
207
+ padding: 0.5rem 1.5rem;
208
+ border-radius: 25px;
209
+ font-family: 'Orbitron', sans-serif;
210
+ font-weight: 700;
211
+ font-size: 1.2rem;
212
+ border: 2px solid rgba(255, 215, 0, 0.5);
213
+ box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);
214
+ text-transform: uppercase;
215
+ letter-spacing: 2px;
216
  }
217
 
218
+ /* Winner Popup */
219
+ .winner-popup {
220
+ position: fixed;
221
+ top: 50%;
222
+ left: 50%;
223
+ transform: translate(-50%, -50%);
224
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
225
+ padding: 3rem;
226
+ border-radius: 20px;
227
+ border: 4px solid #FFD700;
228
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8);
229
+ z-index: 9999;
 
230
  text-align: center;
231
+ animation: popIn 0.5s ease-out;
232
  }
233
 
234
+ @keyframes popIn {
235
+ 0% {
236
+ transform: translate(-50%, -50%) scale(0.5);
237
+ opacity: 0;
238
+ }
239
+ 100% {
240
+ transform: translate(-50%, -50%) scale(1);
241
+ opacity: 1;
242
+ }
243
  }
244
 
245
+ /* Expander Styling */
246
+ .streamlit-expanderHeader {
247
+ background: linear-gradient(135deg, #232526 0%, #414345 100%) !important;
248
+ border-radius: 10px !important;
249
+ border: 1px solid rgba(255, 215, 0, 0.3) !important;
250
+ color: #FFD700 !important;
251
+ font-weight: 600 !important;
 
 
 
 
 
 
 
 
 
252
  }
253
 
254
+ /* Radio Buttons */
255
+ .stRadio label {
256
+ color: #E0E0E0 !important;
257
+ font-weight: 600 !important;
258
+ font-size: 1.1rem !important;
259
  }
260
 
261
+ /* Slider */
262
+ .stSlider {
263
+ padding: 1rem 0;
264
  }
265
 
266
+ /* Text & Paragraphs */
267
+ p, li, span {
268
+ color: #E0E0E0 !important;
269
+ font-size: 1.05rem !important;
270
+ line-height: 1.6 !important;
 
 
 
 
271
  }
272
 
273
+ /* Strong/Bold Text */
274
+ strong {
275
+ color: #FFD700 !important;
276
+ font-weight: 700 !important;
277
  }
278
 
279
+ /* Code blocks */
280
+ code {
281
+ background: rgba(255, 215, 0, 0.1) !important;
282
+ color: #FFD700 !important;
283
+ padding: 0.2rem 0.5rem !important;
284
+ border-radius: 5px !important;
285
  }
286
 
287
+ /* Horizontal Rules */
288
+ hr {
289
+ border-color: rgba(255, 215, 0, 0.3) !important;
290
+ margin: 2rem 0 !important;
291
  }
292
 
293
+ /* Success/Info/Warning/Error Messages */
294
+ .stSuccess, .stInfo, .stWarning, .stError {
295
+ border-radius: 10px !important;
296
+ border-left: 5px solid #FFD700 !important;
 
 
297
  }
298
 
299
+ /* Scrollbar */
300
+ ::-webkit-scrollbar {
301
+ width: 12px;
302
+ height: 12px;
 
303
  }
304
 
305
+ ::-webkit-scrollbar-track {
306
+ background: #1a1a2e;
 
307
  }
308
 
309
+ ::-webkit-scrollbar-thumb {
310
+ background: linear-gradient(135deg, #667eea, #764ba2);
311
+ border-radius: 6px;
 
 
 
312
  }
313
 
314
+ ::-webkit-scrollbar-thumb:hover {
315
+ background: linear-gradient(135deg, #764ba2, #667eea);
 
316
  }
317
 
318
+ /* Download Button */
319
+ .stDownloadButton button {
320
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%) !important;
321
+ color: white !important;
322
+ font-family: 'Orbitron', sans-serif !important;
323
+ font-weight: 700 !important;
324
+ border: 2px solid rgba(255, 215, 0, 0.5) !important;
325
+ border-radius: 12px !important;
326
+ padding: 0.75rem 1.5rem !important;
327
+ box-shadow: 0 8px 20px rgba(17, 153, 142, 0.4) !important;
328
+ transition: all 0.3s ease !important;
329
  }
330
 
331
+ .stDownloadButton button:hover {
332
+ background: linear-gradient(135deg, #38ef7d 0%, #11998e 100%) !important;
333
+ transform: translateY(-3px);
334
+ box-shadow: 0 12px 30px rgba(17, 153, 142, 0.6) !important;
335
  }
336
 
337
+ /* Analysis Cards */
338
+ .analysis-card {
339
+ background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
340
+ border-radius: 15px;
341
+ padding: 1.5rem;
342
+ margin: 1rem 0;
343
+ border-left: 5px solid #FFD700;
344
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
345
  }
346
 
347
+ .analysis-card h4 {
348
+ color: #FFD700 !important;
349
+ font-family: 'Orbitron', sans-serif !important;
350
+ margin-bottom: 1rem !important;
 
 
 
 
 
351
  }
352
  </style>
353
  ''', unsafe_allow_html=True)
354
 
355
+ # Initialize Session State
356
  if 'game' not in st.session_state:
357
  st.session_state.game = ConnectFour()
358
  st.session_state.game_mode = "vs AI"
359
  st.session_state.ai = MinimaxAI(st.session_state.game, depth=5)
 
360
  hf_token = os.getenv('HF_TOKEN')
361
  st.session_state.explainer = MoveExplainer(hf_token=hf_token) if hf_token else None
 
362
  st.session_state.move_history = []
363
  st.session_state.move_history_detailed = []
364
  st.session_state.player1_analyses = []
 
373
  st.session_state.last_ai_move_analysis = None
374
 
375
  # Header
376
+ col_title1, col_title2 = st.columns([3, 1])
377
  with col_title1:
378
+ st.title("๐ŸŽฎ CONNECT FOUR AI PRO")
379
+
380
  with col_title2:
381
  if st.session_state.game_mode == "vs AI":
382
+ st.markdown(f'<div class="ai-badge">โšก ULTRA AI</div>', unsafe_allow_html=True)
383
  else:
384
+ st.markdown('<div class="two-player-badge">๐Ÿ‘ฅ TWO PLAYER</div>', unsafe_allow_html=True)
385
 
386
+ # Sidebar Configuration
387
+ st.sidebar.header("โš™๏ธ GAME SETTINGS")
388
 
389
+ game_mode = st.sidebar.radio(
390
+ "๐ŸŽฎ Game Mode",
391
+ ["vs AI", "Two Player"],
392
+ index=0 if st.session_state.game_mode == "vs AI" else 1
393
+ )
394
 
395
  if game_mode != st.session_state.game_mode:
396
  st.session_state.game_mode = game_mode
 
406
 
407
  if st.session_state.game_mode == "vs AI":
408
  st.sidebar.markdown("---")
409
+ st.sidebar.subheader("๐ŸŽš๏ธ AI Search Depth")
410
+ depth = st.sidebar.slider("Depth Level (3-8)", 3, 8, 5)
411
  st.session_state.ai.depth = depth
412
  st.session_state.current_depth = depth
413
 
414
  st.sidebar.markdown("---")
415
 
416
+ # Control Buttons
417
  col1, col2 = st.sidebar.columns(2)
418
  with col1:
419
  if st.button("๐Ÿ”„ New Game", use_container_width=True):
 
449
  st.session_state.show_winner_popup = False
450
  st.rerun()
451
 
452
+ # Game Statistics
453
  st.sidebar.markdown("---")
454
+ st.sidebar.subheader("๐Ÿ“Š GAME STATISTICS")
455
+ st.sidebar.markdown(f'<div class="metric-container"><strong>Total Moves:</strong> {st.session_state.move_count}</div>', unsafe_allow_html=True)
456
+
457
  if st.session_state.game_mode == "Two Player":
458
+ st.sidebar.markdown(f'<div class="metric-container"><strong>Player 1 (๐Ÿ”ด):</strong> {len(st.session_state.player1_analyses)} moves</div>', unsafe_allow_html=True)
459
+ st.sidebar.markdown(f'<div class="metric-container"><strong>Player 2 (๐ŸŸก):</strong> {len(st.session_state.player2_analyses)} moves</div>', unsafe_allow_html=True)
 
 
 
460
 
461
+ # Move History
462
  if st.session_state.move_history:
463
+ with st.sidebar.expander("๐Ÿ“œ MOVE HISTORY"):
464
  for i, col in enumerate(st.session_state.move_history, 1):
465
  player = "๐Ÿ”ด" if i % 2 == 1 else "๐ŸŸก"
466
+ st.write(f"**Move {i}:** {player} โ†’ Column {col}")
467
 
468
+ # Main Game Layout
469
  if st.session_state.game_mode == "vs AI":
470
  col1, col2 = st.columns([2, 3])
471
 
472
  with col1:
473
+ st.subheader("๐ŸŽฏ GAME BOARD")
 
474
  board_str = st.session_state.game.board_to_string()
475
+ st.markdown(f'<div class="board-container"><pre>{board_str}</pre></div>', unsafe_allow_html=True)
 
476
 
477
+ # Column Selection
478
+ st.markdown("### ๐ŸŽฒ SELECT YOUR MOVE")
479
+ cols = st.columns(7)
480
+ for i, col in enumerate(cols):
481
+ with col:
482
+ if st.button(f"โ†“\n{i}", key=f"col_{i}",
483
+ disabled=st.session_state.game_over or not st.session_state.game.is_valid_move(i)):
484
+ # Human Move
485
+ st.session_state.game.make_move(i, 1)
486
+ st.session_state.move_history.append(i)
487
  st.session_state.move_count += 1
488
 
489
+ # Get AI recommendation
490
+ ai_move = st.session_state.ai.get_best_move()
491
+ is_optimal = (i == ai_move['move'])
492
+ recommendation = {
493
+ 'is_optimal': is_optimal,
494
+ 'suggested_column': ai_move['move'],
495
+ 'score_diff': abs(ai_move['score'] - st.session_state.game.evaluate(2))
496
+ }
497
+
498
+ # Generate explanation
499
+ explanation_data = None
500
+ if st.session_state.explainer:
501
+ move_data = {
502
+ 'move': i,
503
+ 'score': st.session_state.game.evaluate(2),
504
+ 'threats': ai_move.get('threats', []),
505
+ 'nodes_explored': ai_move.get('nodes_explored', 0)
506
+ }
507
+ explanation_result = st.session_state.explainer.explain_move(move_data)
508
+ if explanation_result['success']:
509
+ explanation_data = explanation_result['explanation']
510
+ st.session_state.last_human_move_analysis = explanation_result
511
 
512
  move_detail = {
513
+ 'player': 1,
514
+ 'player_name': 'Human',
515
+ 'column': i,
516
+ 'analysis': ai_move,
517
+ 'explanation': explanation_data,
518
+ 'recommendation': recommendation
519
  }
520
  st.session_state.move_history_detailed.append(move_detail)
521
 
522
+ # Check win
523
+ if st.session_state.game.check_winner(1):
524
  st.session_state.game_over = True
525
  st.session_state.winner = "Human"
526
  st.session_state.show_winner_popup = True
527
+ st.rerun()
528
+
529
+ if st.session_state.game.is_board_full():
530
  st.session_state.game_over = True
531
  st.session_state.winner = "Draw"
532
  st.session_state.show_winner_popup = True
533
+ st.rerun()
534
+
535
+ # AI Move
536
+ if not st.session_state.game_over:
537
+ ai_move = st.session_state.ai.get_best_move()
538
+ st.session_state.game.make_move(ai_move['move'], 2)
539
+ st.session_state.move_history.append(ai_move['move'])
540
  st.session_state.move_count += 1
541
 
542
+ # AI explanation
543
+ ai_explanation_data = None
544
  if st.session_state.explainer:
545
+ ai_move_data = {
546
+ 'move': ai_move['move'],
547
+ 'score': ai_move['score'],
548
+ 'threats': ai_move.get('threats', []),
549
+ 'nodes_explored': ai_move.get('nodes_explored', 0)
 
 
 
550
  }
551
+ ai_explanation_result = st.session_state.explainer.explain_move(ai_move_data)
552
+ if ai_explanation_result['success']:
553
+ ai_explanation_data = ai_explanation_result['explanation']
554
+ st.session_state.last_ai_move_analysis = ai_explanation_result
555
 
556
  ai_move_detail = {
557
  'player': 2,
558
+ 'player_name': 'AI (Ultra)',
559
+ 'column': ai_move['move'],
560
+ 'analysis': ai_move,
561
+ 'explanation': ai_explanation_data
562
  }
563
  st.session_state.move_history_detailed.append(ai_move_detail)
 
 
564
 
565
+ # Check AI win
566
  if st.session_state.game.check_winner(2):
567
  st.session_state.game_over = True
568
  st.session_state.winner = "AI"
569
  st.session_state.show_winner_popup = True
570
+ st.rerun()
571
+
572
+ if st.session_state.game.is_board_full():
573
  st.session_state.game_over = True
574
  st.session_state.winner = "Draw"
575
  st.session_state.show_winner_popup = True
576
+ st.rerun()
577
 
578
  st.rerun()
579
 
580
  with col2:
581
+ st.subheader("๐Ÿค– AI ANALYSIS & INSIGHTS")
582
 
 
583
  if st.session_state.last_human_move_analysis:
584
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
585
+ st.markdown("#### ๐Ÿ‘ค YOUR LAST MOVE ANALYSIS")
586
+ analysis = st.session_state.last_human_move_analysis
587
+ st.info(f"**Explanation:** {analysis['explanation']}")
588
+ st.success(f"**Threat Analysis:** {analysis['threat_analysis']}")
589
+ st.warning(f"**Key Insight:** {analysis['key_insight']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  st.markdown('</div>', unsafe_allow_html=True)
591
 
592
+ if st.session_state.last_ai_move_analysis:
593
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
594
+ st.markdown("#### ๐Ÿค– AI'S LAST MOVE ANALYSIS")
595
+ analysis = st.session_state.last_ai_move_analysis
596
+ st.info(f"**Explanation:** {analysis['explanation']}")
597
+ st.success(f"**Threat Analysis:** {analysis['threat_analysis']}")
598
+ st.warning(f"**Key Insight:** {analysis['key_insight']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
  st.markdown('</div>', unsafe_allow_html=True)
600
+
601
+ if not st.session_state.last_human_move_analysis and not st.session_state.last_ai_move_analysis:
602
+ st.info("๐ŸŽฒ Make your first move to see AI analysis!")
603
 
604
  else: # Two Player Mode
605
+ col1, col2 = st.columns([2, 3])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
 
607
  with col1:
608
+ st.subheader("๐ŸŽฏ GAME BOARD")
609
+ board_str = st.session_state.game.board_to_string()
610
+ st.markdown(f'<div class="board-container"><pre>{board_str}</pre></div>', unsafe_allow_html=True)
611
+
612
+ # Current Player Indicator
613
+ current_player_symbol = "๐Ÿ”ด RED" if st.session_state.current_player == 1 else "๐ŸŸก YELLOW"
614
+ st.markdown(f'<div class="info-card"><h3 style="text-align: center;">Current Turn: {current_player_symbol}</h3></div>', unsafe_allow_html=True)
615
+
616
+ # Column Selection
617
+ st.markdown("### ๐ŸŽฒ SELECT COLUMN")
618
+ cols = st.columns(7)
619
+ for i, col in enumerate(cols):
620
+ with col:
621
+ if st.button(f"โ†“\n{i}", key=f"col_{i}",
622
+ disabled=st.session_state.game_over or not st.session_state.game.is_valid_move(i)):
623
+ # Make move
624
+ st.session_state.game.make_move(i, st.session_state.current_player)
625
+ st.session_state.move_history.append(i)
626
+ st.session_state.move_count += 1
627
+
628
+ # Get AI analysis
629
+ ai_move = st.session_state.ai.get_best_move()
630
+ is_optimal = (i == ai_move['move'])
631
+ recommendation = {
632
+ 'is_optimal': is_optimal,
633
+ 'suggested_column': ai_move['move'],
634
+ 'score_diff': abs(ai_move['score'] - st.session_state.game.evaluate(2))
635
+ }
636
+
637
+ # Generate explanation
638
+ explanation_data = None
639
+ if st.session_state.explainer:
640
+ move_data = {
641
+ 'move': i,
642
+ 'score': st.session_state.game.evaluate(2),
643
+ 'threats': ai_move.get('threats', []),
644
+ 'nodes_explored': ai_move.get('nodes_explored', 0)
645
+ }
646
+ explanation_result = st.session_state.explainer.explain_move(move_data)
647
+ if explanation_result['success']:
648
+ explanation_data = explanation_result['explanation']
649
+ if st.session_state.current_player == 1:
650
+ st.session_state.player1_analyses.append(explanation_result)
651
+ else:
652
+ st.session_state.player2_analyses.append(explanation_result)
653
+
654
+ player_name = f"Player {st.session_state.current_player}"
655
+ move_detail = {
656
+ 'player': st.session_state.current_player,
657
+ 'player_name': player_name,
658
+ 'column': i,
659
+ 'analysis': ai_move,
660
+ 'explanation': explanation_data,
661
+ 'recommendation': recommendation
662
+ }
663
+ st.session_state.move_history_detailed.append(move_detail)
664
+
665
+ # Check win
666
+ if st.session_state.game.check_winner(st.session_state.current_player):
667
+ st.session_state.game_over = True
668
+ st.session_state.winner = f"Player {st.session_state.current_player}"
669
+ st.session_state.show_winner_popup = True
670
+ st.rerun()
671
+
672
+ if st.session_state.game.is_board_full():
673
+ st.session_state.game_over = True
674
+ st.session_state.winner = "Draw"
675
+ st.session_state.show_winner_popup = True
676
+ st.rerun()
677
+
678
+ # Switch player
679
+ st.session_state.current_player = 2 if st.session_state.current_player == 1 else 1
680
+ st.rerun()
681
 
682
  with col2:
683
+ st.subheader("๐Ÿ“Š PLAYER ANALYSIS")
684
+
685
+ if st.session_state.player1_analyses:
686
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
687
+ st.markdown("#### ๐Ÿ”ด PLAYER 1 - LAST MOVE")
688
+ analysis = st.session_state.player1_analyses[-1]
689
+ st.info(f"**Explanation:** {analysis['explanation']}")
690
+ st.success(f"**Threat Analysis:** {analysis['threat_analysis']}")
691
+ st.warning(f"**Key Insight:** {analysis['key_insight']}")
692
+ st.markdown('</div>', unsafe_allow_html=True)
693
+
694
  if st.session_state.player2_analyses:
695
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
696
+ st.markdown("#### ๐ŸŸก PLAYER 2 - LAST MOVE")
697
+ analysis = st.session_state.player2_analyses[-1]
698
+ st.info(f"**Explanation:** {analysis['explanation']}")
699
+ st.success(f"**Threat Analysis:** {analysis['threat_analysis']}")
700
+ st.warning(f"**Key Insight:** {analysis['key_insight']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  st.markdown('</div>', unsafe_allow_html=True)
702
+
703
+ if not st.session_state.player1_analyses and not st.session_state.player2_analyses:
704
+ st.info("๐ŸŽฒ Start playing to see move analysis!")
705
 
706
  # Winner Display
707
+ if st.session_state.show_winner_popup:
708
+ if st.session_state.winner == "Draw":
709
  st.balloons()
710
+ st.success("๐Ÿค **GAME OVER - IT'S A DRAW!**")
711
+ elif st.session_state.winner == "Human":
 
 
712
  st.balloons()
713
+ st.success("๐Ÿ† **CONGRATULATIONS! YOU WON!**")
714
+ elif st.session_state.winner == "AI":
715
+ st.error("๐Ÿค– **AI WINS! Better luck next time!**")
716
  else:
717
+ st.balloons()
718
+ st.success(f"๐Ÿ† **{st.session_state.winner.upper()} WINS!**")
 
719
 
720
+ # Match Report Download
721
+ if st.session_state.game_over and st.session_state.move_history_detailed:
722
  st.markdown("---")
723
+ st.subheader("๐Ÿ“„ MATCH REPORT")
724
 
725
  report = generate_match_report(
726
+ game_mode=st.session_state.game_mode,
727
+ ai_mode="Ultra AI" if st.session_state.game_mode == "vs AI" else "N/A",
728
+ winner=st.session_state.winner,
729
+ move_history_data=st.session_state.move_history_detailed,
730
+ game_board=st.session_state.game.board_to_string(),
731
  search_depth=st.session_state.current_depth if st.session_state.game_mode == "vs AI" else None
732
  )
733
 
734
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
 
 
735
  st.download_button(
736
+ label="๐Ÿ“ฅ DOWNLOAD MATCH REPORT",
737
  data=report,
738
+ file_name=f"connect_four_match_report_{timestamp}.txt",
739
  mime="text/plain",
740
+ use_container_width=True
741
+ )
 
connect_four_game.py CHANGED
@@ -3,21 +3,21 @@ from typing import Tuple, List, Dict
3
 
4
  class ConnectFour:
5
  '''Connect Four game engine with clean board display'''
6
-
7
  def __init__(self, rows=6, cols=7):
8
  self.rows = rows
9
  self.cols = cols
10
  self.board = np.zeros((rows, cols), dtype=int)
11
  self.move_count = 0
12
-
13
  def is_valid_move(self, col: int) -> bool:
14
  if col < 0 or col >= self.cols:
15
  return False
16
  return self.board[0][col] == 0
17
-
18
  def get_valid_moves(self) -> List[int]:
19
  return [col for col in range(self.cols) if self.is_valid_move(col)]
20
-
21
  def make_move(self, col: int, player: int) -> bool:
22
  for row in range(self.rows - 1, -1, -1):
23
  if self.board[row][col] == 0:
@@ -25,49 +25,49 @@ class ConnectFour:
25
  self.move_count += 1
26
  return True
27
  return False
28
-
29
  def undo_move(self, col: int):
30
  for row in range(self.rows):
31
  if self.board[row][col] != 0:
32
  self.board[row][col] = 0
33
  self.move_count -= 1
34
  return
35
-
36
  def check_winner(self, player: int) -> bool:
37
  # Horizontal
38
  for row in range(self.rows):
39
  for col in range(self.cols - 3):
40
  if all(self.board[row][col + i] == player for i in range(4)):
41
  return True
42
-
43
  # Vertical
44
  for row in range(self.rows - 3):
45
  for col in range(self.cols):
46
  if all(self.board[row + i][col] == player for i in range(4)):
47
  return True
48
-
49
  # Diagonal
50
  for row in range(self.rows - 3):
51
  for col in range(self.cols - 3):
52
  if all(self.board[row + i][col + i] == player for i in range(4)):
53
  return True
54
-
55
  for row in range(3, self.rows):
56
  for col in range(self.cols - 3):
57
  if all(self.board[row - i][col + i] == player for i in range(4)):
58
  return True
59
-
60
  return False
61
-
62
  def is_board_full(self) -> bool:
63
  return len(self.get_valid_moves()) == 0
64
-
65
  def get_board_copy(self):
66
  return self.board.copy()
67
-
68
  def count_threats(self, player: int) -> Dict[str, int]:
69
  threats = {'threes': 0, 'twos': 0, 'center': 0}
70
-
71
  for row in range(self.rows):
72
  for col in range(self.cols):
73
  if self.board[row][col] == player:
@@ -79,53 +79,53 @@ class ConnectFour:
79
  threats['threes'] += 1
80
  if row - 3 >= 0 and col + 3 < self.cols and all(self.board[row - i][col + i] == player for i in range(1, 4)):
81
  threats['threes'] += 1
82
-
83
  for row in range(self.rows):
84
  for col in [2, 3, 4]:
85
  if self.board[row][col] == player:
86
  threats['center'] += 1
87
-
88
  return threats
89
-
90
  def evaluate(self, ai_player=2) -> int:
91
  human = 1
92
-
93
  if self.check_winner(ai_player):
94
  return 10000
95
  if self.check_winner(human):
96
  return -10000
97
  if self.is_board_full():
98
  return 0
99
-
100
  score = 0
101
  ai_threats = self.count_threats(ai_player)
102
  human_threats = self.count_threats(human)
103
-
104
  score += ai_threats['threes'] * 1200
105
  score -= human_threats['threes'] * 1100
106
  score += ai_threats['twos'] * 60
107
  score -= human_threats['twos'] * 50
108
  score += ai_threats['center'] * 4
109
  score -= human_threats['center'] * 3
110
-
111
  return score
112
-
113
  def board_to_string(self) -> str:
114
  '''Clean board display without dotted lines'''
115
  mapping = {0: 'โšช', 1: '๐Ÿ”ด', 2: '๐ŸŸก'}
116
-
117
  # Column header with proper spacing
118
- result = " 0 1 2 3 4 5 6\n"
119
  result += "\n"
120
-
121
  for row in range(self.rows):
122
- result += f" {row} โ”‚ "
123
  for col in range(self.cols):
124
- result += mapping[self.board[row][col]] + " "
125
  result += "โ”‚\n"
126
-
127
  return result
128
-
129
  def reset(self):
130
  self.board = np.zeros((self.rows, self.cols), dtype=int)
131
  self.move_count = 0
 
3
 
4
  class ConnectFour:
5
  '''Connect Four game engine with clean board display'''
6
+
7
  def __init__(self, rows=6, cols=7):
8
  self.rows = rows
9
  self.cols = cols
10
  self.board = np.zeros((rows, cols), dtype=int)
11
  self.move_count = 0
12
+
13
  def is_valid_move(self, col: int) -> bool:
14
  if col < 0 or col >= self.cols:
15
  return False
16
  return self.board[0][col] == 0
17
+
18
  def get_valid_moves(self) -> List[int]:
19
  return [col for col in range(self.cols) if self.is_valid_move(col)]
20
+
21
  def make_move(self, col: int, player: int) -> bool:
22
  for row in range(self.rows - 1, -1, -1):
23
  if self.board[row][col] == 0:
 
25
  self.move_count += 1
26
  return True
27
  return False
28
+
29
  def undo_move(self, col: int):
30
  for row in range(self.rows):
31
  if self.board[row][col] != 0:
32
  self.board[row][col] = 0
33
  self.move_count -= 1
34
  return
35
+
36
  def check_winner(self, player: int) -> bool:
37
  # Horizontal
38
  for row in range(self.rows):
39
  for col in range(self.cols - 3):
40
  if all(self.board[row][col + i] == player for i in range(4)):
41
  return True
42
+
43
  # Vertical
44
  for row in range(self.rows - 3):
45
  for col in range(self.cols):
46
  if all(self.board[row + i][col] == player for i in range(4)):
47
  return True
48
+
49
  # Diagonal
50
  for row in range(self.rows - 3):
51
  for col in range(self.cols - 3):
52
  if all(self.board[row + i][col + i] == player for i in range(4)):
53
  return True
54
+
55
  for row in range(3, self.rows):
56
  for col in range(self.cols - 3):
57
  if all(self.board[row - i][col + i] == player for i in range(4)):
58
  return True
59
+
60
  return False
61
+
62
  def is_board_full(self) -> bool:
63
  return len(self.get_valid_moves()) == 0
64
+
65
  def get_board_copy(self):
66
  return self.board.copy()
67
+
68
  def count_threats(self, player: int) -> Dict[str, int]:
69
  threats = {'threes': 0, 'twos': 0, 'center': 0}
70
+
71
  for row in range(self.rows):
72
  for col in range(self.cols):
73
  if self.board[row][col] == player:
 
79
  threats['threes'] += 1
80
  if row - 3 >= 0 and col + 3 < self.cols and all(self.board[row - i][col + i] == player for i in range(1, 4)):
81
  threats['threes'] += 1
82
+
83
  for row in range(self.rows):
84
  for col in [2, 3, 4]:
85
  if self.board[row][col] == player:
86
  threats['center'] += 1
87
+
88
  return threats
89
+
90
  def evaluate(self, ai_player=2) -> int:
91
  human = 1
92
+
93
  if self.check_winner(ai_player):
94
  return 10000
95
  if self.check_winner(human):
96
  return -10000
97
  if self.is_board_full():
98
  return 0
99
+
100
  score = 0
101
  ai_threats = self.count_threats(ai_player)
102
  human_threats = self.count_threats(human)
103
+
104
  score += ai_threats['threes'] * 1200
105
  score -= human_threats['threes'] * 1100
106
  score += ai_threats['twos'] * 60
107
  score -= human_threats['twos'] * 50
108
  score += ai_threats['center'] * 4
109
  score -= human_threats['center'] * 3
110
+
111
  return score
112
+
113
  def board_to_string(self) -> str:
114
  '''Clean board display without dotted lines'''
115
  mapping = {0: 'โšช', 1: '๐Ÿ”ด', 2: '๐ŸŸก'}
116
+
117
  # Column header with proper spacing
118
+ result = " 0 1 2 3 4 5 6\n"
119
  result += "\n"
120
+
121
  for row in range(self.rows):
122
+ result += f" {row} โ”‚ "
123
  for col in range(self.cols):
124
+ result += mapping[self.board[row][col]] + " "
125
  result += "โ”‚\n"
126
+
127
  return result
128
+
129
  def reset(self):
130
  self.board = np.zeros((self.rows, self.cols), dtype=int)
131
  self.move_count = 0
minimax_ai.py CHANGED
@@ -33,10 +33,9 @@ class MinimaxAI:
33
  except:
34
  return None
35
 
36
- def minimax(self, depth: int, alpha: int, beta: int,
37
  is_maximizing: bool, current_depth=0) -> int:
38
  '''Minimax with Alpha-Beta Pruning (Fixed)'''
39
-
40
  self.max_depth_reached = max(self.max_depth_reached, current_depth)
41
  self.nodes_explored += 1
42
 
@@ -64,7 +63,6 @@ class MinimaxAI:
64
  return self.game.evaluate(self.ai_player)
65
 
66
  valid_moves = self.game.get_valid_moves()
67
-
68
  if not valid_moves:
69
  return self.game.evaluate(self.ai_player)
70
 
@@ -90,7 +88,6 @@ class MinimaxAI:
90
  if board_key:
91
  self.transposition_table[board_key] = (max_eval, depth)
92
  return max_eval
93
-
94
  else:
95
  min_eval = float('inf')
96
  for i, col in enumerate(sorted_moves):
@@ -113,7 +110,6 @@ class MinimaxAI:
113
 
114
  def get_best_move(self) -> Dict:
115
  '''Find best move (Fixed Pruning Counter)'''
116
-
117
  self.reset_stats()
118
  start_time = time.time()
119
 
@@ -144,7 +140,6 @@ class MinimaxAI:
144
 
145
  # Minimax search
146
  score = self.minimax(self.depth - 1, float('-inf'), float('inf'), False)
147
-
148
  self.game.undo_move(col)
149
 
150
  if score > best_score:
@@ -152,9 +147,8 @@ class MinimaxAI:
152
  best_move = col
153
 
154
  elapsed_time = (time.time() - start_time) * 1000
155
-
156
  pruning_efficiency = (self.nodes_pruned / (self.nodes_explored + self.nodes_pruned) * 100) \
157
- if (self.nodes_explored + self.nodes_pruned) > 0 else 0
158
 
159
  return {
160
  'move': best_move,
 
33
  except:
34
  return None
35
 
36
+ def minimax(self, depth: int, alpha: int, beta: int,
37
  is_maximizing: bool, current_depth=0) -> int:
38
  '''Minimax with Alpha-Beta Pruning (Fixed)'''
 
39
  self.max_depth_reached = max(self.max_depth_reached, current_depth)
40
  self.nodes_explored += 1
41
 
 
63
  return self.game.evaluate(self.ai_player)
64
 
65
  valid_moves = self.game.get_valid_moves()
 
66
  if not valid_moves:
67
  return self.game.evaluate(self.ai_player)
68
 
 
88
  if board_key:
89
  self.transposition_table[board_key] = (max_eval, depth)
90
  return max_eval
 
91
  else:
92
  min_eval = float('inf')
93
  for i, col in enumerate(sorted_moves):
 
110
 
111
  def get_best_move(self) -> Dict:
112
  '''Find best move (Fixed Pruning Counter)'''
 
113
  self.reset_stats()
114
  start_time = time.time()
115
 
 
140
 
141
  # Minimax search
142
  score = self.minimax(self.depth - 1, float('-inf'), float('inf'), False)
 
143
  self.game.undo_move(col)
144
 
145
  if score > best_score:
 
147
  best_move = col
148
 
149
  elapsed_time = (time.time() - start_time) * 1000
 
150
  pruning_efficiency = (self.nodes_pruned / (self.nodes_explored + self.nodes_pruned) * 100) \
151
+ if (self.nodes_explored + self.nodes_pruned) > 0 else 0
152
 
153
  return {
154
  'move': best_move,
report_generator.py CHANGED
@@ -2,25 +2,22 @@ from datetime import datetime
2
 
3
  def generate_match_report(game_mode, ai_mode, winner, move_history_data, game_board, search_depth=None):
4
  """Generate comprehensive match report with algorithm details"""
5
-
6
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
7
 
8
  # Build report
9
  lines = []
10
  lines.append("=" * 79)
11
  lines.append("")
12
- lines.append(" CONNECT FOUR AI - MATCH REPORT")
13
  lines.append("")
14
  lines.append("=" * 79)
15
  lines.append("")
16
  lines.append(f"Date: {timestamp}")
17
  lines.append(f"Game Mode: {game_mode}")
18
-
19
  if game_mode == "vs AI":
20
  lines.append(f"AI Difficulty: {ai_mode}")
21
  if search_depth:
22
  lines.append(f"Selected Search Depth: {search_depth}")
23
-
24
  lines.append(f"Winner: {winner}")
25
  lines.append(f"Total Moves: {len(move_history_data)}")
26
  lines.append("")
@@ -55,34 +52,34 @@ def generate_match_report(game_mode, ai_mode, winner, move_history_data, game_bo
55
  for threat in analysis['threats']:
56
  lines.append(f" * {threat}")
57
  lines.append("")
58
-
59
- if 'explanation' in move_data and move_data['explanation']:
60
- lines.append(" Move Explanation:")
61
- explanation = move_data['explanation']
62
- words = explanation.split()
63
- current_line = " "
64
- for word in words:
65
- if len(current_line + word) > 75:
66
- lines.append(current_line)
67
- current_line = " " + word + " "
68
- else:
69
- current_line += word + " "
70
- if current_line.strip():
71
  lines.append(current_line)
72
- lines.append("")
 
 
 
 
 
73
 
74
  if 'recommendation' in move_data:
75
  lines.append("")
76
  if move_data['recommendation']['is_optimal']:
77
- lines.append(" EXCELLENT MOVE! Matches AI recommendation.")
78
  else:
79
- lines.append(f" AI Alternative: Column {move_data['recommendation']['suggested_column']}")
80
- lines.append(f" (Score difference: {move_data['recommendation']['score_diff']} points)")
 
81
 
 
82
  lines.append("")
83
 
84
- lines.append("=" * 79)
85
- lines.append("")
86
  lines.append("MATCH STATISTICS")
87
  lines.append("-" * 79)
88
  lines.append("")
@@ -141,23 +138,21 @@ def generate_match_report(game_mode, ai_mode, winner, move_history_data, game_bo
141
  lines.append("")
142
 
143
  if winner == "Human":
144
- lines.append("Congratulations! You defeated the AI!")
145
  elif winner == "AI":
146
- lines.append("The AI won this match. Better luck next time!")
147
  elif "Player" in winner:
148
- lines.append(f"{winner} won the match!")
149
  else:
150
- lines.append("The match ended in a draw.")
151
 
152
  lines.append("")
153
  lines.append(f"Total Moves Played: {len(move_history_data)}")
154
  lines.append(f"Game Mode: {game_mode}")
155
-
156
  if game_mode == "vs AI":
157
  lines.append(f"AI Difficulty: {ai_mode}")
158
  if search_depth:
159
  lines.append(f"Selected Search Depth: {search_depth}")
160
-
161
  lines.append("")
162
  lines.append("=" * 79)
163
  lines.append("")
 
2
 
3
  def generate_match_report(game_mode, ai_mode, winner, move_history_data, game_board, search_depth=None):
4
  """Generate comprehensive match report with algorithm details"""
 
5
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
6
 
7
  # Build report
8
  lines = []
9
  lines.append("=" * 79)
10
  lines.append("")
11
+ lines.append(" CONNECT FOUR AI - MATCH REPORT")
12
  lines.append("")
13
  lines.append("=" * 79)
14
  lines.append("")
15
  lines.append(f"Date: {timestamp}")
16
  lines.append(f"Game Mode: {game_mode}")
 
17
  if game_mode == "vs AI":
18
  lines.append(f"AI Difficulty: {ai_mode}")
19
  if search_depth:
20
  lines.append(f"Selected Search Depth: {search_depth}")
 
21
  lines.append(f"Winner: {winner}")
22
  lines.append(f"Total Moves: {len(move_history_data)}")
23
  lines.append("")
 
52
  for threat in analysis['threats']:
53
  lines.append(f" * {threat}")
54
  lines.append("")
55
+
56
+ if 'explanation' in move_data and move_data['explanation']:
57
+ lines.append(" Move Explanation:")
58
+ explanation = move_data['explanation']
59
+ words = explanation.split()
60
+ current_line = " "
61
+ for word in words:
62
+ if len(current_line + word) > 75:
 
 
 
 
 
63
  lines.append(current_line)
64
+ current_line = " " + word + " "
65
+ else:
66
+ current_line += word + " "
67
+ if current_line.strip():
68
+ lines.append(current_line)
69
+ lines.append("")
70
 
71
  if 'recommendation' in move_data:
72
  lines.append("")
73
  if move_data['recommendation']['is_optimal']:
74
+ lines.append(" โœ“ EXCELLENT MOVE! Matches AI recommendation.")
75
  else:
76
+ lines.append(f" โ“˜ AI Alternative: Column {move_data['recommendation']['suggested_column']}")
77
+ lines.append(f" (Score difference: {move_data['recommendation']['score_diff']} points)")
78
+ lines.append("")
79
 
80
+ lines.append("=" * 79)
81
  lines.append("")
82
 
 
 
83
  lines.append("MATCH STATISTICS")
84
  lines.append("-" * 79)
85
  lines.append("")
 
138
  lines.append("")
139
 
140
  if winner == "Human":
141
+ lines.append("๐Ÿ† Congratulations! You defeated the AI!")
142
  elif winner == "AI":
143
+ lines.append("๐Ÿค– The AI won this match. Better luck next time!")
144
  elif "Player" in winner:
145
+ lines.append(f"๐Ÿ† {winner} won the match!")
146
  else:
147
+ lines.append("๐Ÿค The match ended in a draw.")
148
 
149
  lines.append("")
150
  lines.append(f"Total Moves Played: {len(move_history_data)}")
151
  lines.append(f"Game Mode: {game_mode}")
 
152
  if game_mode == "vs AI":
153
  lines.append(f"AI Difficulty: {ai_mode}")
154
  if search_depth:
155
  lines.append(f"Selected Search Depth: {search_depth}")
 
156
  lines.append("")
157
  lines.append("=" * 79)
158
  lines.append("")