|
|
import streamlit as st |
|
|
import os |
|
|
from datetime import datetime |
|
|
from connect_four_game import ConnectFour |
|
|
from minimax_ai import MinimaxAI |
|
|
from llm_explanation import MoveExplainer |
|
|
from report_generator import generate_match_report |
|
|
import random |
|
|
|
|
|
st.set_page_config( |
|
|
page_title="Connect Four AI Pro", |
|
|
layout="wide", |
|
|
initial_sidebar_state="expanded" |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown(''' |
|
|
<style> |
|
|
/* Import Google Fonts */ |
|
|
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Rajdhani:wght@300;400;500;600;700&display=swap'); |
|
|
|
|
|
/* Global Styles */ |
|
|
* { |
|
|
font-family: 'Rajdhani', sans-serif !important; |
|
|
} |
|
|
|
|
|
/* Main Background with Gaming Gradient */ |
|
|
.stApp { |
|
|
background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); |
|
|
background-attachment: fixed; |
|
|
} |
|
|
|
|
|
/* Sidebar Styling */ |
|
|
[data-testid="stSidebar"] { |
|
|
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%); |
|
|
border-right: 2px solid rgba(255, 215, 0, 0.3); |
|
|
} |
|
|
|
|
|
[data-testid="stSidebar"] h1, |
|
|
[data-testid="stSidebar"] h2, |
|
|
[data-testid="stSidebar"] h3 { |
|
|
color: #FFD700 !important; |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); |
|
|
} |
|
|
|
|
|
[data-testid="stSidebar"] p, |
|
|
[data-testid="stSidebar"] label { |
|
|
color: #E0E0E0 !important; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
/* Project Info Container */ |
|
|
.project-info-container { |
|
|
background: linear-gradient(135deg, rgba(255, 107, 107, 0.15), rgba(255, 165, 0, 0.15)); |
|
|
border-radius: 12px; |
|
|
padding: 1rem; |
|
|
margin: 0 0 1rem 0; |
|
|
border-left: 4px solid #FF6B6B; |
|
|
} |
|
|
|
|
|
.project-info-title { |
|
|
color: #FF6B6B !important; |
|
|
font-weight: 700 !important; |
|
|
font-size: 0.9rem !important; |
|
|
} |
|
|
|
|
|
.project-field { |
|
|
background: rgba(0, 0, 0, 0.3); |
|
|
border-left: 3px solid #FF6B6B; |
|
|
padding: 0.5rem 0.7rem; |
|
|
margin: 0.4rem 0; |
|
|
border-radius: 6px; |
|
|
color: #E0E0E0 !important; |
|
|
font-size: 0.8rem !important; |
|
|
line-height: 1.4 !important; |
|
|
} |
|
|
|
|
|
.project-field-label { |
|
|
color: #FF6B6B !important; |
|
|
font-weight: 700 !important; |
|
|
} |
|
|
|
|
|
.project-field-value { |
|
|
color: #E0E0E0 !important; |
|
|
margin-top: 0.2rem; |
|
|
font-size: 0.75rem; |
|
|
} |
|
|
|
|
|
/* Syllabus Coverage Container */ |
|
|
.syllabus-container { |
|
|
background: linear-gradient(135deg, rgba(0, 212, 255, 0.1), rgba(102, 126, 234, 0.1)); |
|
|
border-radius: 12px; |
|
|
padding: 1rem; |
|
|
margin: 1rem 0; |
|
|
border-left: 4px solid #00D4FF; |
|
|
} |
|
|
|
|
|
.syllabus-title { |
|
|
color: #00D4FF !important; |
|
|
font-weight: 700 !important; |
|
|
font-size: 0.95rem !important; |
|
|
} |
|
|
|
|
|
.syllabus-category { |
|
|
color: #FFD700 !important; |
|
|
font-weight: 700 !important; |
|
|
font-size: 0.85rem !important; |
|
|
margin-top: 0.8rem !important; |
|
|
margin-bottom: 0.4rem !important; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.5px; |
|
|
} |
|
|
|
|
|
.syllabus-item-direct { |
|
|
background: rgba(0, 255, 136, 0.15); |
|
|
border-left: 3px solid #00FF88; |
|
|
color: #00FF88 !important; |
|
|
} |
|
|
|
|
|
.syllabus-item-partial { |
|
|
background: rgba(255, 165, 0, 0.15); |
|
|
border-left: 3px solid #FFA500; |
|
|
color: #FFD700 !important; |
|
|
} |
|
|
|
|
|
.syllabus-item-foundational { |
|
|
background: rgba(100, 150, 255, 0.15); |
|
|
border-left: 3px solid #6496FF; |
|
|
color: #A8D8FF !important; |
|
|
} |
|
|
|
|
|
.syllabus-item-extensible { |
|
|
background: rgba(200, 100, 255, 0.15); |
|
|
border-left: 3px solid #C864FF; |
|
|
color: #E0B8FF !important; |
|
|
} |
|
|
|
|
|
.syllabus-item { |
|
|
padding: 0.6rem 0.7rem; |
|
|
margin: 0.4rem 0; |
|
|
border-radius: 6px; |
|
|
font-size: 0.8rem !important; |
|
|
line-height: 1.4 !important; |
|
|
} |
|
|
|
|
|
.syllabus-topic-name { |
|
|
font-weight: 700 !important; |
|
|
} |
|
|
|
|
|
.syllabus-topic-usage { |
|
|
font-size: 0.75rem !important; |
|
|
margin-top: 0.2rem; |
|
|
opacity: 0.9; |
|
|
} |
|
|
|
|
|
/* Main Title */ |
|
|
h1 { |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
font-weight: 900 !important; |
|
|
font-size: 3rem !important; |
|
|
background: linear-gradient(90deg, #FFD700, #FFA500, #FF6347); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
text-align: center; |
|
|
text-shadow: 0 0 30px rgba(255, 215, 0, 0.5); |
|
|
margin-bottom: 1rem !important; |
|
|
animation: glow 2s ease-in-out infinite alternate; |
|
|
} |
|
|
|
|
|
@keyframes glow { |
|
|
from { |
|
|
filter: drop-shadow(0 0 5px #FFD700); |
|
|
} |
|
|
to { |
|
|
filter: drop-shadow(0 0 20px #FFD700); |
|
|
} |
|
|
} |
|
|
|
|
|
/* Subheaders */ |
|
|
h2, h3 { |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
color: #FFD700 !important; |
|
|
font-weight: 700 !important; |
|
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.3); |
|
|
border-bottom: 2px solid rgba(255, 215, 0, 0.3); |
|
|
padding-bottom: 0.5rem; |
|
|
margin-top: 1.5rem !important; |
|
|
} |
|
|
|
|
|
/* Game Board Container */ |
|
|
.board-container { |
|
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); |
|
|
border-radius: 20px; |
|
|
padding: 1rem; |
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), |
|
|
inset 0 0 30px rgba(255, 215, 0, 0.1); |
|
|
border: 3px solid rgba(255, 215, 0, 0.3); |
|
|
margin: 1rem auto; |
|
|
text-align: center; |
|
|
width: fit-content; |
|
|
display: block; |
|
|
position: static !important; |
|
|
height: auto !important; |
|
|
} |
|
|
|
|
|
/* Board Display */ |
|
|
.board-display { |
|
|
font-family: 'Courier New', monospace !important; |
|
|
font-size: 0.85rem !important; |
|
|
line-height: 1.2 !important; |
|
|
color: #FFD700 !important; |
|
|
background: linear-gradient(135deg, rgba(15, 12, 41, 0.8), rgba(48, 43, 99, 0.8)) !important; |
|
|
border: 2px solid rgba(255, 215, 0, 0.4) !important; |
|
|
border-radius: 12px !important; |
|
|
padding: 0.8rem !important; |
|
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.3) !important; |
|
|
overflow-x: auto !important; |
|
|
white-space: pre !important; |
|
|
width: fit-content !important; |
|
|
margin: 0 auto !important; |
|
|
height: auto !important; |
|
|
position: static !important; |
|
|
} |
|
|
|
|
|
/* Learning Insights Container */ |
|
|
.learning-insights { |
|
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)); |
|
|
border-radius: 12px; |
|
|
padding: 1rem; |
|
|
margin-top: 1rem; |
|
|
border-left: 4px solid #00D4FF; |
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
|
|
|
.insight-title { |
|
|
color: #00D4FF !important; |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
font-weight: 700 !important; |
|
|
font-size: 0.95rem !important; |
|
|
text-shadow: 0 0 8px rgba(0, 212, 255, 0.5) !important; |
|
|
margin-bottom: 0.8rem !important; |
|
|
} |
|
|
|
|
|
.insight-item { |
|
|
background: rgba(0, 0, 0, 0.2); |
|
|
border-left: 3px solid #00D4FF; |
|
|
padding: 0.6rem 0.8rem; |
|
|
margin: 0.5rem 0; |
|
|
border-radius: 6px; |
|
|
color: #E0E0E0 !important; |
|
|
font-size: 0.9rem !important; |
|
|
line-height: 1.4 !important; |
|
|
} |
|
|
|
|
|
/* Buttons - Gaming Style */ |
|
|
.stButton button { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; |
|
|
color: white !important; |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
font-weight: 700 !important; |
|
|
font-size: 1.1rem !important; |
|
|
border: 2px solid rgba(255, 215, 0, 0.5) !important; |
|
|
border-radius: 12px !important; |
|
|
padding: 0.75rem 1.5rem !important; |
|
|
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4) !important; |
|
|
transition: all 0.3s ease !important; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.stButton button:hover { |
|
|
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important; |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 12px 30px rgba(102, 126, 234, 0.6) !important; |
|
|
border-color: #FFD700 !important; |
|
|
} |
|
|
|
|
|
.stButton button:disabled { |
|
|
background: linear-gradient(135deg, #4a4a4a 0%, #2d2d2d 100%) !important; |
|
|
opacity: 0.5 !important; |
|
|
cursor: not-allowed !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
|
|
|
/* Column Buttons - Special Styling */ |
|
|
div[data-testid="column"] .stButton button { |
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%) !important; |
|
|
width: 100%; |
|
|
height: 60px; |
|
|
font-size: 1.3rem !important; |
|
|
margin: 0.2rem 0; |
|
|
} |
|
|
|
|
|
div[data-testid="column"] .stButton button:hover { |
|
|
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%) !important; |
|
|
transform: scale(1.05); |
|
|
} |
|
|
|
|
|
/* Info Cards */ |
|
|
.info-card { |
|
|
background: linear-gradient(135deg, #232526 0%, #414345 100%); |
|
|
border-radius: 15px; |
|
|
padding: 1.5rem; |
|
|
margin: 1rem 0; |
|
|
border: 2px solid rgba(255, 215, 0, 0.3); |
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); |
|
|
} |
|
|
|
|
|
/* Stats Display */ |
|
|
.metric-container { |
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); |
|
|
border-radius: 12px; |
|
|
padding: 1rem; |
|
|
margin: 0.5rem 0; |
|
|
border-left: 4px solid #FFD700; |
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
|
|
|
/* Algorithm Performance Card */ |
|
|
.algo-perf-card { |
|
|
background: linear-gradient(135deg, #1a3a52 0%, #2d5a7b 100%); |
|
|
border-radius: 15px; |
|
|
padding: 1.2rem; |
|
|
margin: 1rem 0; |
|
|
border: 2px solid rgba(102, 126, 234, 0.5); |
|
|
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2); |
|
|
} |
|
|
|
|
|
.algo-perf-card h4 { |
|
|
color: #00D4FF !important; |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
margin-bottom: 1rem !important; |
|
|
text-shadow: 0 0 10px rgba(0, 212, 255, 0.5); |
|
|
} |
|
|
|
|
|
.algo-stat-item { |
|
|
background: rgba(0, 0, 0, 0.3); |
|
|
border-left: 3px solid #00D4FF; |
|
|
padding: 0.8rem; |
|
|
margin: 0.5rem 0; |
|
|
border-radius: 8px; |
|
|
color: #E0E0E0; |
|
|
font-size: 0.95rem; |
|
|
} |
|
|
|
|
|
.algo-stat-label { |
|
|
color: #00D4FF !important; |
|
|
font-weight: 700 !important; |
|
|
} |
|
|
|
|
|
/* AI Badge */ |
|
|
.ai-badge { |
|
|
display: inline-block; |
|
|
background: linear-gradient(135deg, #FF6347, #FF4500); |
|
|
color: white; |
|
|
padding: 0.5rem 1.5rem; |
|
|
border-radius: 25px; |
|
|
font-family: 'Orbitron', sans-serif; |
|
|
font-weight: 700; |
|
|
font-size: 1.2rem; |
|
|
border: 2px solid rgba(255, 215, 0, 0.5); |
|
|
box-shadow: 0 5px 15px rgba(255, 99, 71, 0.4); |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 2px; |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0%, 100% { |
|
|
transform: scale(1); |
|
|
} |
|
|
50% { |
|
|
transform: scale(1.05); |
|
|
} |
|
|
} |
|
|
|
|
|
/* Two Player Badge */ |
|
|
.two-player-badge { |
|
|
display: inline-block; |
|
|
background: linear-gradient(135deg, #4facfe, #00f2fe); |
|
|
color: white; |
|
|
padding: 0.5rem 1.5rem; |
|
|
border-radius: 25px; |
|
|
font-family: 'Orbitron', sans-serif; |
|
|
font-weight: 700; |
|
|
font-size: 1.2rem; |
|
|
border: 2px solid rgba(255, 215, 0, 0.5); |
|
|
box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4); |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 2px; |
|
|
} |
|
|
|
|
|
/* Winner Popup */ |
|
|
.winner-popup { |
|
|
position: fixed; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
padding: 3rem; |
|
|
border-radius: 20px; |
|
|
border: 4px solid #FFD700; |
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8); |
|
|
z-index: 9999; |
|
|
text-align: center; |
|
|
animation: popIn 0.5s ease-out; |
|
|
} |
|
|
|
|
|
@keyframes popIn { |
|
|
0% { |
|
|
transform: translate(-50%, -50%) scale(0.5); |
|
|
opacity: 0; |
|
|
} |
|
|
100% { |
|
|
transform: translate(-50%, -50%) scale(1); |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
/* Expander Styling */ |
|
|
.streamlit-expanderHeader { |
|
|
background: linear-gradient(135deg, #232526 0%, #414345 100%) !important; |
|
|
border-radius: 10px !important; |
|
|
border: 1px solid rgba(255, 215, 0, 0.3) !important; |
|
|
color: #FFD700 !important; |
|
|
font-weight: 600 !important; |
|
|
} |
|
|
|
|
|
/* Radio Buttons */ |
|
|
.stRadio label { |
|
|
color: #E0E0E0 !important; |
|
|
font-weight: 600 !important; |
|
|
font-size: 1.1rem !important; |
|
|
} |
|
|
|
|
|
/* Slider */ |
|
|
.stSlider { |
|
|
padding: 1rem 0; |
|
|
} |
|
|
|
|
|
/* Text & Paragraphs */ |
|
|
p, li, span { |
|
|
color: #E0E0E0 !important; |
|
|
font-size: 1.05rem !important; |
|
|
line-height: 1.6 !important; |
|
|
} |
|
|
|
|
|
/* Strong/Bold Text */ |
|
|
strong { |
|
|
color: #FFD700 !important; |
|
|
font-weight: 700 !important; |
|
|
} |
|
|
|
|
|
/* Code blocks */ |
|
|
code { |
|
|
background: rgba(255, 215, 0, 0.1) !important; |
|
|
color: #FFD700 !important; |
|
|
padding: 0.2rem 0.5rem !important; |
|
|
border-radius: 5px !important; |
|
|
} |
|
|
|
|
|
/* Horizontal Rules */ |
|
|
hr { |
|
|
border-color: rgba(255, 215, 0, 0.3) !important; |
|
|
margin: 2rem 0 !important; |
|
|
} |
|
|
|
|
|
/* Success/Info/Warning/Error Messages */ |
|
|
.stSuccess, .stInfo, .stWarning, .stError { |
|
|
border-radius: 10px !important; |
|
|
border-left: 5px solid #FFD700 !important; |
|
|
} |
|
|
|
|
|
/* Scrollbar */ |
|
|
::-webkit-scrollbar { |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-track { |
|
|
background: #1a1a2e; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-thumb { |
|
|
background: linear-gradient(135deg, #667eea, #764ba2); |
|
|
border-radius: 6px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-thumb:hover { |
|
|
background: linear-gradient(135deg, #764ba2, #667eea); |
|
|
} |
|
|
|
|
|
/* Download Button */ |
|
|
.stDownloadButton button { |
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%) !important; |
|
|
color: white !important; |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
font-weight: 700 !important; |
|
|
border: 2px solid rgba(255, 215, 0, 0.5) !important; |
|
|
border-radius: 12px !important; |
|
|
padding: 0.75rem 1.5rem !important; |
|
|
box-shadow: 0 8px 20px rgba(17, 153, 142, 0.4) !important; |
|
|
transition: all 0.3s ease !important; |
|
|
} |
|
|
|
|
|
.stDownloadButton button:hover { |
|
|
background: linear-gradient(135deg, #38ef7d 0%, #11998e 100%) !important; |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 12px 30px rgba(17, 153, 142, 0.6) !important; |
|
|
} |
|
|
|
|
|
/* Analysis Cards */ |
|
|
.analysis-card { |
|
|
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); |
|
|
border-radius: 15px; |
|
|
padding: 1.5rem; |
|
|
margin: 1rem 0; |
|
|
border-left: 5px solid #FFD700; |
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); |
|
|
} |
|
|
|
|
|
.analysis-card h4 { |
|
|
color: #FFD700 !important; |
|
|
font-family: 'Orbitron', sans-serif !important; |
|
|
margin-bottom: 1rem !important; |
|
|
} |
|
|
</style> |
|
|
''', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
LEARNING_INSIGHTS = [ |
|
|
"๐ก Minimax: Plans moves ahead like a chess master, evaluating all possibilities recursively", |
|
|
"๐ Alpha-Beta Pruning: Eliminates 60-70% of unnecessary branches, making deep search practical", |
|
|
"๐ฎ Perfect Information: Connect Four has no hidden cardsโAI can calculate optimal play", |
|
|
"๐ณ Search Depth: Each level deeper exponentially increases positions (7^d growth)", |
|
|
"โ๏ธ Threat Detection: AI identifies winning opportunities AND blocks opponent threats", |
|
|
"โฑ๏ธ Terminal States: When AI finds winning move immediately (depth 0), no deeper search needed", |
|
|
"๐ Recursion: Minimax calls itself to evaluate all game futures before deciding", |
|
|
"๐ Big-O Notation: O(b^d) without pruning, O(b^d/2) with pruningโtheory meets practice", |
|
|
"๐ฏ Game Theory: Minimax chooses moves that maximize AI score, minimize opponent score", |
|
|
"๐พ State Management: Session state persists game across page refreshesโno data loss", |
|
|
"๐ Optimal Play: Depth 5 proves strong enough to block your best strategies", |
|
|
"โณ Time Trade-off: Deeper search = more thinking but slower response time", |
|
|
"๐ค Heuristics: Evaluation function rates positions without exploring every possibility", |
|
|
"๐ DFS vs BFS: Game AI uses depth-first search (memory efficient) not breadth-first", |
|
|
"๐ Exponential Growth: Why depth 8 takes 10x longer than depth 5", |
|
|
] |
|
|
|
|
|
|
|
|
SYLLABUS_CATEGORIES = { |
|
|
"๐ข DIRECTLY IMPLEMENTED (100%)": [ |
|
|
("State Space Search", "Board states and transitions"), |
|
|
("Uninformed Search", "DFS in minimax algorithm"), |
|
|
("Heuristic Search", "Position evaluation function"), |
|
|
("Informed Search", "Move ordering optimization"), |
|
|
("Game Playing", "Perfect information, turn-based"), |
|
|
("Minimax + Alpha-Beta", "Core AI engine"), |
|
|
], |
|
|
"๐ก PARTIALLY IMPLEMENTED (50%)": [ |
|
|
("Constraint Satisfaction", "Constraints and validity"), |
|
|
("AND/OR Graphs", "Game tree structure"), |
|
|
("Knowledge Representation", "Board and rules repr."), |
|
|
("Uncertainty Handling", "Heuristic confidence"), |
|
|
("Sequential Decisions", "Move sequencing"), |
|
|
], |
|
|
"๐ต FOUNDATIONAL CONCEPTS": [ |
|
|
("Propositional Logic", "Game rules as logic"), |
|
|
("Planning", "Planning moves ahead"), |
|
|
("Complex Decisions", "Balancing objectives"), |
|
|
], |
|
|
"๐ฃ EXTENSIBLE (Could Add)": [ |
|
|
("Machine Learning", "Learn evaluation function"), |
|
|
("Decision Trees", "Learn move selection"), |
|
|
("Reinforcement Learning", "Learn through self-play"), |
|
|
("Deep Learning", "Neural net evaluation"), |
|
|
], |
|
|
} |
|
|
|
|
|
|
|
|
PROJECT_INFO = { |
|
|
"Project Name": "Connect Four AI Pro", |
|
|
"Student Name": "Shubhanshu Bansod", |
|
|
"Roll Number": "03", |
|
|
"Project Guide": "Prof. Swati Chopade", |
|
|
"Department": "MCA", |
|
|
"College": "Veetmata Jijabai Technological Institute, Matunga, Mumbai", |
|
|
} |
|
|
|
|
|
|
|
|
if 'game' not in st.session_state: |
|
|
st.session_state.game = ConnectFour() |
|
|
st.session_state.game_mode = "vs AI" |
|
|
st.session_state.ai = MinimaxAI(st.session_state.game, depth=5) |
|
|
hf_token = os.getenv('HF_TOKEN') |
|
|
st.session_state.explainer = MoveExplainer(hf_token=hf_token) if hf_token else None |
|
|
st.session_state.move_history = [] |
|
|
st.session_state.move_history_detailed = [] |
|
|
st.session_state.player1_analyses = [] |
|
|
st.session_state.player2_analyses = [] |
|
|
st.session_state.game_over = False |
|
|
st.session_state.winner = None |
|
|
st.session_state.current_player = 1 |
|
|
st.session_state.show_winner_popup = False |
|
|
st.session_state.move_count = 0 |
|
|
st.session_state.current_depth = 5 |
|
|
st.session_state.last_human_move_analysis = None |
|
|
st.session_state.last_ai_move_analysis = None |
|
|
st.session_state.last_human_algo_perf = None |
|
|
st.session_state.last_ai_algo_perf = None |
|
|
st.session_state.last_p1_algo_perf = None |
|
|
st.session_state.last_p2_algo_perf = None |
|
|
st.session_state.current_insight_index = 0 |
|
|
|
|
|
|
|
|
col_title1, col_title2 = st.columns([3, 1]) |
|
|
with col_title1: |
|
|
st.title("๐ฎ CONNECT FOUR AI PRO") |
|
|
|
|
|
with col_title2: |
|
|
if st.session_state.game_mode == "vs AI": |
|
|
st.markdown(f'<div class="ai-badge">โก ULTRA AI</div>', unsafe_allow_html=True) |
|
|
else: |
|
|
st.markdown('<div class="two-player-badge">๐ฅ TWO PLAYER</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.sidebar.header("๐ PROJECT INFORMATION") |
|
|
|
|
|
|
|
|
with st.sidebar.expander("๐ ABOUT THIS PROJECT"): |
|
|
st.markdown('<div class="project-info-container"><div class="project-info-title">Project Details</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
for field, value in PROJECT_INFO.items(): |
|
|
st.markdown(f'<div class="project-field"><span class="project-field-label">{field}:</span><div class="project-field-value">{value}</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
st.sidebar.markdown("---") |
|
|
st.sidebar.header("โ๏ธ GAME SETTINGS") |
|
|
|
|
|
game_mode = st.sidebar.radio( |
|
|
"๐ฎ Game Mode", |
|
|
["vs AI", "Two Player"], |
|
|
index=0 if st.session_state.game_mode == "vs AI" else 1 |
|
|
) |
|
|
|
|
|
if game_mode != st.session_state.game_mode: |
|
|
st.session_state.game_mode = game_mode |
|
|
st.session_state.game.reset() |
|
|
st.session_state.move_history = [] |
|
|
st.session_state.move_history_detailed = [] |
|
|
st.session_state.player1_analyses = [] |
|
|
st.session_state.player2_analyses = [] |
|
|
st.session_state.game_over = False |
|
|
st.session_state.current_player = 1 |
|
|
st.session_state.move_count = 0 |
|
|
st.rerun() |
|
|
|
|
|
if st.session_state.game_mode == "vs AI": |
|
|
st.sidebar.markdown("---") |
|
|
st.sidebar.subheader("๐๏ธ AI SEARCH DEPTH") |
|
|
depth = st.sidebar.slider("Depth Level (3-8)", 3, 8, 5) |
|
|
st.session_state.ai.depth = depth |
|
|
st.session_state.current_depth = depth |
|
|
|
|
|
st.sidebar.markdown("---") |
|
|
|
|
|
|
|
|
col1, col2 = st.sidebar.columns(2) |
|
|
with col1: |
|
|
if st.button("๐ NEW GAME", use_container_width=True): |
|
|
st.session_state.game.reset() |
|
|
st.session_state.move_history = [] |
|
|
st.session_state.move_history_detailed = [] |
|
|
st.session_state.player1_analyses = [] |
|
|
st.session_state.player2_analyses = [] |
|
|
st.session_state.game_over = False |
|
|
st.session_state.winner = None |
|
|
st.session_state.current_player = 1 |
|
|
st.session_state.show_winner_popup = False |
|
|
st.session_state.move_count = 0 |
|
|
st.session_state.last_human_move_analysis = None |
|
|
st.session_state.last_ai_move_analysis = None |
|
|
st.session_state.last_human_algo_perf = None |
|
|
st.session_state.last_ai_algo_perf = None |
|
|
st.session_state.last_p1_algo_perf = None |
|
|
st.session_state.last_p2_algo_perf = None |
|
|
st.rerun() |
|
|
|
|
|
with col2: |
|
|
if st.button("โฉ๏ธ UNDO", use_container_width=True, disabled=len(st.session_state.move_history) == 0): |
|
|
undo_count = min(2, len(st.session_state.move_history)) if st.session_state.game_mode == "vs AI" else 1 |
|
|
for _ in range(undo_count): |
|
|
if st.session_state.move_history: |
|
|
st.session_state.game.undo_move(st.session_state.move_history[-1]) |
|
|
st.session_state.move_history.pop() |
|
|
st.session_state.move_history_detailed.pop() |
|
|
if st.session_state.game_mode == "Two Player" and st.session_state.player1_analyses and st.session_state.player2_analyses: |
|
|
if st.session_state.move_count % 2 == 0: |
|
|
st.session_state.player2_analyses.pop() |
|
|
else: |
|
|
st.session_state.player1_analyses.pop() |
|
|
st.session_state.move_count -= 1 |
|
|
st.session_state.game_over = False |
|
|
st.session_state.show_winner_popup = False |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
st.sidebar.markdown("---") |
|
|
st.sidebar.subheader("๐ GAME STATISTICS") |
|
|
st.sidebar.markdown(f'<div class="metric-container"><strong>Total Moves:</strong> {st.session_state.move_count}</div>', unsafe_allow_html=True) |
|
|
|
|
|
if st.session_state.game_mode == "Two Player": |
|
|
st.sidebar.markdown(f'<div class="metric-container"><strong>Player 1 (๐ด):</strong> {len(st.session_state.player1_analyses)} moves</div>', unsafe_allow_html=True) |
|
|
st.sidebar.markdown(f'<div class="metric-container"><strong>Player 2 (๐ก):</strong> {len(st.session_state.player2_analyses)} moves</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.sidebar.markdown("---") |
|
|
with st.sidebar.expander("๐ COMPREHENSIVE COVERAGE (21 Topics)"): |
|
|
st.markdown('<div class="syllabus-container"><div class="syllabus-title">Topics Across 4 Categories</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
for category, topics in SYLLABUS_CATEGORIES.items(): |
|
|
st.markdown(f'<div class="syllabus-category">{category}</div>', unsafe_allow_html=True) |
|
|
|
|
|
if "DIRECTLY" in category: |
|
|
style_class = "syllabus-item-direct" |
|
|
elif "PARTIALLY" in category: |
|
|
style_class = "syllabus-item-partial" |
|
|
elif "FOUNDATIONAL" in category: |
|
|
style_class = "syllabus-item-foundational" |
|
|
else: |
|
|
style_class = "syllabus-item-extensible" |
|
|
|
|
|
for topic_name, topic_usage in topics: |
|
|
st.markdown(f'<div class="syllabus-item {style_class}"><span class="syllabus-topic-name">{topic_name}</span><div class="syllabus-topic-usage">{topic_usage}</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if st.session_state.move_history: |
|
|
with st.sidebar.expander("๐ MOVE HISTORY"): |
|
|
for i, col in enumerate(st.session_state.move_history, 1): |
|
|
player = "๐ด" if i % 2 == 1 else "๐ก" |
|
|
st.write(f"**Move {i}:** {player} โ Column {col}") |
|
|
|
|
|
|
|
|
def display_algorithm_performance(title, algo_perf): |
|
|
"""Display algorithm performance metrics in a styled card""" |
|
|
if algo_perf: |
|
|
st.markdown(f'<div class="algo-perf-card">', unsafe_allow_html=True) |
|
|
st.markdown(f"#### โ๏ธ {title}") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
st.markdown(f'<div class="algo-stat-item"><span class="algo-stat-label">๐ Nodes Explored:</span> {algo_perf["nodes_explored"]:,}</div>', unsafe_allow_html=True) |
|
|
st.markdown(f'<div class="algo-stat-item"><span class="algo-stat-label">โ๏ธ Pruning Efficiency:</span> {algo_perf["pruning_efficiency"]:.1f}%</div>', unsafe_allow_html=True) |
|
|
|
|
|
with col2: |
|
|
st.markdown(f'<div class="algo-stat-item"><span class="algo-stat-label">๐ซ Nodes Pruned:</span> {algo_perf["nodes_pruned"]:,}</div>', unsafe_allow_html=True) |
|
|
st.markdown(f'<div class="algo-stat-item"><span class="algo-stat-label">โก Response Time:</span> {algo_perf["time_ms"]:.0f}ms</div>', unsafe_allow_html=True) |
|
|
|
|
|
st.markdown(f'<div class="algo-stat-item"><span class="algo-stat-label">๐ณ Search Depth:</span> {algo_perf["depth"]}</div>', unsafe_allow_html=True) |
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if st.session_state.game_mode == "vs AI": |
|
|
col1, col2 = st.columns([1, 1], gap="large") |
|
|
|
|
|
with col1: |
|
|
st.subheader("๐ฏ GAME BOARD") |
|
|
board_str = st.session_state.game.board_to_string() |
|
|
st.markdown(f'<div class="board-container"><div class="board-display">{board_str}</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown(f'<div class="learning-insights"><div class="insight-title">๐ QUICK INSIGHT</div><div class="insight-item">{random.choice(LEARNING_INSIGHTS)}</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown("### ๐ฒ SELECT YOUR MOVE") |
|
|
cols = st.columns(7) |
|
|
for i, col in enumerate(cols): |
|
|
with col: |
|
|
if st.button(f"โ\n{i}", key=f"col_{i}", |
|
|
disabled=st.session_state.game_over or not st.session_state.game.is_valid_move(i)): |
|
|
|
|
|
st.session_state.game.make_move(i, 1) |
|
|
st.session_state.move_history.append(i) |
|
|
st.session_state.move_count += 1 |
|
|
|
|
|
|
|
|
ai_move = st.session_state.ai.get_best_move() |
|
|
is_optimal = (i == ai_move['move']) |
|
|
recommendation = { |
|
|
'is_optimal': is_optimal, |
|
|
'suggested_column': ai_move['move'], |
|
|
'score_diff': abs(ai_move['score'] - st.session_state.game.evaluate(2)) |
|
|
} |
|
|
|
|
|
|
|
|
human_algo_perf = { |
|
|
'nodes_explored': ai_move.get('nodes_explored', 0), |
|
|
'nodes_pruned': ai_move.get('nodes_pruned', 0), |
|
|
'pruning_efficiency': ai_move.get('pruning_efficiency', 0), |
|
|
'depth': ai_move.get('depth', 0), |
|
|
'time_ms': ai_move.get('time_ms', 0) |
|
|
} |
|
|
st.session_state.last_human_algo_perf = human_algo_perf |
|
|
|
|
|
|
|
|
explanation_data = None |
|
|
if st.session_state.explainer: |
|
|
move_data = { |
|
|
'move': i, |
|
|
'score': st.session_state.game.evaluate(2), |
|
|
'threats': ai_move.get('threats', []), |
|
|
'nodes_explored': ai_move.get('nodes_explored', 0) |
|
|
} |
|
|
explanation_result = st.session_state.explainer.explain_move(move_data) |
|
|
if explanation_result['success']: |
|
|
explanation_data = explanation_result['explanation'] |
|
|
st.session_state.last_human_move_analysis = explanation_result |
|
|
|
|
|
move_detail = { |
|
|
'player': 1, |
|
|
'player_name': 'Human', |
|
|
'column': i, |
|
|
'analysis': ai_move, |
|
|
'explanation': explanation_data, |
|
|
'recommendation': recommendation |
|
|
} |
|
|
st.session_state.move_history_detailed.append(move_detail) |
|
|
|
|
|
|
|
|
if st.session_state.game.check_winner(1): |
|
|
st.session_state.game_over = True |
|
|
st.session_state.winner = "Human" |
|
|
st.session_state.show_winner_popup = True |
|
|
st.rerun() |
|
|
|
|
|
if st.session_state.game.is_board_full(): |
|
|
st.session_state.game_over = True |
|
|
st.session_state.winner = "Draw" |
|
|
st.session_state.show_winner_popup = True |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
if not st.session_state.game_over: |
|
|
ai_move = st.session_state.ai.get_best_move() |
|
|
st.session_state.game.make_move(ai_move['move'], 2) |
|
|
st.session_state.move_history.append(ai_move['move']) |
|
|
st.session_state.move_count += 1 |
|
|
|
|
|
|
|
|
ai_algo_perf = { |
|
|
'nodes_explored': ai_move.get('nodes_explored', 0), |
|
|
'nodes_pruned': ai_move.get('nodes_pruned', 0), |
|
|
'pruning_efficiency': ai_move.get('pruning_efficiency', 0), |
|
|
'depth': ai_move.get('depth', 0), |
|
|
'time_ms': ai_move.get('time_ms', 0) |
|
|
} |
|
|
st.session_state.last_ai_algo_perf = ai_algo_perf |
|
|
|
|
|
|
|
|
ai_explanation_data = None |
|
|
if st.session_state.explainer: |
|
|
ai_move_data = { |
|
|
'move': ai_move['move'], |
|
|
'score': ai_move['score'], |
|
|
'threats': ai_move.get('threats', []), |
|
|
'nodes_explored': ai_move.get('nodes_explored', 0) |
|
|
} |
|
|
ai_explanation_result = st.session_state.explainer.explain_move(ai_move_data) |
|
|
if ai_explanation_result['success']: |
|
|
ai_explanation_data = ai_explanation_result['explanation'] |
|
|
st.session_state.last_ai_move_analysis = ai_explanation_result |
|
|
|
|
|
ai_move_detail = { |
|
|
'player': 2, |
|
|
'player_name': 'AI (Ultra)', |
|
|
'column': ai_move['move'], |
|
|
'analysis': ai_move, |
|
|
'explanation': ai_explanation_data |
|
|
} |
|
|
st.session_state.move_history_detailed.append(ai_move_detail) |
|
|
|
|
|
|
|
|
if st.session_state.game.check_winner(2): |
|
|
st.session_state.game_over = True |
|
|
st.session_state.winner = "AI" |
|
|
st.session_state.show_winner_popup = True |
|
|
st.rerun() |
|
|
|
|
|
if st.session_state.game.is_board_full(): |
|
|
st.session_state.game_over = True |
|
|
st.session_state.winner = "Draw" |
|
|
st.session_state.show_winner_popup = True |
|
|
st.rerun() |
|
|
|
|
|
st.rerun() |
|
|
|
|
|
with col2: |
|
|
st.subheader("๐ค AI ANALYSIS & INSIGHTS") |
|
|
|
|
|
|
|
|
if st.session_state.last_human_move_analysis: |
|
|
st.markdown('<div class="analysis-card">', unsafe_allow_html=True) |
|
|
st.markdown("#### ๐ค YOUR LAST MOVE") |
|
|
analysis = st.session_state.last_human_move_analysis |
|
|
st.info(f"**Explanation:** {analysis['explanation']}") |
|
|
st.success(f"**Threat Analysis:** {analysis['threat_analysis']}") |
|
|
st.warning(f"**Key Insight:** {analysis['key_insight']}") |
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if st.session_state.last_human_algo_perf: |
|
|
display_algorithm_performance("YOUR MOVE - ALGORITHM PERFORMANCE", st.session_state.last_human_algo_perf) |
|
|
|
|
|
|
|
|
if st.session_state.last_ai_move_analysis: |
|
|
st.markdown('<div class="analysis-card">', unsafe_allow_html=True) |
|
|
st.markdown("#### ๐ค AI'S LAST MOVE") |
|
|
analysis = st.session_state.last_ai_move_analysis |
|
|
st.info(f"**Explanation:** {analysis['explanation']}") |
|
|
st.success(f"**Threat Analysis:** {analysis['threat_analysis']}") |
|
|
st.warning(f"**Key Insight:** {analysis['key_insight']}") |
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if st.session_state.last_ai_algo_perf: |
|
|
display_algorithm_performance("AI MOVE - ALGORITHM PERFORMANCE", st.session_state.last_ai_algo_perf) |
|
|
|
|
|
if not st.session_state.last_human_move_analysis and not st.session_state.last_ai_move_analysis: |
|
|
st.info("๐ฒ Make your first move to see AI analysis!") |
|
|
|
|
|
else: |
|
|
col1, col2 = st.columns([1, 1], gap="large") |
|
|
|
|
|
with col1: |
|
|
st.subheader("๐ฏ GAME BOARD") |
|
|
board_str = st.session_state.game.board_to_string() |
|
|
st.markdown(f'<div class="board-container"><div class="board-display">{board_str}</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown(f'<div class="learning-insights"><div class="insight-title">๐ QUICK INSIGHT</div><div class="insight-item">{random.choice(LEARNING_INSIGHTS)}</div></div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
current_player_symbol = "๐ด RED" if st.session_state.current_player == 1 else "๐ก YELLOW" |
|
|
st.markdown(f'<div class="info-card"><h3 style="text-align: center;">Current Turn: {current_player_symbol}</h3></div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown("### ๐ฒ SELECT COLUMN") |
|
|
cols = st.columns(7) |
|
|
for i, col in enumerate(cols): |
|
|
with col: |
|
|
if st.button(f"โ\n{i}", key=f"col_{i}", |
|
|
disabled=st.session_state.game_over or not st.session_state.game.is_valid_move(i)): |
|
|
|
|
|
st.session_state.game.make_move(i, st.session_state.current_player) |
|
|
st.session_state.move_history.append(i) |
|
|
st.session_state.move_count += 1 |
|
|
|
|
|
|
|
|
ai_move = st.session_state.ai.get_best_move() |
|
|
is_optimal = (i == ai_move['move']) |
|
|
recommendation = { |
|
|
'is_optimal': is_optimal, |
|
|
'suggested_column': ai_move['move'], |
|
|
'score_diff': abs(ai_move['score'] - st.session_state.game.evaluate(2)) |
|
|
} |
|
|
|
|
|
|
|
|
algo_perf = { |
|
|
'nodes_explored': ai_move.get('nodes_explored', 0), |
|
|
'nodes_pruned': ai_move.get('nodes_pruned', 0), |
|
|
'pruning_efficiency': ai_move.get('pruning_efficiency', 0), |
|
|
'depth': ai_move.get('depth', 0), |
|
|
'time_ms': ai_move.get('time_ms', 0) |
|
|
} |
|
|
|
|
|
if st.session_state.current_player == 1: |
|
|
st.session_state.last_p1_algo_perf = algo_perf |
|
|
else: |
|
|
st.session_state.last_p2_algo_perf = algo_perf |
|
|
|
|
|
|
|
|
explanation_data = None |
|
|
if st.session_state.explainer: |
|
|
move_data = { |
|
|
'move': i, |
|
|
'score': st.session_state.game.evaluate(2), |
|
|
'threats': ai_move.get('threats', []), |
|
|
'nodes_explored': ai_move.get('nodes_explored', 0) |
|
|
} |
|
|
explanation_result = st.session_state.explainer.explain_move(move_data) |
|
|
if explanation_result['success']: |
|
|
explanation_data = explanation_result['explanation'] |
|
|
if st.session_state.current_player == 1: |
|
|
st.session_state.player1_analyses.append(explanation_result) |
|
|
else: |
|
|
st.session_state.player2_analyses.append(explanation_result) |
|
|
|
|
|
player_name = f"Player {st.session_state.current_player}" |
|
|
move_detail = { |
|
|
'player': st.session_state.current_player, |
|
|
'player_name': player_name, |
|
|
'column': i, |
|
|
'analysis': ai_move, |
|
|
'explanation': explanation_data, |
|
|
'recommendation': recommendation |
|
|
} |
|
|
st.session_state.move_history_detailed.append(move_detail) |
|
|
|
|
|
|
|
|
if st.session_state.game.check_winner(st.session_state.current_player): |
|
|
st.session_state.game_over = True |
|
|
st.session_state.winner = f"Player {st.session_state.current_player}" |
|
|
st.session_state.show_winner_popup = True |
|
|
st.rerun() |
|
|
|
|
|
if st.session_state.game.is_board_full(): |
|
|
st.session_state.game_over = True |
|
|
st.session_state.winner = "Draw" |
|
|
st.session_state.show_winner_popup = True |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
st.session_state.current_player = 2 if st.session_state.current_player == 1 else 1 |
|
|
st.rerun() |
|
|
|
|
|
with col2: |
|
|
st.subheader("๐ PLAYER ANALYSIS & PERFORMANCE") |
|
|
|
|
|
|
|
|
if st.session_state.player1_analyses: |
|
|
st.markdown('<div class="analysis-card">', unsafe_allow_html=True) |
|
|
st.markdown("#### ๐ด PLAYER 1 - LAST MOVE") |
|
|
analysis = st.session_state.player1_analyses[-1] |
|
|
st.info(f"**Explanation:** {analysis['explanation']}") |
|
|
st.success(f"**Threat Analysis:** {analysis['threat_analysis']}") |
|
|
st.warning(f"**Key Insight:** {analysis['key_insight']}") |
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if st.session_state.last_p1_algo_perf: |
|
|
display_algorithm_performance("PLAYER 1 - ALGORITHM PERFORMANCE", st.session_state.last_p1_algo_perf) |
|
|
|
|
|
|
|
|
if st.session_state.player2_analyses: |
|
|
st.markdown('<div class="analysis-card">', unsafe_allow_html=True) |
|
|
st.markdown("#### ๐ก PLAYER 2 - LAST MOVE") |
|
|
analysis = st.session_state.player2_analyses[-1] |
|
|
st.info(f"**Explanation:** {analysis['explanation']}") |
|
|
st.success(f"**Threat Analysis:** {analysis['threat_analysis']}") |
|
|
st.warning(f"**Key Insight:** {analysis['key_insight']}") |
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if st.session_state.last_p2_algo_perf: |
|
|
display_algorithm_performance("PLAYER 2 - ALGORITHM PERFORMANCE", st.session_state.last_p2_algo_perf) |
|
|
|
|
|
if not st.session_state.player1_analyses and not st.session_state.player2_analyses: |
|
|
st.info("๐ฒ Start playing to see move analysis!") |
|
|
|
|
|
|
|
|
if st.session_state.show_winner_popup: |
|
|
if st.session_state.winner == "Draw": |
|
|
st.balloons() |
|
|
st.success("๐ค **GAME OVER - IT'S A DRAW!**") |
|
|
elif st.session_state.winner == "Human": |
|
|
st.balloons() |
|
|
st.success("๐ **CONGRATULATIONS! YOU WON!**") |
|
|
elif st.session_state.winner == "AI": |
|
|
st.balloons() |
|
|
st.error("๐ค **AI WINS! Better luck next time!**") |
|
|
else: |
|
|
st.balloons() |
|
|
st.success(f"๐ **{st.session_state.winner.upper()} WINS!**") |
|
|
|
|
|
|
|
|
if st.session_state.game_over and st.session_state.move_history_detailed: |
|
|
st.markdown("---") |
|
|
st.subheader("๐ MATCH REPORT") |
|
|
|
|
|
report = generate_match_report( |
|
|
game_mode=st.session_state.game_mode, |
|
|
ai_mode="Ultra AI" if st.session_state.game_mode == "vs AI" else "N/A", |
|
|
winner=st.session_state.winner, |
|
|
move_history_data=st.session_state.move_history_detailed, |
|
|
game_board=st.session_state.game.board_to_string(), |
|
|
search_depth=st.session_state.current_depth if st.session_state.game_mode == "vs AI" else None |
|
|
) |
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
|
st.download_button( |
|
|
label="๐ฅ DOWNLOAD MATCH REPORT", |
|
|
data=report, |
|
|
file_name=f"connect_four_match_report_{timestamp}.txt", |
|
|
mime="text/plain", |
|
|
use_container_width=True |
|
|
) |
|
|
|