Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import numpy as np | |
| import time | |
| import threading | |
| from PIL import Image, ImageDraw | |
| import queue | |
| # Game constants | |
| WIDTH, HEIGHT = 800, 600 | |
| PADDLE_WIDTH, PADDLE_HEIGHT = 20, 100 | |
| BALL_SIZE = 15 | |
| PADDLE_SPEED = 8 | |
| BALL_SPEED_X, BALL_SPEED_Y = 8, 6 | |
| FPS = 60 | |
| class PingPongGame: | |
| def __init__(self): | |
| self.reset_game() | |
| self.game_running = False | |
| self.game_thread = None | |
| self.command_queue = queue.Queue() | |
| def reset_game(self): | |
| # Player paddle (left) | |
| self.player_y = HEIGHT // 2 - PADDLE_HEIGHT // 2 | |
| self.player_score = 0 | |
| # Opponent paddle (right) | |
| self.opponent_y = HEIGHT // 2 - PADDLE_HEIGHT // 2 | |
| self.opponent_score = 0 | |
| # Ball | |
| self.ball_x = WIDTH // 2 | |
| self.ball_y = HEIGHT // 2 | |
| self.ball_dx = BALL_SPEED_X | |
| self.ball_dy = BALL_SPEED_Y | |
| self.game_state = "menu" # menu, playing, game_over | |
| def draw_frame(self): | |
| # Create blank image | |
| img = Image.new('RGB', (WIDTH, HEIGHT), 'black') | |
| draw = ImageDraw.Draw(img) | |
| # Draw center line | |
| draw.line([(WIDTH//2, 0), (WIDTH//2, HEIGHT)], fill='white', width=4) | |
| if self.game_state == "menu": | |
| # Menu text | |
| font = ImageDraw.ImageFont.load_default() | |
| draw.text((WIDTH//2 - 100, HEIGHT//2 - 60), "PING PONG", fill='white', font=font) | |
| draw.text((WIDTH//2 - 120, HEIGHT//2), "Press START", fill='white', font=font) | |
| draw.text((WIDTH//2 - 80, HEIGHT//2 + 40), f"Use UP/DOWN to move", fill='white', font=font) | |
| elif self.game_state == "playing" or self.game_state == "game_over": | |
| # Draw paddles | |
| draw.rectangle([10, self.player_y, 10 + PADDLE_WIDTH, self.player_y + PADDLE_HEIGHT], | |
| fill='white') | |
| draw.rectangle([WIDTH - 30, self.opponent_y, WIDTH - 10, self.opponent_y + PADDLE_HEIGHT], | |
| fill='white') | |
| # Draw ball | |
| draw.ellipse([self.ball_x - BALL_SIZE//2, self.ball_y - BALL_SIZE//2, | |
| self.ball_x + BALL_SIZE//2, self.ball_y + BALL_SIZE//2], fill='white') | |
| # Draw scores | |
| font = ImageDraw.ImageFont.load_default() | |
| draw.text((WIDTH//4, 30), str(self.player_score), fill='white', font=font) | |
| draw.text((3*WIDTH//4, 30), str(self.opponent_score), fill='white', font=font) | |
| if self.game_state == "game_over": | |
| draw.text((WIDTH//2 - 80, HEIGHT//2), "GAME OVER", fill='red', font=font) | |
| draw.text((WIDTH//2 - 100, HEIGHT//2 + 40), "Press RESET", fill='white', font=font) | |
| return np.array(img) | |
| def update_game(self): | |
| if self.game_state != "playing": | |
| return | |
| # Move player paddle (from commands) | |
| try: | |
| while True: | |
| cmd = self.command_queue.get_nowait() | |
| if cmd == "up": | |
| self.player_y = max(0, self.player_y - PADDLE_SPEED) | |
| elif cmd == "down": | |
| self.player_y = min(HEIGHT - PADDLE_HEIGHT, self.player_y + PADDLE_SPEED) | |
| except queue.Empty: | |
| pass | |
| # AI opponent (simple tracking) | |
| if self.opponent_y + PADDLE_HEIGHT // 2 < self.ball_y: | |
| self.opponent_y = min(HEIGHT - PADDLE_HEIGHT, self.opponent_y + PADDLE_SPEED // 2) | |
| elif self.opponent_y + PADDLE_HEIGHT // 2 > self.ball_y: | |
| self.opponent_y = max(0, self.opponent_y - PADDLE_SPEED // 2) | |
| # Move ball | |
| self.ball_x += self.ball_dx | |
| self.ball_y += self.ball_dy | |
| # Ball collision with top/bottom | |
| if self.ball_y <= BALL_SIZE//2 or self.ball_y >= HEIGHT - BALL_SIZE//2: | |
| self.ball_dy = -self.ball_dy | |
| # Ball collision with player paddle | |
| if (self.ball_x <= 10 + PADDLE_WIDTH + BALL_SIZE//2 and | |
| self.player_y <= self.ball_y <= self.player_y + PADDLE_HEIGHT): | |
| self.ball_dx = -self.ball_dx | |
| self.ball_dy += np.random.randint(-2, 3) # Add some spin | |
| # Ball collision with opponent paddle | |
| if (self.ball_x >= WIDTH - 30 - BALL_SIZE//2 and | |
| self.opponent_y <= self.ball_y <= self.opponent_y + PADDLE_HEIGHT): | |
| self.ball_dx = -self.ball_dx | |
| self.ball_dy += np.random.randint(-2, 3) # Add some spin | |
| # Scoring | |
| if self.ball_x < 0: | |
| self.opponent_score += 1 | |
| self.reset_ball() | |
| elif self.ball_x > WIDTH: | |
| self.player_score += 1 | |
| self.reset_ball() | |
| # Check game over (first to 5 points) | |
| if self.player_score >= 5 or self.opponent_score >= 5: | |
| self.game_state = "game_over" | |
| def reset_ball(self): | |
| self.ball_x = WIDTH // 2 | |
| self.ball_y = HEIGHT // 2 | |
| self.ball_dx = BALL_SPEED_X * (1 if self.player_score > self.opponent_score else -1) | |
| self.ball_dy = BALL_SPEED_Y | |
| def start_game(self): | |
| if not self.game_running: | |
| self.game_state = "playing" | |
| self.game_running = True | |
| self.game_thread = threading.Thread(target=self.game_loop, daemon=True) | |
| self.game_thread.start() | |
| def reset(self): | |
| self.game_running = False | |
| if self.game_thread: | |
| self.game_thread.join(timeout=0.1) | |
| self.reset_game() | |
| def game_loop(self): | |
| while self.game_running: | |
| self.update_game() | |
| time.sleep(1/FPS) | |
| def handle_controls(self, up, down): | |
| if self.game_state == "playing": | |
| if up: | |
| self.command_queue.put("up") | |
| if down: | |
| self.command_queue.put("down") | |
| return gr.update(), gr.update() | |
| game = PingPongGame() | |
| def game_view(up, down): | |
| game.handle_controls(up, down) | |
| return game.draw_frame() | |
| with gr.Blocks(title="๐ Ping Pong Game", theme=gr.themes.Soft()) as demo: | |
| gr.HTML( | |
| """ | |
| <div style="text-align: center; margin-bottom: 20px;"> | |
| <h1 style="color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">๐ Ping Pong Game</h1> | |
| <p style="color: #ccc;">Use UP/DOWN arrows (or buttons) to control your paddle (left). Beat the AI!</p> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #1f9fff; font-weight: bold;"> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| game_display = gr.Image(label="Game", interactive=False, height=HEIGHT, width=WIDTH) | |
| with gr.Row(): | |
| up_btn = gr.Button("โฌ UP", variant="secondary", size="lg") | |
| down_btn = gr.Button("โฌ DOWN", variant="secondary", size="lg") | |
| with gr.Row(): | |
| start_btn = gr.Button("โถ๏ธ START GAME", variant="primary", size="lg") | |
| reset_btn = gr.Button("๐ RESET", variant="stop", size="lg") | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| ## ๐ฎ How to Play | |
| **Controls:** | |
| - **โฌ UP / โฌ DOWN buttons** - Move your paddle (left side) | |
| - **START** - Begin new game | |
| - **RESET** - Reset game anytime | |
| **Objective:** | |
| - First to **5 points** wins! | |
| - Keep the ball from passing your paddle | |
| **Features:** | |
| - โ Real-time gameplay (60 FPS) | |
| - โ Smart AI opponent | |
| - โ Ball spin & physics | |
| - โ Smooth paddle movement | |
| """) | |
| # Event handlers | |
| up_btn.click(game_view, inputs=[up_btn, down_btn], outputs=game_display, every=0.1) | |
| down_btn.click(game_view, inputs=[up_btn, down_btn], outputs=game_display, every=0.1) | |
| start_btn.click( | |
| lambda: (game.start_game(), game.draw_frame()), | |
| outputs=[start_btn, game_display] | |
| ) | |
| reset_btn.click( | |
| lambda: (game.reset(), game.draw_frame()), | |
| outputs=[start_btn, game_display] | |
| ) | |
| # Auto-update game display during play | |
| demo.load(game_view, inputs=[gr.State(False), gr.State(False)], outputs=game_display, every=1/FPS) | |
| if __name__ == "__main__": | |
| demo.launch() | |