Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import random | |
| import time | |
| class TicTacToeGradio: | |
| def __init__(self): | |
| self.reset_game() | |
| def reset_game(self, vs_computer=False): | |
| self.board = [' '] * 9 | |
| self.current_player = "X" | |
| self.vs_computer = vs_computer | |
| self.game_over = False | |
| self.winner = None | |
| self.players = {"X": "Player 1", "O": "Computer" if vs_computer else "Player 2"} | |
| def make_move(self, position): | |
| if self.game_over or self.board[position] != ' ': | |
| return False | |
| self.board[position] = self.current_player | |
| return True | |
| def computer_move(self): | |
| time.sleep(0.8) | |
| if self.game_over: | |
| return | |
| empty_positions = [i for i, val in enumerate(self.board) if val == ' '] | |
| if empty_positions: | |
| move = random.choice(empty_positions) | |
| self.board[move] = "O" | |
| def check_winner(self): | |
| wins = [(0,1,2),(3,4,5),(6,7,8), | |
| (0,3,6),(1,4,7),(2,5,8), | |
| (0,4,8),(2,4,6)] | |
| for i,j,k in wins: | |
| if self.board[i] == self.board[j] == self.board[k] and self.board[i] != ' ': | |
| return self.board[i] | |
| return None | |
| def is_draw(self): | |
| return ' ' not in self.board | |
| def switch_player(self): | |
| self.current_player = "O" if self.current_player == "X" else "X" | |
| def get_button_style(self, value): | |
| return "โ" if value == "X" else "โญ" if value == "O" else "" | |
| def get_status_message(self): | |
| if self.winner: | |
| emoji = "๐ฅ" if self.winner == "X" else "๐ค" if self.vs_computer else "๐ซ" | |
| return f"๐ **{self.players[self.winner]}** wins! {emoji}" | |
| elif self.is_draw(): | |
| return "๐ค **It's a draw!** Well played!" | |
| else: | |
| emoji = "๐ฅ" if self.current_player == "X" else "๐ค" if self.vs_computer else "๐ซ" | |
| return f"๐ฎ **{self.players[self.current_player]}'s Turn** {emoji}" | |
| game = TicTacToeGradio() | |
| def handle_click(btn_index): | |
| if game.game_over or not game.make_move(btn_index): | |
| return update_interface() | |
| winner = game.check_winner() | |
| if winner: | |
| game.winner = winner | |
| game.game_over = True | |
| return update_interface() | |
| if game.is_draw(): | |
| game.game_over = True | |
| return update_interface() | |
| game.switch_player() | |
| if game.vs_computer and game.current_player == "O": | |
| game.computer_move() | |
| winner = game.check_winner() | |
| if winner: | |
| game.winner = winner | |
| game.game_over = True | |
| elif game.is_draw(): | |
| game.game_over = True | |
| else: | |
| game.switch_player() | |
| return update_interface() | |
| def update_interface(): | |
| buttons = [ | |
| gr.update(value=game.get_button_style(game.board[i]), interactive=(not game.game_over and game.board[i] == ' ')) | |
| for i in range(9) | |
| ] | |
| status = game.get_status_message() | |
| return buttons + [status] | |
| def start_vs_friend(): | |
| game.reset_game(vs_computer=False) | |
| return update_interface() | |
| def start_vs_computer(): | |
| game.reset_game(vs_computer=True) | |
| return update_interface() | |
| def reset_current_game(): | |
| game.reset_game(game.vs_computer) | |
| return update_interface() | |
| CSS = """ | |
| body, .gradio-container { | |
| background: #fff0f5 !important; | |
| padding: 1rem 1.5rem; | |
| box-sizing: border-box; | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| color: #5b021f; | |
| } | |
| /* Heading and subtext */ | |
| h1 { | |
| font-size: clamp(1.8rem, 5vw, 2.5rem); | |
| color: #db2777; | |
| margin-bottom: 0.2rem; | |
| text-align: center; | |
| font-weight: 700; | |
| user-select: none; | |
| } | |
| .subtext { | |
| font-size: clamp(1rem, 3vw, 1.1rem); | |
| color: #9d174d; | |
| text-align: center; | |
| margin-bottom: 1.5rem; | |
| user-select: none; | |
| } | |
| /* Board container: perfect square, responsive */ | |
| #board-container { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| grid-template-rows: repeat(3, 1fr); | |
| gap: 12px; | |
| width: 90vw; | |
| max-width: 480px; | |
| height: 90vw; | |
| max-height: 480px; | |
| margin: 0 auto 1.5rem auto; | |
| } | |
| /* Buttons fill cells fully, keep perfect squares */ | |
| .game-btn { | |
| width: 100%; | |
| height: 100%; | |
| font-size: clamp(2rem, 6vw, 3.5rem) !important; | |
| border-radius: 12px !important; | |
| background: #fff0f5 !important; | |
| border: 2px solid #f9a8d4 !important; | |
| transition: background 0.2s ease-in-out; | |
| padding: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| user-select: none; | |
| } | |
| .game-btn:hover { | |
| background: #fbcfe8 !important; | |
| border-color: #ec4899 !important; | |
| } | |
| /* Mode Buttons */ | |
| .neat-button { | |
| padding: 12px 16px; | |
| border-radius: 8px; | |
| font-size: clamp(0.9rem, 3vw, 1.1rem); | |
| margin: 6px 0; | |
| background: #fce7f3; | |
| border: 1px solid #f472b6; | |
| color: #be185d; | |
| font-weight: 600; | |
| width: 100%; | |
| box-sizing: border-box; | |
| user-select: none; | |
| } | |
| .neat-button:hover { | |
| background: #f9a8d4; | |
| border-color: #ec4899; | |
| cursor: pointer; | |
| } | |
| /* Status */ | |
| #status { | |
| font-size: clamp(1rem, 4vw, 1.2rem); | |
| font-weight: bold; | |
| color: #db2777; | |
| text-align: center; | |
| margin: 1rem 0; | |
| user-select: none; | |
| } | |
| /* Player legend text */ | |
| .player-legend { | |
| font-size: clamp(0.85rem, 3vw, 1rem); | |
| color: #831843; | |
| margin-top: 0.5rem; | |
| user-select: none; | |
| } | |
| @media (max-width: 768px) { | |
| #board-container { | |
| width: 90vw; | |
| height: 90vw; | |
| max-width: 320px; | |
| max-height: 320px; | |
| gap: 8px; | |
| } | |
| .game-btn { | |
| font-size: clamp(1.8rem, 7vw, 2.5rem) !important; | |
| } | |
| .neat-button { | |
| font-size: clamp(0.85rem, 4vw, 1rem); | |
| } | |
| #status { | |
| font-size: clamp(0.95rem, 4vw, 1.1rem); | |
| } | |
| } | |
| """ | |
| with gr.Blocks(title="Tic Tac Toe", css=CSS) as demo: | |
| gr.Markdown(""" | |
| <h1>๐ TIC TAC TOE</h1> | |
| <p class="subtext">Play solo or with a friend โ may the best player win!</p> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1, min_width=220): | |
| gr.Markdown("### ๐ฎ Choose Game Mode", elem_id="status") | |
| vs_friend_btn = gr.Button("๐ฅ Play vs Friend", elem_classes=["neat-button"]) | |
| vs_computer_btn = gr.Button("๐ค Play vs Computer", elem_classes=["neat-button"]) | |
| reset_btn = gr.Button("๐ Reset Game", elem_classes=["neat-button"]) | |
| gr.Markdown(""" | |
| <div class="player-legend"> | |
| <ul> | |
| <li><strong>Player 1:</strong> โ</li> | |
| <li><strong>Player 2 / Computer:</strong> โญ</li> | |
| </ul> | |
| </div> | |
| """) | |
| with gr.Column(scale=2, min_width=300): | |
| status_display = gr.Markdown("๐ฎ Select a game mode to begin", elem_id="status") | |
| with gr.Column(elem_id="board-container"): | |
| btn0 = gr.Button("", elem_classes=["game-btn"]) | |
| btn1 = gr.Button("", elem_classes=["game-btn"]) | |
| btn2 = gr.Button("", elem_classes=["game-btn"]) | |
| btn3 = gr.Button("", elem_classes=["game-btn"]) | |
| btn4 = gr.Button("", elem_classes=["game-btn"]) | |
| btn5 = gr.Button("", elem_classes=["game-btn"]) | |
| btn6 = gr.Button("", elem_classes=["game-btn"]) | |
| btn7 = gr.Button("", elem_classes=["game-btn"]) | |
| btn8 = gr.Button("", elem_classes=["game-btn"]) | |
| buttons = [btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8] | |
| all_outputs = buttons + [status_display] | |
| for i, btn in enumerate(buttons): | |
| btn.click(fn=lambda i=i: handle_click(i), outputs=all_outputs) | |
| vs_friend_btn.click(fn=start_vs_friend, outputs=all_outputs) | |
| vs_computer_btn.click(fn=start_vs_computer, outputs=all_outputs) | |
| reset_btn.click(fn=reset_current_game, outputs=all_outputs) | |
| demo.launch() | |