protoncluster commited on
Commit
dbd1e72
ยท
verified ยท
1 Parent(s): 7161fbc

Deploy from anycoder

Browse files
Files changed (3) hide show
  1. app.py +231 -0
  2. requirements.txt +1 -0
  3. utils.py +3 -0
app.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import time
4
+ import threading
5
+ from PIL import Image, ImageDraw
6
+ import queue
7
+
8
+ # Game constants
9
+ WIDTH, HEIGHT = 800, 600
10
+ PADDLE_WIDTH, PADDLE_HEIGHT = 20, 100
11
+ BALL_SIZE = 15
12
+ PADDLE_SPEED = 8
13
+ BALL_SPEED_X, BALL_SPEED_Y = 8, 6
14
+ FPS = 60
15
+
16
+ class PingPongGame:
17
+ def __init__(self):
18
+ self.reset_game()
19
+ self.game_running = False
20
+ self.game_thread = None
21
+ self.command_queue = queue.Queue()
22
+
23
+ def reset_game(self):
24
+ # Player paddle (left)
25
+ self.player_y = HEIGHT // 2 - PADDLE_HEIGHT // 2
26
+ self.player_score = 0
27
+
28
+ # Opponent paddle (right)
29
+ self.opponent_y = HEIGHT // 2 - PADDLE_HEIGHT // 2
30
+ self.opponent_score = 0
31
+
32
+ # Ball
33
+ self.ball_x = WIDTH // 2
34
+ self.ball_y = HEIGHT // 2
35
+ self.ball_dx = BALL_SPEED_X
36
+ self.ball_dy = BALL_SPEED_Y
37
+
38
+ self.game_state = "menu" # menu, playing, game_over
39
+
40
+ def draw_frame(self):
41
+ # Create blank image
42
+ img = Image.new('RGB', (WIDTH, HEIGHT), 'black')
43
+ draw = ImageDraw.Draw(img)
44
+
45
+ # Draw center line
46
+ draw.line([(WIDTH//2, 0), (WIDTH//2, HEIGHT)], fill='white', width=4)
47
+
48
+ if self.game_state == "menu":
49
+ # Menu text
50
+ font = ImageDraw.ImageFont.load_default()
51
+ draw.text((WIDTH//2 - 100, HEIGHT//2 - 60), "PING PONG", fill='white', font=font)
52
+ draw.text((WIDTH//2 - 120, HEIGHT//2), "Press START", fill='white', font=font)
53
+ draw.text((WIDTH//2 - 80, HEIGHT//2 + 40), f"Use UP/DOWN to move", fill='white', font=font)
54
+
55
+ elif self.game_state == "playing" or self.game_state == "game_over":
56
+ # Draw paddles
57
+ draw.rectangle([10, self.player_y, 10 + PADDLE_WIDTH, self.player_y + PADDLE_HEIGHT],
58
+ fill='white')
59
+ draw.rectangle([WIDTH - 30, self.opponent_y, WIDTH - 10, self.opponent_y + PADDLE_HEIGHT],
60
+ fill='white')
61
+
62
+ # Draw ball
63
+ draw.ellipse([self.ball_x - BALL_SIZE//2, self.ball_y - BALL_SIZE//2,
64
+ self.ball_x + BALL_SIZE//2, self.ball_y + BALL_SIZE//2], fill='white')
65
+
66
+ # Draw scores
67
+ font = ImageDraw.ImageFont.load_default()
68
+ draw.text((WIDTH//4, 30), str(self.player_score), fill='white', font=font)
69
+ draw.text((3*WIDTH//4, 30), str(self.opponent_score), fill='white', font=font)
70
+
71
+ if self.game_state == "game_over":
72
+ draw.text((WIDTH//2 - 80, HEIGHT//2), "GAME OVER", fill='red', font=font)
73
+ draw.text((WIDTH//2 - 100, HEIGHT//2 + 40), "Press RESET", fill='white', font=font)
74
+
75
+ return np.array(img)
76
+
77
+ def update_game(self):
78
+ if self.game_state != "playing":
79
+ return
80
+
81
+ # Move player paddle (from commands)
82
+ try:
83
+ while True:
84
+ cmd = self.command_queue.get_nowait()
85
+ if cmd == "up":
86
+ self.player_y = max(0, self.player_y - PADDLE_SPEED)
87
+ elif cmd == "down":
88
+ self.player_y = min(HEIGHT - PADDLE_HEIGHT, self.player_y + PADDLE_SPEED)
89
+ except queue.Empty:
90
+ pass
91
+
92
+ # AI opponent (simple tracking)
93
+ if self.opponent_y + PADDLE_HEIGHT // 2 < self.ball_y:
94
+ self.opponent_y = min(HEIGHT - PADDLE_HEIGHT, self.opponent_y + PADDLE_SPEED // 2)
95
+ elif self.opponent_y + PADDLE_HEIGHT // 2 > self.ball_y:
96
+ self.opponent_y = max(0, self.opponent_y - PADDLE_SPEED // 2)
97
+
98
+ # Move ball
99
+ self.ball_x += self.ball_dx
100
+ self.ball_y += self.ball_dy
101
+
102
+ # Ball collision with top/bottom
103
+ if self.ball_y <= BALL_SIZE//2 or self.ball_y >= HEIGHT - BALL_SIZE//2:
104
+ self.ball_dy = -self.ball_dy
105
+
106
+ # Ball collision with player paddle
107
+ if (self.ball_x <= 10 + PADDLE_WIDTH + BALL_SIZE//2 and
108
+ self.player_y <= self.ball_y <= self.player_y + PADDLE_HEIGHT):
109
+ self.ball_dx = -self.ball_dx
110
+ self.ball_dy += np.random.randint(-2, 3) # Add some spin
111
+
112
+ # Ball collision with opponent paddle
113
+ if (self.ball_x >= WIDTH - 30 - BALL_SIZE//2 and
114
+ self.opponent_y <= self.ball_y <= self.opponent_y + PADDLE_HEIGHT):
115
+ self.ball_dx = -self.ball_dx
116
+ self.ball_dy += np.random.randint(-2, 3) # Add some spin
117
+
118
+ # Scoring
119
+ if self.ball_x < 0:
120
+ self.opponent_score += 1
121
+ self.reset_ball()
122
+ elif self.ball_x > WIDTH:
123
+ self.player_score += 1
124
+ self.reset_ball()
125
+
126
+ # Check game over (first to 5 points)
127
+ if self.player_score >= 5 or self.opponent_score >= 5:
128
+ self.game_state = "game_over"
129
+
130
+ def reset_ball(self):
131
+ self.ball_x = WIDTH // 2
132
+ self.ball_y = HEIGHT // 2
133
+ self.ball_dx = BALL_SPEED_X * (1 if self.player_score > self.opponent_score else -1)
134
+ self.ball_dy = BALL_SPEED_Y
135
+
136
+ def start_game(self):
137
+ if not self.game_running:
138
+ self.game_state = "playing"
139
+ self.game_running = True
140
+ self.game_thread = threading.Thread(target=self.game_loop, daemon=True)
141
+ self.game_thread.start()
142
+
143
+ def reset(self):
144
+ self.game_running = False
145
+ if self.game_thread:
146
+ self.game_thread.join(timeout=0.1)
147
+ self.reset_game()
148
+
149
+ def game_loop(self):
150
+ while self.game_running:
151
+ self.update_game()
152
+ time.sleep(1/FPS)
153
+
154
+ def handle_controls(self, up, down):
155
+ if self.game_state == "playing":
156
+ if up:
157
+ self.command_queue.put("up")
158
+ if down:
159
+ self.command_queue.put("down")
160
+ return gr.update(), gr.update()
161
+
162
+ game = PingPongGame()
163
+
164
+ def game_view(up, down):
165
+ game.handle_controls(up, down)
166
+ return game.draw_frame()
167
+
168
+ with gr.Blocks(title="๐Ÿ“ Ping Pong Game", theme=gr.themes.Soft()) as demo:
169
+ gr.HTML(
170
+ """
171
+ <div style="text-align: center; margin-bottom: 20px;">
172
+ <h1 style="color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">๐Ÿ“ Ping Pong Game</h1>
173
+ <p style="color: #ccc;">Use UP/DOWN arrows (or buttons) to control your paddle (left). Beat the AI!</p>
174
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #1f9fff; font-weight: bold;">
175
+ Built with anycoder
176
+ </a>
177
+ </div>
178
+ """
179
+ )
180
+
181
+ with gr.Row():
182
+ with gr.Column(scale=1):
183
+ game_display = gr.Image(label="Game", interactive=False, height=HEIGHT, width=WIDTH)
184
+
185
+ with gr.Row():
186
+ up_btn = gr.Button("โฌ† UP", variant="secondary", size="lg")
187
+ down_btn = gr.Button("โฌ‡ DOWN", variant="secondary", size="lg")
188
+
189
+ with gr.Row():
190
+ start_btn = gr.Button("โ–ถ๏ธ START GAME", variant="primary", size="lg")
191
+ reset_btn = gr.Button("๐Ÿ”„ RESET", variant="stop", size="lg")
192
+
193
+ with gr.Column(scale=1):
194
+ gr.Markdown("""
195
+ ## ๐ŸŽฎ How to Play
196
+
197
+ **Controls:**
198
+ - **โฌ† UP / โฌ‡ DOWN buttons** - Move your paddle (left side)
199
+ - **START** - Begin new game
200
+ - **RESET** - Reset game anytime
201
+
202
+ **Objective:**
203
+ - First to **5 points** wins!
204
+ - Keep the ball from passing your paddle
205
+
206
+ **Features:**
207
+ - โœ… Real-time gameplay (60 FPS)
208
+ - โœ… Smart AI opponent
209
+ - โœ… Ball spin & physics
210
+ - โœ… Smooth paddle movement
211
+ """)
212
+
213
+ # Event handlers
214
+ up_btn.click(game_view, inputs=[up_btn, down_btn], outputs=game_display, every=0.1)
215
+ down_btn.click(game_view, inputs=[up_btn, down_btn], outputs=game_display, every=0.1)
216
+
217
+ start_btn.click(
218
+ lambda: (game.start_game(), game.draw_frame()),
219
+ outputs=[start_btn, game_display]
220
+ )
221
+
222
+ reset_btn.click(
223
+ lambda: (game.reset(), game.draw_frame()),
224
+ outputs=[start_btn, game_display]
225
+ )
226
+
227
+ # Auto-update game display during play
228
+ demo.load(game_view, inputs=[gr.State(False), gr.State(False)], outputs=game_display, every=1/FPS)
229
+
230
+ if __name__ == "__main__":
231
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio>=4.0.0
utils.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # No additional utility functions needed for this application
2
+ # All game logic is contained within app.py for simplicity
3
+ pass