Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import numpy as np | |
| import random | |
| import time | |
| # JavaScript confetti function | |
| def show_confetti(): | |
| st.components.v1.html(""" | |
| <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script> | |
| <script> | |
| confetti({ | |
| particleCount: 150, | |
| spread: 70, | |
| origin: { y: 0.6 }, | |
| colors: ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff'] | |
| }); | |
| setTimeout(() => { | |
| confetti({ | |
| particleCount: 100, | |
| angle: 60, | |
| spread: 55, | |
| origin: { x: 0 } | |
| }); | |
| confetti({ | |
| particleCount: 100, | |
| angle: 120, | |
| spread: 55, | |
| origin: { x: 1 } | |
| }); | |
| }, 250); | |
| </script> | |
| """, height=0) | |
| # Custom CSS for premium UI | |
| st.markdown(""" | |
| <style> | |
| /* Main container */ | |
| .main { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| } | |
| /* Title styling */ | |
| .title { | |
| background: linear-gradient(to right, #3a7bd5, #00d2ff); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| font-size: 3rem; | |
| text-align: center; | |
| margin-bottom: 0.5rem; | |
| font-weight: 800; | |
| text-shadow: 1px 1px 3px rgba(0,0,0,0.1); | |
| } | |
| /* Status message */ | |
| .status { | |
| text-align: center; | |
| font-size: 1.8rem; | |
| margin: 1rem 0; | |
| padding: 0.8rem; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| transition: all 0.3s ease; | |
| } | |
| /* Player X (human) */ | |
| .player-x { | |
| background: linear-gradient(to right, #ff416c, #ff4b2b); | |
| color: white; | |
| } | |
| /* Player O (AI) */ | |
| .player-o { | |
| background: linear-gradient(to right, #4776E6, #8E54E9); | |
| color: white; | |
| } | |
| /* Win message */ | |
| .win-message { | |
| background: linear-gradient(to right, #4CAF50, #8BC34A); | |
| color: white; | |
| animation: pulse 1.5s infinite; | |
| } | |
| /* Tie message */ | |
| .tie-message { | |
| background: linear-gradient(to right, #9E9E9E, #607D8B); | |
| color: white; | |
| } | |
| /* Game board */ | |
| .board-container { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 20px; | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.1); | |
| margin: 0 auto; | |
| max-width: 500px; | |
| } | |
| .board-button { | |
| font-size: 4rem !important; | |
| height: 120px !important; | |
| margin: 0.3rem !important; | |
| border-radius: 15px !important; | |
| transition: all 0.3s ease !important; | |
| border: none !important; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1) !important; | |
| } | |
| .board-button:hover { | |
| transform: translateY(-5px) !important; | |
| box-shadow: 0 8px 15px rgba(0,0,0,0.15) !important; | |
| } | |
| /* X button */ | |
| .x-button { | |
| color: #FF5252 !important; | |
| background: linear-gradient(135deg, #FFF6F6 0%, #FFEBEE 100%) !important; | |
| } | |
| /* O button */ | |
| .o-button { | |
| color: #536DFE !important; | |
| background: linear-gradient(135deg, #F5F7FF 0%, #E8EAF6 100%) !important; | |
| } | |
| /* Highlight last move */ | |
| .last-move { | |
| box-shadow: 0 0 0 3px #FFD700 !important; | |
| transform: scale(1.05) !important; | |
| } | |
| /* Scoreboard */ | |
| .scoreboard { | |
| display: flex; | |
| justify-content: space-around; | |
| margin: 1rem 0; | |
| background: white; | |
| border-radius: 15px; | |
| padding: 1rem; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| } | |
| .score-item { | |
| text-align: center; | |
| padding: 0.5rem; | |
| border-radius: 10px; | |
| flex: 1; | |
| margin: 0 0.5rem; | |
| } | |
| .score-x { | |
| background: #FFEBEE; | |
| color: #FF5252; | |
| } | |
| .score-o { | |
| background: #E8EAF6; | |
| color: #536DFE; | |
| } | |
| .score-tie { | |
| background: #EEEEEE; | |
| color: #616161; | |
| } | |
| /* Difficulty selector */ | |
| .difficulty-selector { | |
| background: white; | |
| border-radius: 15px; | |
| padding: 1rem; | |
| margin: 1rem auto; | |
| max-width: 500px; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| } | |
| /* Reset button */ | |
| .reset-button { | |
| background: linear-gradient(to right, #3a7bd5, #00d2ff) !important; | |
| color: white !important; | |
| font-weight: bold !important; | |
| margin-top: 1rem !important; | |
| border: none !important; | |
| border-radius: 10px !important; | |
| padding: 0.8rem !important; | |
| font-size: 1.1rem !important; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1) !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .reset-button:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 6px 12px rgba(0,0,0,0.15) !important; | |
| } | |
| /* Animations */ | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Sound effects (using HTML audio) | |
| sound_effects = """ | |
| <audio id="clickSound" src="https://www.soundjay.com/buttons/sounds/button-09.mp3" preload="auto"></audio> | |
| <audio id="winSound" src="https://www.soundjay.com/human/sounds/applause-8.mp3" preload="auto"></audio> | |
| <script> | |
| function playSound(soundId) { | |
| document.getElementById(soundId).play(); | |
| } | |
| </script> | |
| """ | |
| st.components.v1.html(sound_effects, height=0) | |
| def initialize_game(): | |
| """Initialize the game board.""" | |
| st.session_state.game['board'] = [' ' for _ in range(9)] | |
| st.session_state.game['current_player'] = 'X' | |
| st.session_state.game['winner'] = None | |
| st.session_state.game['game_over'] = False | |
| st.session_state.game['last_move'] = None | |
| def check_winner(board): | |
| """Check if there's a winner or if the game is a tie.""" | |
| # Check rows | |
| for i in range(0, 9, 3): | |
| if board[i] == board[i+1] == board[i+2] != ' ': | |
| return board[i] | |
| # Check columns | |
| for i in range(3): | |
| if board[i] == board[i+3] == board[i+6] != ' ': | |
| return board[i] | |
| # Check diagonals | |
| if board[0] == board[4] == board[8] != ' ': | |
| return board[0] | |
| if board[2] == board[4] == board[6] != ' ': | |
| return board[2] | |
| # Check for tie | |
| if ' ' not in board: | |
| return 'Tie' | |
| return None | |
| def make_move(board, position, player): | |
| """Make a move on the board.""" | |
| if board[position] == ' ': | |
| board[position] = player | |
| st.session_state.game['last_move'] = position | |
| st.components.v1.html("<script>playSound('clickSound')</script>", height=0) | |
| return True | |
| return False | |
| def minimax(board, depth, is_maximizing): | |
| """Minimax algorithm for unbeatable AI.""" | |
| winner = check_winner(board) | |
| if winner == 'O': | |
| return 10 - depth | |
| elif winner == 'X': | |
| return depth - 10 | |
| elif winner == 'Tie': | |
| return 0 | |
| if is_maximizing: | |
| best_score = -float('inf') | |
| for i in range(9): | |
| if board[i] == ' ': | |
| board[i] = 'O' | |
| score = minimax(board, depth + 1, False) | |
| board[i] = ' ' | |
| best_score = max(score, best_score) | |
| return best_score | |
| else: | |
| best_score = float('inf') | |
| for i in range(9): | |
| if board[i] == ' ': | |
| board[i] = 'X' | |
| score = minimax(board, depth + 1, True) | |
| board[i] = ' ' | |
| best_score = min(score, best_score) | |
| return best_score | |
| def ai_move(board, difficulty): | |
| """AI move based on selected difficulty.""" | |
| if difficulty == 'Easy': | |
| # Random moves | |
| available = [i for i, spot in enumerate(board) if spot == ' '] | |
| return random.choice(available) if available else -1 | |
| elif difficulty == 'Medium': | |
| # Mix of random and smart moves | |
| if random.random() < 0.5: | |
| return ai_move(board, 'Easy') | |
| else: | |
| return ai_move(board, 'Hard') | |
| elif difficulty == 'Hard': | |
| # Unbeatable minimax AI | |
| best_score = -float('inf') | |
| best_move = -1 | |
| for i in range(9): | |
| if board[i] == ' ': | |
| board[i] = 'O' | |
| score = minimax(board, 0, False) | |
| board[i] = ' ' | |
| if score > best_score: | |
| best_score = score | |
| best_move = i | |
| return best_move | |
| def display_board(board): | |
| """Display the Tic Tac Toe board with premium UI.""" | |
| st.markdown('<div class="board-container fade-in">', unsafe_allow_html=True) | |
| cols = st.columns(3) | |
| for i in range(9): | |
| with cols[i % 3]: | |
| button_label = board[i] if board[i] != ' ' else ' ' | |
| button_class = "" | |
| if board[i] == 'X': | |
| button_class = "x-button" | |
| elif board[i] == 'O': | |
| button_class = "o-button" | |
| # Highlight last move | |
| last_move_class = "last-move" if i == st.session_state.game['last_move'] else "" | |
| if st.button( | |
| button_label, | |
| key=f"btn_{i}", | |
| disabled=(board[i] != ' ' or | |
| st.session_state.game['game_over'] or | |
| st.session_state.game['current_player'] == 'O'), | |
| use_container_width=True, | |
| kwargs={"class": f"board-button {button_class} {last_move_class}"} | |
| ): | |
| if make_move(board, i, 'X'): | |
| st.session_state.game['winner'] = check_winner(board) | |
| if st.session_state.game['winner']: | |
| st.session_state.game['game_over'] = True | |
| if st.session_state.game['winner'] == 'X': | |
| st.session_state.game['scores']['X'] += 1 | |
| show_confetti() | |
| st.components.v1.html("<script>playSound('winSound')</script>", height=0) | |
| elif st.session_state.game['winner'] == 'O': | |
| st.session_state.game['scores']['O'] += 1 | |
| else: | |
| st.session_state.game['scores']['Ties'] += 1 | |
| else: | |
| st.session_state.game['current_player'] = 'O' | |
| st.rerun() | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| def ai_turn(): | |
| """Handle the AI's turn with visual feedback.""" | |
| if st.session_state.game['current_player'] == 'O' and not st.session_state.game['game_over']: | |
| with st.spinner("π€ AI is thinking..."): | |
| time.sleep(0.8) # Simulate thinking time | |
| move = ai_move(st.session_state.game['board'], st.session_state.game['difficulty']) | |
| if move != -1 and make_move(st.session_state.game['board'], move, 'O'): | |
| st.session_state.game['winner'] = check_winner(st.session_state.game['board']) | |
| if st.session_state.game['winner']: | |
| st.session_state.game['game_over'] = True | |
| if st.session_state.game['winner'] == 'X': | |
| st.session_state.game['scores']['X'] += 1 | |
| show_confetti() | |
| st.components.v1.html("<script>playSound('winSound')</script>", height=0) | |
| elif st.session_state.game['winner'] == 'O': | |
| st.session_state.game['scores']['O'] += 1 | |
| else: | |
| st.session_state.game['scores']['Ties'] += 1 | |
| else: | |
| st.session_state.game['current_player'] = 'X' | |
| st.rerun() | |
| def display_status(): | |
| """Display the current game status with premium UI.""" | |
| if st.session_state.game['winner']: | |
| if st.session_state.game['winner'] == 'Tie': | |
| st.markdown('<div class="status tie-message">π It\'s a Tie! π</div>', unsafe_allow_html=True) | |
| elif st.session_state.game['winner'] == 'X': | |
| st.markdown('<div class="status win-message">π Congratulations! You Won! π</div>', unsafe_allow_html=True) | |
| else: | |
| st.markdown('<div class="status win-message">π€ AI Defeated You!</div>', unsafe_allow_html=True) | |
| else: | |
| if st.session_state.game['current_player'] == 'X': | |
| st.markdown('<div class="status player-x">β¨ Your Turn (X) β¨</div>', unsafe_allow_html=True) | |
| else: | |
| st.markdown('<div class="status player-o">π€ AI\'s Turn (O)</div>', unsafe_allow_html=True) | |
| def display_scoreboard(): | |
| """Display the scoreboard with premium UI.""" | |
| st.markdown(""" | |
| <div class="scoreboard fade-in"> | |
| <div class="score-item score-x"> | |
| <div style="font-size: 1.2rem; font-weight: bold;">You (X)</div> | |
| <div style="font-size: 1.8rem;">{x_score}</div> | |
| </div> | |
| <div class="score-item score-tie"> | |
| <div style="font-size: 1.2rem; font-weight: bold;">Ties</div> | |
| <div style="font-size: 1.8rem;">{tie_score}</div> | |
| </div> | |
| <div class="score-item score-o"> | |
| <div style="font-size: 1.2rem; font-weight: bold;">AI (O)</div> | |
| <div style="font-size: 1.8rem;">{o_score}</div> | |
| </div> | |
| </div> | |
| """.format( | |
| x_score=st.session_state.game['scores']['X'], | |
| o_score=st.session_state.game['scores']['O'], | |
| tie_score=st.session_state.game['scores']['Ties'] | |
| ), unsafe_allow_html=True) | |
| def difficulty_selector(): | |
| """Display difficulty selector.""" | |
| st.markdown('<div class="difficulty-selector">', unsafe_allow_html=True) | |
| st.write("### Difficulty Level") | |
| difficulty = st.radio( | |
| "Choose AI difficulty:", | |
| ("Easy", "Medium", "Hard"), | |
| index=["Easy", "Medium", "Hard"].index(st.session_state.game['difficulty']), | |
| horizontal=True, | |
| label_visibility="collapsed" | |
| ) | |
| st.session_state.game['difficulty'] = difficulty | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| def main(): | |
| """Main function to run the Tic Tac Toe game.""" | |
| st.markdown('<h1 class="title">Ultimate Tic Tac Toe</h1>', unsafe_allow_html=True) | |
| st.markdown('<h4 style="text-align: center; color: #555;">You (X) vs AI (O)</h4>', unsafe_allow_html=True) | |
| # Display difficulty selector | |
| difficulty_selector() | |
| # Display scoreboard | |
| display_scoreboard() | |
| # Display game status | |
| display_status() | |
| # Display the board | |
| display_board(st.session_state.game['board']) | |
| # Handle AI turn | |
| ai_turn() | |
| # Reset button | |
| if st.button("π New Game", key="reset", use_container_width=True, | |
| kwargs={"class": "reset-button"}): | |
| initialize_game() | |
| st.rerun() | |
| if __name__ == "__main__": | |
| main() | |