Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import random | |
| import threading | |
| import time | |
| class TicTacToe: | |
| def __init__(self): | |
| self.reset_game() | |
| self.scores = { | |
| "pvp": {"X": 0, "O": 0, "Draws": 0}, | |
| "pvc": {"X": 0, "O": 0, "Draws": 0} | |
| } | |
| def reset_game(self): | |
| """Reset game state completely""" | |
| self.board = [" " for _ in range(9)] | |
| self.current_player = "X" | |
| self.game_over = False | |
| self.player_names = {"X": "Player X", "O": "Player O"} | |
| def reset_scores_for_mode(self, mode): | |
| """Reset scores when switching game modes""" | |
| if mode in self.scores: | |
| self.scores[mode] = {"X": 0, "O": 0, "Draws": 0} | |
| def check_winner(self): | |
| win_conditions = [ | |
| [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 condition in win_conditions: | |
| if self.board[condition[0]] == self.board[condition[1]] == self.board[condition[2]] != " ": | |
| return self.board[condition[0]] | |
| if " " not in self.board: | |
| return "Draw" | |
| return None | |
| def computer_move(self): | |
| empty_spots = [i for i, spot in enumerate(self.board) if spot == " "] | |
| if not empty_spots: | |
| return None | |
| for spot in empty_spots: | |
| self.board[spot] = "O" | |
| if self.check_winner() == "O": | |
| self.board[spot] = " " | |
| return spot | |
| self.board[spot] = " " | |
| for spot in empty_spots: | |
| self.board[spot] = "X" | |
| if self.check_winner() == "X": | |
| self.board[spot] = " " | |
| return spot | |
| self.board[spot] = " " | |
| if 4 in empty_spots: | |
| return 4 | |
| return random.choice(empty_spots) | |
| def make_move(self, index): | |
| if self.game_over or self.board[index] != " ": | |
| return self.get_game_state() | |
| self.board[index] = self.current_player | |
| winner = self.check_winner() | |
| if winner: | |
| self.game_over = True | |
| if winner != "Draw": | |
| self.scores[self.game_mode][winner] += 1 | |
| else: | |
| self.scores[self.game_mode]["Draws"] += 1 | |
| else: | |
| self.current_player = "O" if self.current_player == "X" else "X" | |
| if hasattr(self, 'game_mode') and self.game_mode == "pvc" and self.current_player == "O" and not self.game_over: | |
| comp_move = self.computer_move() | |
| if comp_move is not None: | |
| return self.make_move(comp_move) | |
| return self.get_game_state() | |
| def get_game_state(self): | |
| status = "" | |
| if self.game_over: | |
| winner = self.check_winner() | |
| if winner == "Draw": | |
| status = "🤝 Game ended in a draw!" | |
| else: | |
| status = f"🎉 {self.player_names[winner]} wins!" | |
| else: | |
| status = f"🎯 {self.player_names[self.current_player]}'s turn" | |
| current_scores = {"X": 0, "O": 0, "Draws": 0} | |
| if hasattr(self, 'game_mode') and self.game_mode in self.scores: | |
| current_scores = self.scores[self.game_mode] | |
| return { | |
| "board": self.board, | |
| "status": status, | |
| "scores": current_scores, | |
| "game_over": self.game_over | |
| } | |
| def create_ui(): | |
| game = TicTacToe() | |
| custom_css = """ | |
| #main_title { | |
| text-align: center; | |
| color: #2E86AB; | |
| font-size: 2.5em; | |
| margin-bottom: 20px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.1); | |
| } | |
| #game_board { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 10px; | |
| max-width: 300px; | |
| margin: 20px auto; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 15px; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.1); | |
| } | |
| .cell_button { | |
| height: 80px; | |
| font-size: 36px; | |
| font-weight: bold; | |
| border-radius: 10px; | |
| border: 2px solid #fff; | |
| background: rgba(255,255,255,0.9); | |
| backdrop-filter: blur(10px); | |
| transition: all 0.3s ease; | |
| cursor: pointer; | |
| color: #333; | |
| } | |
| .cell_button:hover { | |
| background: rgba(255,255,255,1); | |
| transform: scale(1.05); | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| } | |
| .cell_x { | |
| background: linear-gradient(135deg, #ff6b6b, #ee5a52) !important; | |
| color: white !important; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| border: 2px solid #ff4757 !important; | |
| box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4) !important; | |
| } | |
| .cell_o { | |
| background: linear-gradient(135deg, #4ecdc4, #44a08d) !important; | |
| color: white !important; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| border: 2px solid #26d0ce !important; | |
| box-shadow: 0 4px 15px rgba(78, 205, 196, 0.4) !important; | |
| } | |
| .cell_x:hover { | |
| background: linear-gradient(135deg, #ee5a52, #ff6b6b) !important; | |
| transform: scale(1.08) !important; | |
| box-shadow: 0 6px 20px rgba(255, 107, 107, 0.6) !important; | |
| } | |
| .cell_o:hover { | |
| background: linear-gradient(135deg, #44a08d, #4ecdc4) !important; | |
| transform: scale(1.08) !important; | |
| box-shadow: 0 6px 20px rgba(78, 205, 196, 0.6) !important; | |
| } | |
| #status_display { | |
| text-align: center; | |
| font-size: 1.4em; | |
| font-weight: bold; | |
| padding: 15px; | |
| margin: 20px 0; | |
| background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); | |
| border-radius: 10px; | |
| color: #333; | |
| } | |
| #scores_section { | |
| background: rgba(255,255,255,0.8); | |
| backdrop-filter: blur(10px); | |
| border-radius: 15px; | |
| padding: 20px; | |
| margin: 20px 0; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1); | |
| } | |
| .score_display { | |
| text-align: center; | |
| font-size: 1.2em; | |
| font-weight: bold; | |
| color: #333; | |
| } | |
| .control_button { | |
| background: linear-gradient(45deg, #667eea, #764ba2); | |
| color: white; | |
| border: none; | |
| padding: 12px 25px; | |
| border-radius: 25px; | |
| font-weight: bold; | |
| font-size: 16px; | |
| margin: 5px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .control_button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
| } | |
| .mode_button { | |
| background: linear-gradient(45deg, #f093fb, #f5576c); | |
| color: white; | |
| border: none; | |
| padding: 15px 30px; | |
| border-radius: 25px; | |
| font-weight: bold; | |
| font-size: 18px; | |
| margin: 10px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .mode_button:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 6px 20px rgba(0,0,0,0.2); | |
| } | |
| """ | |
| with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="Ultimate Tic-Tac-Toe") as app: | |
| gr.HTML('<h1 id="main_title">🎮 Ultimate Tic-Tac-Toe</h1>') | |
| current_view = gr.State("main_menu") | |
| game_mode = gr.State() | |
| player_x_name = gr.State() | |
| player_o_name = gr.State() | |
| with gr.Column(visible=True, elem_id="main_menu") as main_menu: | |
| gr.HTML('<div style="text-align: center; margin: 30px 0;"><p style="font-size: 1.2em; color: #666;">Challenge yourself in the classic game of strategy!</p></div>') | |
| play_btn = gr.Button("🚀 Start Playing", elem_classes="mode_button", size="lg") | |
| with gr.Column(visible=False, elem_id="mode_select") as mode_select: | |
| gr.HTML('<h2 style="text-align: center; color: #333; margin: 20px 0;">🎯 Select Game Mode</h2>') | |
| with gr.Row(): | |
| pvp_btn = gr.Button("👥 Player vs Player", elem_classes="mode_button", size="lg") | |
| pvc_btn = gr.Button("🤖 Player vs Computer", elem_classes="mode_button", size="lg") | |
| back_to_menu_btn = gr.Button("← Back to Menu", elem_classes="control_button") | |
| with gr.Column(visible=False, elem_id="name_input") as name_input: | |
| gr.HTML('<h2 style="text-align: center; color: #333; margin: 20px 0;">📝 Player Setup</h2>') | |
| with gr.Row(): | |
| p1_name = gr.Textbox(label="🎯 Player X Name", value="Player X", max_lines=1) | |
| p2_name = gr.Textbox(label="🎯 Player O Name", value="Player O", visible=False, max_lines=1) | |
| start_game_btn = gr.Button("🚀 Start Game", elem_classes="mode_button", size="lg") | |
| back_to_mode_btn = gr.Button("← Back", elem_classes="control_button") | |
| with gr.Column(visible=False, elem_id="game_ui") as game_ui: | |
| with gr.Row(elem_id="scores_section"): | |
| x_score = gr.Textbox("X: 0", label="🔥 Player X Wins", interactive=False, elem_classes="score_display") | |
| draws_score = gr.Textbox("Draws: 0", label="🤝 Draws", interactive=False, elem_classes="score_display") | |
| o_score = gr.Textbox("O: 0", label="⭐ Player O Wins", interactive=False, elem_classes="score_display") | |
| status_display = gr.HTML('<div id="status_display">🎯 Game Starting...</div>') | |
| with gr.Column(elem_id="game_board"): | |
| with gr.Row(): | |
| btn_0 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| btn_1 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| btn_2 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| with gr.Row(): | |
| btn_3 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| btn_4 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| btn_5 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| with gr.Row(): | |
| btn_6 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| btn_7 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| btn_8 = gr.Button(" ", elem_classes="cell_button", size="lg", min_width=80) | |
| buttons = [btn_0, btn_1, btn_2, btn_3, btn_4, btn_5, btn_6, btn_7, btn_8] | |
| with gr.Row(): | |
| reset_btn = gr.Button("🔄 New Game", elem_classes="control_button") | |
| menu_btn = gr.Button("🏠 Main Menu", elem_classes="control_button") | |
| def show_mode_select(): | |
| return { | |
| main_menu: gr.Column(visible=False), | |
| mode_select: gr.Column(visible=True), | |
| current_view: "mode_select" | |
| } | |
| def show_main_menu(): | |
| game.reset_game() | |
| return { | |
| main_menu: gr.Column(visible=True), | |
| mode_select: gr.Column(visible=False), | |
| name_input: gr.Column(visible=False), | |
| game_ui: gr.Column(visible=False), | |
| current_view: "main_menu" | |
| } | |
| def show_name_input(mode): | |
| game.reset_scores_for_mode(mode) | |
| game.game_mode = mode | |
| return { | |
| mode_select: gr.Column(visible=False), | |
| name_input: gr.Column(visible=True), | |
| p2_name: gr.Textbox(visible=(mode == "pvp")), | |
| current_view: "name_input", | |
| game_mode: mode | |
| } | |
| def start_game(p1, p2, mode): | |
| try: | |
| game.reset_game() | |
| game.game_mode = mode | |
| game.player_names["X"] = p1 or "Player X" | |
| game.player_names["O"] = p2 if mode == "pvp" else "Computer" | |
| initial_state = game.get_game_state() | |
| updates = { | |
| name_input: gr.Column(visible=False), | |
| game_ui: gr.Column(visible=True), | |
| status_display: f'<div id="status_display">{initial_state["status"]}</div>', | |
| x_score: f'{game.player_names["X"]}: {initial_state["scores"]["X"]}', | |
| o_score: f'{game.player_names["O"]}: {initial_state["scores"]["O"]}', | |
| draws_score: f'Draws: {initial_state["scores"]["Draws"]}', | |
| current_view: "game_ui" | |
| } | |
| for i in range(9): | |
| updates[buttons[i]] = gr.Button(" ", elem_classes="cell_button") | |
| return updates | |
| except Exception as e: | |
| print(f"Error starting game: {e}") | |
| return {} | |
| def handle_click(index): | |
| try: | |
| if game.game_over: | |
| return {status_display: f'<div id="status_display">Game Over! Click New Game to restart.</div>'} | |
| new_state = game.make_move(index) | |
| updates = { | |
| status_display: f'<div id="status_display">{new_state["status"]}</div>', | |
| x_score: f'{game.player_names["X"]}: {new_state["scores"]["X"]}', | |
| o_score: f'{game.player_names["O"]}: {new_state["scores"]["O"]}', | |
| draws_score: f'Draws: {new_state["scores"]["Draws"]}' | |
| } | |
| for i in range(9): | |
| cell_value = new_state['board'][i] | |
| if cell_value == "X": | |
| updates[buttons[i]] = gr.Button("X", elem_classes="cell_button cell_x") | |
| elif cell_value == "O": | |
| updates[buttons[i]] = gr.Button("O", elem_classes="cell_button cell_o") | |
| else: | |
| updates[buttons[i]] = gr.Button(" ", elem_classes="cell_button") | |
| if new_state["game_over"]: | |
| def auto_restart(): | |
| time.sleep(3) | |
| pass | |
| threading.Thread(target=auto_restart, daemon=True).start() | |
| return updates | |
| except Exception as e: | |
| print(f"Error handling click: {e}") | |
| return {status_display: f'<div id="status_display">Error occurred. Please start a new game.</div>'} | |
| def reset_game_manual(): | |
| try: | |
| game.reset_game() | |
| initial_state = game.get_game_state() | |
| updates = { | |
| status_display: f'<div id="status_display">{initial_state["status"]}</div>' | |
| } | |
| for i in range(9): | |
| updates[buttons[i]] = gr.Button(" ", elem_classes="cell_button") | |
| return updates | |
| except Exception as e: | |
| print(f"Error resetting game: {e}") | |
| return {status_display: f'<div id="status_display">Error resetting game. Please try again.</div>'} | |
| play_btn.click( | |
| show_mode_select, | |
| outputs=[main_menu, mode_select, current_view] | |
| ) | |
| back_to_menu_btn.click( | |
| show_main_menu, | |
| outputs=[main_menu, mode_select, name_input, game_ui, current_view] | |
| ) | |
| pvp_btn.click( | |
| lambda: show_name_input("pvp"), | |
| outputs=[mode_select, name_input, p2_name, current_view, game_mode] | |
| ) | |
| pvc_btn.click( | |
| lambda: show_name_input("pvc"), | |
| outputs=[mode_select, name_input, p2_name, current_view, game_mode] | |
| ) | |
| back_to_mode_btn.click( | |
| lambda: show_mode_select(), | |
| outputs=[mode_select, name_input, current_view] | |
| ) | |
| start_game_btn.click( | |
| lambda p1, p2, mode: start_game(p1, p2, mode), | |
| inputs=[p1_name, p2_name, game_mode], | |
| outputs=[name_input, game_ui, status_display, x_score, o_score, draws_score] + buttons + [current_view] | |
| ) | |
| for i, btn in enumerate(buttons): | |
| btn.click( | |
| lambda idx=i: handle_click(idx), | |
| outputs=[status_display, x_score, o_score, draws_score] + buttons | |
| ) | |
| reset_btn.click( | |
| reset_game_manual, | |
| outputs=[status_display] + buttons | |
| ) | |
| menu_btn.click( | |
| show_main_menu, | |
| outputs=[main_menu, mode_select, name_input, game_ui, current_view] | |
| ) | |
| return app | |
| if __name__ == "__main__": | |
| app = create_ui() | |
| app.launch() |