sagar007 commited on
Commit
1135c55
·
verified ·
1 Parent(s): 3ddf78f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +517 -172
app.py CHANGED
@@ -1,209 +1,554 @@
1
  import gradio as gr
2
- import random
3
  import numpy as np
4
- from PIL import Image, ImageDraw, ImageFont
5
- import os
 
 
 
6
 
7
- class SnakeAndLadderGame:
8
  def __init__(self):
9
- # Board size
10
- self.board_size = 10
11
- self.total_cells = self.board_size * self.board_size
12
-
13
- # Initialize snakes and ladders
14
- self.snakes = {
15
- 16: 6,
16
- 47: 26,
17
- 49: 11,
18
- 56: 53,
19
- 62: 19,
20
- 64: 60,
21
- 87: 24,
22
- 93: 73,
23
- 95: 75,
24
- 98: 78
25
- }
26
 
27
- self.ladders = {
28
- 1: 38,
29
- 4: 14,
30
- 9: 31,
31
- 21: 42,
32
- 28: 84,
33
- 36: 44,
34
- 51: 67,
35
- 71: 91,
36
- 80: 100
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
 
39
- # Player positions
40
- self.player_positions = [0, 0] # Two players starting at position 0
41
- self.current_player = 0
42
- self.game_over = False
43
- self.winner = None
44
- self.last_move = ""
45
 
46
- def roll_dice(self):
47
- return random.randint(1, 6)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- def move_player(self, player_idx, steps):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  if self.game_over:
51
- return f"Game over! Player {self.winner + 1} has won!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- old_position = self.player_positions[player_idx]
54
- new_position = old_position + steps
 
 
 
55
 
56
- # Check if the player won
57
- if new_position >= 100:
58
- self.player_positions[player_idx] = 100
59
- self.game_over = True
60
- self.winner = player_idx
61
- return f"Player {player_idx + 1} has won the game!"
62
 
63
- # Check if the player landed on a snake
64
- elif new_position in self.snakes:
65
- self.player_positions[player_idx] = self.snakes[new_position]
66
- return f"Player {player_idx + 1} got bitten by a snake and moved from {new_position} to {self.snakes[new_position]}"
67
 
68
- # Check if the player landed on a ladder
69
- elif new_position in self.ladders:
70
- self.player_positions[player_idx] = self.ladders[new_position]
71
- return f"Player {player_idx + 1} climbed a ladder and moved from {new_position} to {self.ladders[new_position]}"
 
 
 
 
 
72
 
73
- else:
74
- self.player_positions[player_idx] = new_position
75
- return f"Player {player_idx + 1} moved from {old_position} to {new_position}"
76
 
77
- def play_turn(self):
 
 
 
 
 
 
78
  if self.game_over:
79
- return self.draw_board(), f"Game over! Player {self.winner + 1} has won!"
 
80
 
81
- dice_roll = self.roll_dice()
82
- move_message = self.move_player(self.current_player, dice_roll)
 
 
 
83
 
84
- # Switch to the next player
85
- self.current_player = (self.current_player + 1) % 2
 
 
 
 
 
 
86
 
87
- status = f"Dice: {dice_roll}. {move_message}"
88
- if not self.game_over:
89
- status += f"\nPlayer {self.current_player + 1}'s turn next."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- return self.draw_board(), status
92
 
93
- def draw_board(self):
94
- # Create a new image
95
- cell_size = 60
96
- board_width = self.board_size * cell_size
97
- board_height = self.board_size * cell_size
98
- padding = 20
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- img = Image.new('RGB', (board_width + 2*padding, board_height + 2*padding), color=(255, 255, 255))
101
- draw = ImageDraw.Draw(img)
 
 
 
 
 
102
 
103
- try:
104
- font = ImageFont.truetype("arial.ttf", 16)
105
- except IOError:
106
- font = ImageFont.load_default()
107
-
108
- # Draw board grid
109
- for row in range(self.board_size):
110
- for col in range(self.board_size):
111
- # Determine cell number based on row (0-9 from bottom to top)
112
- if row % 2 == 0: # Even rows (0, 2, 4, 6, 8) go left to right
113
- cell_num = (self.board_size - 1 - row) * self.board_size + col + 1
114
- else: # Odd rows (1, 3, 5, 7, 9) go right to left
115
- cell_num = (self.board_size - 1 - row) * self.board_size + (self.board_size - col)
116
-
117
- # Cell position
118
- x0 = col * cell_size + padding
119
- y0 = (self.board_size - 1 - row) * cell_size + padding
120
- x1 = x0 + cell_size
121
- y1 = y0 + cell_size
122
-
123
- # Alternate cell colors for better visualization
124
- cell_color = (220, 220, 220) if (row + col) % 2 == 0 else (240, 240, 240)
125
-
126
- # Highlight snake cells
127
- if cell_num in self.snakes:
128
- cell_color = (255, 200, 200) # Light red for snakes
129
-
130
- # Highlight ladder cells
131
- if cell_num in self.ladders:
132
- cell_color = (200, 255, 200) # Light green for ladders
133
-
134
- draw.rectangle([x0, y0, x1, y1], fill=cell_color, outline=(0, 0, 0))
135
-
136
- # Draw cell number
137
- draw.text((x0 + 5, y0 + 5), str(cell_num), fill=(0, 0, 0), font=font)
138
-
139
- # Mark snakes
140
- if cell_num in self.snakes:
141
- draw.text((x0 + 5, y0 + 25), f"S→{self.snakes[cell_num]}", fill=(255, 0, 0), font=font)
142
-
143
- # Mark ladders
144
- if cell_num in self.ladders:
145
- draw.text((x0 + 5, y0 + 25), f"L→{self.ladders[cell_num]}", fill=(0, 128, 0), font=font)
146
-
147
- # Draw player positions
148
- player_colors = [(0, 0, 255), (255, 0, 0)] # Blue for Player 1, Red for Player 2
149
- for idx, pos in enumerate(self.player_positions):
150
- if pos > 0:
151
- # Find the cell position for the player
152
- row = (self.board_size - 1) - ((pos - 1) // self.board_size)
153
- if row % 2 == 0: # Even rows go left to right
154
- col = (pos - 1) % self.board_size
155
- else: # Odd rows go right to left
156
- col = self.board_size - 1 - ((pos - 1) % self.board_size)
157
-
158
- # Draw player token
159
- player_x = col * cell_size + padding + (cell_size // 2) + (idx * 10 - 5)
160
- player_y = (self.board_size - 1 - row) * cell_size + padding + (cell_size // 2) + 10
161
-
162
- draw.ellipse([player_x - 10, player_y - 10, player_x + 10, player_y + 10],
163
- fill=player_colors[idx], outline=(0, 0, 0))
164
- draw.text((player_x - 4, player_y - 8), str(idx + 1), fill=(255, 255, 255), font=font)
165
 
166
- return img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- def reset_game():
169
- global game
170
- game = SnakeAndLadderGame()
171
- return game.draw_board(), "Game reset. Player 1's turn to roll the dice."
172
 
173
- def take_turn():
174
- global game
175
- return game.play_turn()
176
 
177
- # Initialize game
178
- game = SnakeAndLadderGame()
179
 
180
- # Create Gradio interface
181
- with gr.Blocks(title="Snake and Ladder Game") as demo:
182
- gr.Markdown("# Snake and Ladder Game")
183
- gr.Markdown("Roll the dice and make your way to 100 while avoiding snakes and climbing ladders!")
 
 
 
 
 
 
184
 
185
  with gr.Row():
186
- with gr.Column(scale=2):
187
- board_display = gr.Image(game.draw_board(), label="Game Board")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- with gr.Column(scale=1):
190
- status_text = gr.Textbox(value="Welcome to Snake and Ladder Game! Player 1's turn to roll the dice.",
191
- label="Game Status", lines=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
- roll_button = gr.Button("Roll Dice")
194
- reset_button = gr.Button("Reset Game")
195
-
196
- gr.Markdown("### Game Rules:")
197
- gr.Markdown("""
198
- 1. Two players take turns rolling a dice.
199
- 2. Move your token according to the dice roll.
200
- 3. If you land on a snake's head, you'll slide down to its tail.
201
- 4. If you land on the bottom of a ladder, you'll climb up to the top.
202
- 5. The first player to reach or exceed position 100 wins!
203
- """)
 
 
 
 
 
 
 
204
 
205
- roll_button.click(take_turn, inputs=[], outputs=[board_display, status_text])
206
- reset_button.click(reset_game, inputs=[], outputs=[board_display, status_text])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  # Launch the app
209
  if __name__ == "__main__":
 
1
  import gradio as gr
 
2
  import numpy as np
3
+ import time
4
+ from PIL import Image, ImageDraw
5
+ import random
6
+ import json
7
+ import base64
8
 
9
+ class SpaceShooterGame:
10
  def __init__(self):
11
+ # Canvas dimensions
12
+ self.width = 480
13
+ self.height = 640
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ # Game state
16
+ self.score = 0
17
+ self.lives = 3
18
+ self.game_over = False
19
+ self.game_started = False
20
+ self.last_update = time.time()
21
+
22
+ # Player
23
+ self.player = {
24
+ 'x': self.width / 2,
25
+ 'y': self.height - 60,
26
+ 'width': 40,
27
+ 'height': 40,
28
+ 'speed': 5,
29
+ 'color': '#3498db',
30
+ 'is_moving_left': False,
31
+ 'is_moving_right': False,
32
+ 'is_moving_up': False,
33
+ 'is_moving_down': False,
34
+ 'is_shooting': False,
35
+ 'last_shot': 0,
36
+ 'shoot_cooldown': 300 # milliseconds
37
  }
38
 
39
+ # Game elements
40
+ self.enemies = []
41
+ self.bullets = []
42
+ self.stars = []
43
+ self.particles = []
 
44
 
45
+ # Timers
46
+ self.last_enemy_spawn = 0
47
+ self.enemy_spawn_rate = 1500 # milliseconds
48
+ self.last_star_spawn = 0
49
+ self.star_spawn_rate = 200 # milliseconds
50
+
51
+ # Initialize stars
52
+ self.init_stars()
53
+
54
+ def init_stars(self):
55
+ for _ in range(50):
56
+ self.stars.append({
57
+ 'x': random.random() * self.width,
58
+ 'y': random.random() * self.height,
59
+ 'size': random.random() * 2 + 1,
60
+ 'speed': random.random() * 2 + 1
61
+ })
62
+
63
+ def update_stars(self, timestamp):
64
+ # Move existing stars
65
+ for i in range(len(self.stars) - 1, -1, -1):
66
+ self.stars[i]['y'] += self.stars[i]['speed']
67
+
68
+ # Remove stars that go off screen
69
+ if self.stars[i]['y'] > self.height:
70
+ self.stars.pop(i)
71
+
72
+ # Add new stars occasionally
73
+ if timestamp - self.last_star_spawn > self.star_spawn_rate:
74
+ self.stars.append({
75
+ 'x': random.random() * self.width,
76
+ 'y': 0,
77
+ 'size': random.random() * 2 + 1,
78
+ 'speed': random.random() * 2 + 1
79
+ })
80
+ self.last_star_spawn = timestamp
81
+
82
+ def shoot(self, timestamp):
83
+ if self.player['is_shooting'] and timestamp - self.player['last_shot'] > self.player['shoot_cooldown']:
84
+ self.bullets.append({
85
+ 'x': self.player['x'],
86
+ 'y': self.player['y'] - self.player['height'] / 2,
87
+ 'width': 4,
88
+ 'height': 15,
89
+ 'speed': 10,
90
+ 'color': '#ffff00'
91
+ })
92
+ self.player['last_shot'] = timestamp
93
 
94
+ def update_bullets(self):
95
+ for i in range(len(self.bullets) - 1, -1, -1):
96
+ self.bullets[i]['y'] -= self.bullets[i]['speed']
97
+
98
+ # Remove bullets that go off screen
99
+ if self.bullets[i]['y'] < 0:
100
+ self.bullets.pop(i)
101
+
102
+ def spawn_enemies(self, timestamp):
103
+ if timestamp - self.last_enemy_spawn > self.enemy_spawn_rate:
104
+ size = random.random() * 20 + 20
105
+ self.enemies.append({
106
+ 'x': random.random() * (self.width - size) + size / 2,
107
+ 'y': 0,
108
+ 'width': size,
109
+ 'height': size,
110
+ 'speed': random.random() * 2 + 1,
111
+ 'color': f'hsl({random.random() * 360}, 100%, 50%)'
112
+ })
113
+ self.last_enemy_spawn = timestamp
114
+
115
+ # Increase difficulty over time
116
+ if self.enemy_spawn_rate > 500:
117
+ self.enemy_spawn_rate -= 10
118
+
119
+ def update_enemies(self):
120
+ for i in range(len(self.enemies) - 1, -1, -1):
121
+ self.enemies[i]['y'] += self.enemies[i]['speed']
122
+
123
+ # Game over if enemy reaches bottom
124
+ if self.enemies[i]['y'] > self.height:
125
+ self.enemies.pop(i)
126
+ self.lives -= 1
127
+
128
+ if self.lives <= 0:
129
+ self.game_over = True
130
+
131
+ def create_explosion(self, x, y, color):
132
+ particle_count = 15
133
+ for _ in range(particle_count):
134
+ angle = random.random() * 3.14159 * 2
135
+ speed = random.random() * 3 + 1
136
+ self.particles.append({
137
+ 'x': x,
138
+ 'y': y,
139
+ 'vx': np.cos(angle) * speed,
140
+ 'vy': np.sin(angle) * speed,
141
+ 'radius': random.random() * 3 + 1,
142
+ 'color': color,
143
+ 'life': 30 # frames
144
+ })
145
+
146
+ def update_particles(self):
147
+ for i in range(len(self.particles) - 1, -1, -1):
148
+ self.particles[i]['x'] += self.particles[i]['vx']
149
+ self.particles[i]['y'] += self.particles[i]['vy']
150
+ self.particles[i]['life'] -= 1
151
+
152
+ if self.particles[i]['life'] <= 0:
153
+ self.particles.pop(i)
154
+
155
+ def check_collisions(self):
156
+ # Check bullet-enemy collisions
157
+ for i in range(len(self.bullets) - 1, -1, -1):
158
+ bullet_removed = False
159
+ for j in range(len(self.enemies) - 1, -1, -1):
160
+ if (
161
+ self.bullets[i]['x'] < self.enemies[j]['x'] + self.enemies[j]['width'] / 2 and
162
+ self.bullets[i]['x'] + self.bullets[i]['width'] > self.enemies[j]['x'] - self.enemies[j]['width'] / 2 and
163
+ self.bullets[i]['y'] < self.enemies[j]['y'] + self.enemies[j]['height'] / 2 and
164
+ self.bullets[i]['y'] + self.bullets[i]['height'] > self.enemies[j]['y'] - self.enemies[j]['height'] / 2
165
+ ):
166
+ # Collision detected
167
+ self.create_explosion(self.enemies[j]['x'], self.enemies[j]['y'], self.enemies[j]['color'])
168
+ self.score += int(self.enemies[j]['width'])
169
+
170
+ # Remove the bullet and enemy
171
+ if not bullet_removed:
172
+ self.bullets.pop(i)
173
+ bullet_removed = True
174
+ self.enemies.pop(j)
175
+ break
176
+
177
+ if bullet_removed:
178
+ break
179
+
180
+ # Check player-enemy collisions
181
+ for i in range(len(self.enemies) - 1, -1, -1):
182
+ dx = self.player['x'] - self.enemies[i]['x']
183
+ dy = self.player['y'] - self.enemies[i]['y']
184
+ distance = np.sqrt(dx * dx + dy * dy)
185
+
186
+ if distance < (self.player['width'] / 2 + self.enemies[i]['width'] / 2):
187
+ # Collision detected
188
+ self.create_explosion(self.player['x'], self.player['y'], self.player['color'])
189
+ self.create_explosion(self.enemies[i]['x'], self.enemies[i]['y'], self.enemies[i]['color'])
190
+
191
+ # Remove the enemy
192
+ self.enemies.pop(i)
193
+
194
+ # Decrease player lives
195
+ self.lives -= 1
196
+
197
+ if self.lives <= 0:
198
+ self.game_over = True
199
+ break
200
+
201
+ def update_player(self):
202
+ # Update player position based on current movement flags
203
+ if self.player['is_moving_left'] and self.player['x'] > self.player['width'] / 2:
204
+ self.player['x'] -= self.player['speed']
205
+ if self.player['is_moving_right'] and self.player['x'] < self.width - self.player['width'] / 2:
206
+ self.player['x'] += self.player['speed']
207
+ if self.player['is_moving_up'] and self.player['y'] > self.player['height']:
208
+ self.player['y'] -= self.player['speed']
209
+ if self.player['is_moving_down'] and self.player['y'] < self.height - self.player['height'] / 2:
210
+ self.player['y'] += self.player['speed']
211
+
212
+ def render_frame(self):
213
+ # Create image
214
+ img = Image.new('RGB', (self.width, self.height), (17, 17, 17))
215
+ draw = ImageDraw.Draw(img)
216
+
217
+ # Draw stars
218
+ for star in self.stars:
219
+ draw.ellipse(
220
+ [star['x'] - star['size'], star['y'] - star['size'],
221
+ star['x'] + star['size'], star['y'] + star['size']],
222
+ fill='white'
223
+ )
224
+
225
+ # Draw enemies
226
+ for enemy in self.enemies:
227
+ # Draw enemy as a circle
228
+ draw.ellipse(
229
+ [enemy['x'] - enemy['width'] / 2, enemy['y'] - enemy['height'] / 2,
230
+ enemy['x'] + enemy['width'] / 2, enemy['y'] + enemy['height'] / 2],
231
+ fill=enemy['color']
232
+ )
233
+
234
+ # Add details to the enemy
235
+ draw.ellipse(
236
+ [enemy['x'] - enemy['width'] / 3, enemy['y'] - enemy['height'] / 3,
237
+ enemy['x'] + enemy['width'] / 3, enemy['y'] + enemy['height'] / 3],
238
+ outline='white'
239
+ )
240
+
241
+ # Draw bullets
242
+ for bullet in self.bullets:
243
+ draw.rectangle(
244
+ [bullet['x'] - bullet['width'] / 2, bullet['y'],
245
+ bullet['x'] + bullet['width'] / 2, bullet['y'] + bullet['height']],
246
+ fill=bullet['color']
247
+ )
248
+
249
+ # Draw player ship
250
+ if not self.game_over:
251
+ # Ship body (triangle)
252
+ draw.polygon(
253
+ [
254
+ (self.player['x'], self.player['y'] - self.player['height'] / 2),
255
+ (self.player['x'] - self.player['width'] / 2, self.player['y'] + self.player['height'] / 2),
256
+ (self.player['x'] + self.player['width'] / 2, self.player['y'] + self.player['height'] / 2)
257
+ ],
258
+ fill=self.player['color']
259
+ )
260
+
261
+ # Engine glow
262
+ draw.polygon(
263
+ [
264
+ (self.player['x'] - self.player['width'] / 4, self.player['y'] + self.player['height'] / 2),
265
+ (self.player['x'], self.player['y'] + self.player['height'] / 2 + 10),
266
+ (self.player['x'] + self.player['width'] / 4, self.player['y'] + self.player['height'] / 2)
267
+ ],
268
+ fill='#ff9900'
269
+ )
270
+
271
+ # Draw particles
272
+ for particle in self.particles:
273
+ # Calculate alpha based on life
274
+ alpha = int(255 * (particle['life'] / 30))
275
+ color = self.hex_to_rgb(particle['color'])
276
+ particle_color = (color[0], color[1], color[2], alpha)
277
+
278
+ draw.ellipse(
279
+ [particle['x'] - particle['radius'], particle['y'] - particle['radius'],
280
+ particle['x'] + particle['radius'], particle['y'] + particle['radius']],
281
+ fill=particle['color']
282
+ )
283
+
284
+ # Draw UI
285
+ draw.text((10, 10), f"Score: {self.score}", fill='white')
286
+ draw.text((self.width - 70, 10), f"Lives: {self.lives}", fill='white')
287
+
288
+ # Draw game over screen
289
  if self.game_over:
290
+ # Semi-transparent background
291
+ overlay = Image.new('RGBA', (self.width, self.height), (0, 0, 0, 180))
292
+ img = Image.alpha_composite(img.convert('RGBA'), overlay)
293
+ draw = ImageDraw.Draw(img)
294
+
295
+ # Game over text
296
+ draw.text((self.width // 2 - 40, self.height // 2 - 30), "GAME OVER", fill='red')
297
+ draw.text((self.width // 2 - 50, self.height // 2), f"Final Score: {self.score}", fill='white')
298
+ draw.text((self.width // 2 - 65, self.height // 2 + 30), "Click to play again", fill='white')
299
+
300
+ # Draw start screen
301
+ if not self.game_started and not self.game_over:
302
+ # Semi-transparent background
303
+ overlay = Image.new('RGBA', (self.width, self.height), (0, 0, 0, 180))
304
+ img = Image.alpha_composite(img.convert('RGBA'), overlay)
305
+ draw = ImageDraw.Draw(img)
306
+
307
+ # Start game text
308
+ draw.text((self.width // 2 - 60, self.height // 2 - 50), "SPACE SHOOTER", fill='#ff5555')
309
+ draw.text((self.width // 2 - 90, self.height // 2 - 20), "Use arrow keys to move", fill='white')
310
+ draw.text((self.width // 2 - 85, self.height // 2), "Click mouse to shoot", fill='white')
311
+ draw.text((self.width // 2 - 55, self.height // 2 + 30), "Click to start", fill='white')
312
 
313
+ return img
314
+
315
+ def update(self):
316
+ if not self.game_started or self.game_over:
317
+ return self.render_frame()
318
 
319
+ current_time = time.time() * 1000 # Convert to milliseconds
 
 
 
 
 
320
 
321
+ # Calculate delta time
322
+ delta_time = current_time - self.last_update
323
+ self.last_update = current_time
 
324
 
325
+ # Update game state
326
+ self.update_stars(current_time)
327
+ self.spawn_enemies(current_time)
328
+ self.update_enemies()
329
+ self.update_player()
330
+ self.shoot(current_time)
331
+ self.update_bullets()
332
+ self.update_particles()
333
+ self.check_collisions()
334
 
335
+ return self.render_frame()
 
 
336
 
337
+ def handle_click(self, evt: gr.SelectData):
338
+ # Start game if not started
339
+ if not self.game_started:
340
+ self.game_started = True
341
+ return self.update()
342
+
343
+ # Restart game if game over
344
  if self.game_over:
345
+ self.reset_game()
346
+ return self.update()
347
 
348
+ # Handle shooting on left click
349
+ if evt.index == 1: # Left click
350
+ self.player['is_shooting'] = True
351
+ self.shoot(time.time() * 1000)
352
+ self.player['is_shooting'] = False
353
 
354
+ return self.update()
355
+
356
+ def handle_keypress(self, key):
357
+ if not self.game_started or self.game_over:
358
+ self.game_started = True
359
+ if self.game_over:
360
+ self.reset_game()
361
+ return self.update()
362
 
363
+ # Handle arrow key movement
364
+ if key == "ArrowLeft":
365
+ self.player['is_moving_left'] = True
366
+ elif key == "ArrowRight":
367
+ self.player['is_moving_right'] = True
368
+ elif key == "ArrowUp":
369
+ self.player['is_moving_up'] = True
370
+ elif key == "ArrowDown":
371
+ self.player['is_moving_down'] = True
372
+ elif key == "a":
373
+ self.player['is_moving_left'] = True
374
+ elif key == "d":
375
+ self.player['is_moving_right'] = True
376
+ elif key == "w":
377
+ self.player['is_moving_up'] = True
378
+ elif key == "s":
379
+ self.player['is_moving_down'] = True
380
+ elif key == " ":
381
+ self.player['is_shooting'] = True
382
 
383
+ return self.update()
384
 
385
+ def handle_keyrelease(self, key):
386
+ if key == "ArrowLeft":
387
+ self.player['is_moving_left'] = False
388
+ elif key == "ArrowRight":
389
+ self.player['is_moving_right'] = False
390
+ elif key == "ArrowUp":
391
+ self.player['is_moving_up'] = False
392
+ elif key == "ArrowDown":
393
+ self.player['is_moving_down'] = False
394
+ elif key == "a":
395
+ self.player['is_moving_left'] = False
396
+ elif key == "d":
397
+ self.player['is_moving_right'] = False
398
+ elif key == "w":
399
+ self.player['is_moving_up'] = False
400
+ elif key == "s":
401
+ self.player['is_moving_down'] = False
402
+ elif key == " ":
403
+ self.player['is_shooting'] = False
404
 
405
+ return self.update()
406
+
407
+ def handle_mouse_move(self, evt: gr.SelectData):
408
+ # Update player position based on mouse position if game is running
409
+ if self.game_started and not self.game_over:
410
+ x_coordinates = evt.index[0] if isinstance(evt.index, tuple) else evt.index
411
+ self.player['x'] = max(self.player['width'] / 2, min(x_coordinates, self.width - self.player['width'] / 2))
412
 
413
+ return self.update()
414
+
415
+ def reset_game(self):
416
+ # Reset game state
417
+ self.score = 0
418
+ self.lives = 3
419
+ self.game_over = False
420
+ self.game_started = True
421
+ self.enemies = []
422
+ self.bullets = []
423
+ self.particles = []
424
+ self.last_enemy_spawn = 0
425
+ self.enemy_spawn_rate = 1500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
+ # Reset player position
428
+ self.player['x'] = self.width / 2
429
+ self.player['y'] = self.height - 60
430
+
431
+ self.last_update = time.time() * 1000
432
+
433
+ def hex_to_rgb(self, hex_color):
434
+ hex_color = hex_color.lstrip('#')
435
+ if len(hex_color) == 6:
436
+ return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
437
+ else:
438
+ # Handle HSL colors by converting to a default color
439
+ return (255, 0, 0) # Return red by default
440
+
441
+ # Create game instance
442
+ game = SpaceShooterGame()
443
+
444
+ def update_frame():
445
+ return game.update()
446
 
447
+ def handle_click(img, evt: gr.SelectData):
448
+ return game.handle_click(evt)
 
 
449
 
450
+ def handle_keypress(key):
451
+ return game.handle_keypress(key)
 
452
 
453
+ def handle_keyrelease(key):
454
+ return game.handle_keyrelease(key)
455
 
456
+ def handle_mouse_move(img, evt: gr.SelectData):
457
+ return game.handle_mouse_move(evt)
458
+
459
+ # Set up Gradio interface
460
+ with gr.Blocks() as demo:
461
+ gr.Markdown("# Space Shooter Game")
462
+ gr.Markdown("Use arrow keys (or WASD) to move. Left click to shoot.")
463
+
464
+ with gr.Row():
465
+ game_display = gr.Image(game.render_frame(), elem_id="game-canvas")
466
 
467
  with gr.Row():
468
+ gr.Markdown("### Controls:")
469
+ gr.Markdown("- **Arrow Keys or WASD**: Move ship")
470
+ gr.Markdown("- **Left Click**: Shoot")
471
+ gr.Markdown("- **Click on Game**: Start/Restart")
472
+
473
+ # Handle events
474
+ game_display.select(handle_click, [game_display], [game_display])
475
+
476
+ # Game loop using Gradio's JavaScript event system
477
+ demo.load(update_frame, [], [game_display], every=0.1)
478
+
479
+ # Handle keyboard events
480
+ demo.queue()
481
+
482
+ # Handle keyboard inputs via JavaScript events
483
+ js_keyboard = """
484
+ function setupKeyboardHandlers() {
485
+ const gameCanvas = document.getElementById('game-canvas');
486
+ if (!gameCanvas) {
487
+ setTimeout(setupKeyboardHandlers, 100);
488
+ return;
489
+ }
490
 
491
+ document.addEventListener('keydown', function(e) {
492
+ if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'a', 'd', 'w', 's', ' '].includes(e.key)) {
493
+ e.preventDefault();
494
+ keyPressEvent(e.key);
495
+ }
496
+ });
497
+
498
+ document.addEventListener('keyup', function(e) {
499
+ if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'a', 'd', 'w', 's', ' '].includes(e.key)) {
500
+ e.preventDefault();
501
+ keyReleaseEvent(e.key);
502
+ }
503
+ });
504
+
505
+ // Add mousemove handler with debouncing for better performance
506
+ let lastMove = 0;
507
+ gameCanvas.addEventListener('mousemove', function(e) {
508
+ const now = Date.now();
509
+ if (now - lastMove < 50) return; // Only process every 50ms
510
+ lastMove = now;
511
 
512
+ const rect = gameCanvas.getBoundingClientRect();
513
+ const x = e.clientX - rect.left;
514
+ const y = e.clientY - rect.top;
515
+
516
+ // Only update if mouse is over the canvas
517
+ if (x >= 0 && x <= rect.width && y >= 0 && y <= rect.height) {
518
+ // Scale coordinates to match game dimensions
519
+ const scaleX = 480 / rect.width;
520
+ const scaledX = x * scaleX;
521
+
522
+ mouseMove([scaledX, 0]);
523
+ }
524
+ });
525
+ }
526
+
527
+ // Set up the event handlers once the page loads
528
+ setTimeout(setupKeyboardHandlers, 100);
529
+ """
530
 
531
+ demo.load(None, [], [], _js=js_keyboard)
532
+
533
+ # Add events for keyboard handling
534
+ keypress_event = demo.input(fn=handle_keypress, inputs=[], outputs=[game_display])
535
+ keyrelease_event = demo.input(fn=handle_keyrelease, inputs=[], outputs=[game_display])
536
+ mousemove_event = demo.input(fn=handle_mouse_move, inputs=[game_display], outputs=[game_display])
537
+
538
+ # Connect JavaScript events to Python functions
539
+ demo.after_setup(_js=f"""
540
+ function keyPressEvent(key) {{
541
+ {keypress_event.name}(key);
542
+ }}
543
+
544
+ function keyReleaseEvent(key) {{
545
+ {keyrelease_event.name}(key);
546
+ }}
547
+
548
+ function mouseMove(coords) {{
549
+ {mousemove_event.name}(null, coords);
550
+ }}
551
+ """)
552
 
553
  # Launch the app
554
  if __name__ == "__main__":