File size: 8,573 Bytes
97c981c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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()