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( """
Use UP/DOWN arrows (or buttons) to control your paddle (left). Beat the AI!
Built with anycoder