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( """

🏓 Ping Pong Game

Use UP/DOWN arrows (or buttons) to control your paddle (left). Beat the AI!

Built with anycoder
""" ) 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()