IncreasingLoss commited on
Commit
80b8cfd
·
verified ·
1 Parent(s): eb7210a

Upload 3 files

Browse files
Files changed (2) hide show
  1. app.py +216 -165
  2. car_game_classes_gradio.py +34 -13
app.py CHANGED
@@ -3,6 +3,9 @@ import numpy as np
3
  import pygame
4
  import threading
5
  import queue
 
 
 
6
  from PIL import Image, ImageDraw, ImageFont
7
  from car_game_classes_gradio import Car, RayCasting, MapGenerator
8
  from ppo_agent_gradio import PPOAgent
@@ -10,7 +13,7 @@ from collections import deque
10
 
11
  # Initialize Pygame
12
  pygame.init()
13
- SCREEN_WIDTH, SCREEN_HEIGHT = 1600, 900
14
  screen = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
15
  clock = pygame.time.Clock()
16
 
@@ -18,13 +21,18 @@ clock = pygame.time.Clock()
18
  ACTION_SPACE = 8
19
  state_size = 22
20
  LOADED_MODEL = "model/18.6_car_model_epoch_270_lvl3_final.pth"
21
- SIMULATION_FPS = 120 # Fixed simulation FPS, matching training
22
- DT = 1.0 / SIMULATION_FPS # Fixed time step
23
- DISPLAY_FPS = 24
24
  DIFFICULTY = 2
25
 
26
- # Frame queue for Gradio
27
- frame_queue = queue.Queue(maxsize=1)
 
 
 
 
 
28
 
29
  def point_to_line_distance(point, line_p0, line_p1):
30
  p = pygame.math.Vector2(point)
@@ -43,10 +51,11 @@ def point_to_line_distance(point, line_p0, line_p1):
43
  class TrainingEnvironment:
44
  def __init__(self, action_space=8):
45
  self.action_space = action_space
46
- self.map_generator = MapGenerator(SCREEN_WIDTH, SCREEN_HEIGHT, DIFFICULTY, 18) # Difficulty 2 = lvl 3 in training
47
  car_surf = pygame.image.load("assets/car_transparent.png").convert_alpha()
48
  w, h = car_surf.get_size()
49
- self.car_image = pygame.transform.scale(car_surf, (w//18, h//18))
 
50
  self.start_pos = None
51
  self.start_dir = None
52
  self.previous_speed = 0.0
@@ -61,6 +70,7 @@ class TrainingEnvironment:
61
  self.map_generator.draw(self.background)
62
  self.reset()
63
 
 
64
  def generate_new_map(self):
65
  start_pos, start_dir = self.map_generator.update()
66
  self.start_pos = pygame.math.Vector2(start_pos)
@@ -92,6 +102,38 @@ class TrainingEnvironment:
92
  self.map_generator.draw(self.background)
93
  self.reset_car()
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  def detect_steering_need(self, ray_distances):
96
  norm_distances = [min(d / self.rays.max_distance, 1.0) for d in ray_distances]
97
  left_rays = norm_distances[0:3]
@@ -126,38 +168,6 @@ class TrainingEnvironment:
126
  'center_distance': center_ray
127
  }
128
 
129
- def get_state(self):
130
- self.rays.update(self.car.pos, self.car.direction)
131
- ray_distances = self.rays.get_ray_distances()
132
- normalized_ray_distances = [min(distance / self.rays.max_distance, 1.0) for distance in ray_distances]
133
- steering_info = self.detect_steering_need(ray_distances)
134
- state = [
135
- self.car.velocity / self.car.max_velocity,
136
- self.car.direction.x,
137
- self.car.direction.y,
138
- int(self.car.is_braking),
139
- ]
140
- state.extend(normalized_ray_distances)
141
- state.extend([
142
- float(steering_info['needed']),
143
- steering_info['direction'],
144
- steering_info['urgency'],
145
- steering_info['space_diff'],
146
- steering_info['left_space'],
147
- steering_info['right_space'],
148
- steering_info['center_distance']
149
- ])
150
- total_goals = len(self.map_generator.goals)
151
- progress = self.current_goal_index / max(total_goals, 1)
152
- state.append(progress)
153
- state.append(self.last_action / 4.0)
154
- speed_change = self.car.velocity - self.previous_speed
155
- self.speed_history.append(speed_change)
156
- avg_speed_change = sum(self.speed_history) / max(len(self.speed_history), 1)
157
- state.extend([speed_change / 100.0, avg_speed_change / 100.0])
158
- self.previous_speed = self.car.velocity
159
- return np.array(state, dtype=np.float32)
160
-
161
  def execute_action_dispatch(self, action, dt):
162
  action_int = int(action)
163
  self.last_action = action_int
@@ -261,56 +271,6 @@ class TrainingEnvironment:
261
  reward += -0.5
262
  if hasattr(self, 'speed_consistency_tracker'):
263
  self.speed_consistency_tracker = {'appropriate': False, 'count': 0}
264
- if steering_info['needed']:
265
- steering_urgency = steering_info['urgency']
266
- base_steering_reward = 5.0 + (steering_urgency * 4.0)
267
- correct_action = False
268
- if steering_info['direction'] == -1:
269
- if self.last_action in [1, 3, 6]:
270
- correct_action = True
271
- if self.steering_persistence['direction'] == -1:
272
- self.steering_persistence['count'] += 2
273
- persistence_bonus = min(3.0, self.steering_persistence['count'] * 0.5)
274
- base_steering_reward += persistence_bonus
275
- else:
276
- self.steering_persistence = {'direction': -1, 'count': 1}
277
- if curve_severity > 0.4 and self.last_action == 6:
278
- base_steering_reward += 5.0
279
- elif steering_info['direction'] == 1:
280
- if self.last_action in [2, 4, 7]:
281
- correct_action = True
282
- if self.steering_persistence['direction'] == 1:
283
- self.steering_persistence['count'] += 2
284
- persistence_bonus = min(3.0, self.steering_persistence['count'] * 0.5)
285
- base_steering_reward += persistence_bonus
286
- else:
287
- self.steering_persistence = {'direction': 1, 'count': 1}
288
- if curve_severity > 0.4 and self.last_action == 7:
289
- base_steering_reward += 5.0
290
- if correct_action:
291
- reward += base_steering_reward
292
- else:
293
- self.steering_persistence = {'direction': 0, 'count': 0}
294
- if steering_urgency > 0.6:
295
- penalty = -4.0 * steering_urgency
296
- reward += penalty
297
- else:
298
- self.steering_persistence = {'direction': 0, 'count': 0}
299
- speed_factor = self.car.velocity / self.car.max_velocity
300
- min_side_distance = min([ray_distances[i] for i in [0, 1, 2, 4, 5, 6]]) / self.rays.max_distance
301
- center_distance = ray_distances[3] / self.rays.max_distance
302
- if min_side_distance < 0.01:
303
- danger_penalty = -5.0 * (1.0 - min_side_distance * 12.5) * (0.5 + speed_factor * 0.5)
304
- reward += danger_penalty
305
- elif min_side_distance < 0.0166:
306
- if speed_factor > 0.7:
307
- minor_penalty = -2 * (1.0 - min_side_distance * 6.67)
308
- reward += minor_penalty
309
- if 0.01666 <= min_side_distance and center_distance > 0.2:
310
- safety_efficiency_bonus = 0.3
311
- if 0.3 <= speed_factor <= 0.8:
312
- safety_efficiency_bonus += 0.2
313
- reward += safety_efficiency_bonus
314
  total_goals = len(self.map_generator.goals)
315
  if self.current_goal_index < total_goals:
316
  goal = self.map_generator.goals[self.current_goal_index]
@@ -327,7 +287,45 @@ class TrainingEnvironment:
327
  self.last_steering_reward = reward
328
  return reward, collision, all_goals_completed
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  def game_loop():
 
 
 
331
  try:
332
  pygame.init()
333
  pygame.display.set_mode((1, 1), pygame.NOFRAME)
@@ -336,22 +334,28 @@ def game_loop():
336
  env.reset()
337
  agent = PPOAgent(state_size, ACTION_SPACE)
338
  agent.load(LOADED_MODEL)
 
339
  steps = 0
340
  action_counts = [0] * ACTION_SPACE
341
  clock = pygame.time.Clock()
342
 
343
- # FPS measurement variables
344
- start_time = pygame.time.get_ticks()
345
- frame_count = 0
346
- fps = 0
 
 
347
 
348
- while True:
 
 
 
349
  for event in pygame.event.get():
350
  if event.type == pygame.QUIT:
351
- pygame.quit()
352
  return
353
 
354
- # Game logic
355
  state = env.get_state()
356
  action, _ = agent.select_action(state)
357
  action_int = int(action)
@@ -359,91 +363,138 @@ def game_loop():
359
  env.execute_action_dispatch(action_int, DT)
360
  reward, collision, all_goals_completed = env.calculate_reward()
361
  env.rays.update(env.car.pos, env.car.direction)
 
362
  if collision or all_goals_completed:
363
  env.reset()
364
 
365
  steps += 1
366
- frame_count += 1
367
-
368
- # Calculate FPS every second
369
- current_time = pygame.time.get_ticks()
370
- elapsed = (current_time - start_time) / 1000 # in seconds
371
- if elapsed >= 1:
372
- fps = frame_count / elapsed
373
- frame_count = 0
374
- start_time = current_time
375
-
376
- # Render
377
- screen.blit(env.background, (0, 0)) # Cached map
378
- env.car.draw(screen)
379
- for i, (angle, offset) in enumerate(zip([-60, -30, -15, 0, 15, 30, 60], [14, 22, 27, 26, 27, 22, 14])):
380
- if i < len(env.rays.last_collisions):
381
- ray_dir = env.car.direction.rotate(angle).normalize()
382
- origin = env.car.pos + ray_dir * offset
383
- end_point = env.rays.last_collisions[i]
384
- pygame.draw.line(screen, (255, 255, 0),
385
- (int(origin.x), int(origin.y)),
386
- (int(end_point.x), int(end_point.y)),
387
- width=2)
388
 
389
- # Convert to PIL image and resize for display
390
- raw_str = pygame.image.tostring(screen, 'RGB')
391
- pil_image = Image.frombytes('RGB', (SCREEN_WIDTH, SCREEN_HEIGHT), raw_str)
392
- pil_image = pil_image.resize((int(SCREEN_WIDTH/1), int(SCREEN_HEIGHT/1)))
393
-
394
- # Add FPS text overlay to the image
395
- try:
396
- draw = ImageDraw.Draw(pil_image)
397
- # Try to use a default font, fallback to basic if not available
398
- try:
399
- font = ImageFont.truetype("arial.ttf", 20)
400
- except:
401
- font = ImageFont.load_default()
402
 
403
- fps_text = f"FPS: {fps:.1f}"
404
- draw.text((10, 10), fps_text, fill=(255, 255, 255), font=font)
405
- except Exception as e:
406
- print(f"Warning: Could not add FPS overlay: {e}")
407
-
408
- # Send only the PIL image to Gradio
409
- try:
410
- frame_queue.put(pil_image, block=False)
411
- except queue.Full:
412
- pass
413
 
 
414
  clock.tick(SIMULATION_FPS)
 
415
  except Exception as e:
416
  print(f"Error in game loop: {e}")
 
417
 
418
  def get_frame():
 
419
  try:
420
- return frame_queue.get_nowait()
421
- except queue.Empty:
422
- return Image.new('RGB', (SCREEN_WIDTH//2, SCREEN_HEIGHT//2), (0, 0, 0))
 
 
 
 
 
423
 
424
- css = """#game-image {
425
- height: calc(100vw * 9 / 16) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  object-fit: contain;
427
  margin: 0 auto;
428
- display: block;
429
- }"""
 
 
 
 
 
 
 
430
 
431
- def update_difficulty(input_diff_lvl):
432
- global DIFFICULTY
433
- DIFFICULTY = input_diff_lvl
 
 
 
 
 
 
 
 
 
 
 
 
434
 
435
- def execute_interference():
436
- game_thread = threading.Thread(target=game_loop)
437
- game_thread.daemon = True
438
- game_thread.start()
439
- with gr.Blocks(css=css) as demo:
440
- with gr.Column():
441
- img = gr.Image(type="pil", label="Game Stream", elem_id="game-image", scale=1, min_width=0)
442
- difficulty_slider = gr.Slider(minimum=1, maximum=2, step=1, value=2, label="Difficulty Level")
443
- difficulty_slider.change(fn=update_difficulty, inputs=difficulty_slider)
444
- timer = gr.Timer(value=1/DISPLAY_FPS)
445
- timer.tick(fn=get_frame, inputs=None, outputs=img)
446
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
 
448
  if __name__ == "__main__":
449
- execute_interference()
 
 
 
 
3
  import pygame
4
  import threading
5
  import queue
6
+ import time
7
+ import io
8
+ import base64
9
  from PIL import Image, ImageDraw, ImageFont
10
  from car_game_classes_gradio import Car, RayCasting, MapGenerator
11
  from ppo_agent_gradio import PPOAgent
 
13
 
14
  # Initialize Pygame
15
  pygame.init()
16
+ SCREEN_WIDTH, SCREEN_HEIGHT = 800, 450 # Reduced resolution for better streaming
17
  screen = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
18
  clock = pygame.time.Clock()
19
 
 
21
  ACTION_SPACE = 8
22
  state_size = 22
23
  LOADED_MODEL = "model/18.6_car_model_epoch_270_lvl3_final.pth"
24
+ SIMULATION_FPS = 60 # Reduced from 120
25
+ DT = 1.0 / SIMULATION_FPS
26
+ STREAM_FPS = 20 # Separate streaming FPS
27
  DIFFICULTY = 2
28
 
29
+ # Optimized frame buffer using threading-safe deque
30
+ frame_buffer = deque(maxlen=2) # Small buffer to prevent memory buildup
31
+ buffer_lock = threading.Lock()
32
+
33
+ # Global variables for performance monitoring
34
+ game_running = False
35
+ performance_stats = {}
36
 
37
  def point_to_line_distance(point, line_p0, line_p1):
38
  p = pygame.math.Vector2(point)
 
51
  class TrainingEnvironment:
52
  def __init__(self, action_space=8):
53
  self.action_space = action_space
54
+ self.map_generator = MapGenerator(SCREEN_WIDTH, SCREEN_HEIGHT, DIFFICULTY, 18)
55
  car_surf = pygame.image.load("assets/car_transparent.png").convert_alpha()
56
  w, h = car_surf.get_size()
57
+ # Further scale down car for performance
58
+ self.car_image = pygame.transform.scale(car_surf, (w//24, h//24))
59
  self.start_pos = None
60
  self.start_dir = None
61
  self.previous_speed = 0.0
 
70
  self.map_generator.draw(self.background)
71
  self.reset()
72
 
73
+ # ... (keeping all your existing methods unchanged for brevity)
74
  def generate_new_map(self):
75
  start_pos, start_dir = self.map_generator.update()
76
  self.start_pos = pygame.math.Vector2(start_pos)
 
102
  self.map_generator.draw(self.background)
103
  self.reset_car()
104
 
105
+ def get_state(self):
106
+ self.rays.update(self.car.pos, self.car.direction)
107
+ ray_distances = self.rays.get_ray_distances()
108
+ normalized_ray_distances = [min(distance / self.rays.max_distance, 1.0) for distance in ray_distances]
109
+ steering_info = self.detect_steering_need(ray_distances)
110
+ state = [
111
+ self.car.velocity / self.car.max_velocity,
112
+ self.car.direction.x,
113
+ self.car.direction.y,
114
+ int(self.car.is_braking),
115
+ ]
116
+ state.extend(normalized_ray_distances)
117
+ state.extend([
118
+ float(steering_info['needed']),
119
+ steering_info['direction'],
120
+ steering_info['urgency'],
121
+ steering_info['space_diff'],
122
+ steering_info['left_space'],
123
+ steering_info['right_space'],
124
+ steering_info['center_distance']
125
+ ])
126
+ total_goals = len(self.map_generator.goals)
127
+ progress = self.current_goal_index / max(total_goals, 1)
128
+ state.append(progress)
129
+ state.append(self.last_action / 4.0)
130
+ speed_change = self.car.velocity - self.previous_speed
131
+ self.speed_history.append(speed_change)
132
+ avg_speed_change = sum(self.speed_history) / max(len(self.speed_history), 1)
133
+ state.extend([speed_change / 100.0, avg_speed_change / 100.0])
134
+ self.previous_speed = self.car.velocity
135
+ return np.array(state, dtype=np.float32)
136
+
137
  def detect_steering_need(self, ray_distances):
138
  norm_distances = [min(d / self.rays.max_distance, 1.0) for d in ray_distances]
139
  left_rays = norm_distances[0:3]
 
168
  'center_distance': center_ray
169
  }
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  def execute_action_dispatch(self, action, dt):
172
  action_int = int(action)
173
  self.last_action = action_int
 
271
  reward += -0.5
272
  if hasattr(self, 'speed_consistency_tracker'):
273
  self.speed_consistency_tracker = {'appropriate': False, 'count': 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  total_goals = len(self.map_generator.goals)
275
  if self.current_goal_index < total_goals:
276
  goal = self.map_generator.goals[self.current_goal_index]
 
287
  self.last_steering_reward = reward
288
  return reward, collision, all_goals_completed
289
 
290
+ def create_optimized_frame(screen, env, fps):
291
+ """Create and optimize frame for streaming"""
292
+ # Use a smaller render surface for better performance
293
+ render_surface = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
294
+ render_surface.blit(env.background, (0, 0))
295
+ env.car.draw(render_surface)
296
+
297
+ # Draw only every other ray for better performance
298
+ for i in range(0, len(env.rays.last_collisions), 2): # Skip every other ray
299
+ if i < len([-60, -30, -15, 0, 15, 30, 60]):
300
+ angle = [-60, -30, -15, 0, 15, 30, 60][i]
301
+ offset = [14, 22, 27, 26, 27, 22, 14][i]
302
+ ray_dir = env.car.direction.rotate(angle).normalize()
303
+ origin = env.car.pos + ray_dir * offset
304
+ end_point = env.rays.last_collisions[i]
305
+ pygame.draw.line(render_surface, (255, 255, 0),
306
+ (int(origin.x), int(origin.y)),
307
+ (int(end_point.x), int(end_point.y)),
308
+ width=1) # Thinner lines
309
+
310
+ # Convert to PIL with optimizations
311
+ raw_str = pygame.image.tostring(render_surface, 'RGB')
312
+ pil_image = Image.frombytes('RGB', (SCREEN_WIDTH, SCREEN_HEIGHT), raw_str)
313
+
314
+ # Add FPS overlay more efficiently
315
+ draw = ImageDraw.Draw(pil_image)
316
+ try:
317
+ font = ImageFont.load_default()
318
+ fps_text = f"FPS: {fps:.0f}"
319
+ draw.text((10, 10), fps_text, fill=(255, 255, 255), font=font)
320
+ except:
321
+ pass
322
+
323
+ return pil_image
324
+
325
  def game_loop():
326
+ """Optimized game loop with better frame management"""
327
+ global game_running, performance_stats
328
+
329
  try:
330
  pygame.init()
331
  pygame.display.set_mode((1, 1), pygame.NOFRAME)
 
334
  env.reset()
335
  agent = PPOAgent(state_size, ACTION_SPACE)
336
  agent.load(LOADED_MODEL)
337
+
338
  steps = 0
339
  action_counts = [0] * ACTION_SPACE
340
  clock = pygame.time.Clock()
341
 
342
+ # Performance tracking
343
+ frame_times = deque(maxlen=60)
344
+ last_stream_time = time.time()
345
+ stream_interval = 1.0 / STREAM_FPS
346
+
347
+ game_running = True
348
 
349
+ while game_running:
350
+ frame_start = time.time()
351
+
352
+ # Handle pygame events
353
  for event in pygame.event.get():
354
  if event.type == pygame.QUIT:
355
+ game_running = False
356
  return
357
 
358
+ # Game logic (runs at full simulation FPS)
359
  state = env.get_state()
360
  action, _ = agent.select_action(state)
361
  action_int = int(action)
 
363
  env.execute_action_dispatch(action_int, DT)
364
  reward, collision, all_goals_completed = env.calculate_reward()
365
  env.rays.update(env.car.pos, env.car.direction)
366
+
367
  if collision or all_goals_completed:
368
  env.reset()
369
 
370
  steps += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
+ # Stream frame at reduced rate
373
+ current_time = time.time()
374
+ if current_time - last_stream_time >= stream_interval:
375
+ fps = 1.0 / (current_time - frame_start + 0.001) # Avoid division by zero
376
+ frame_times.append(current_time - frame_start)
377
+ avg_fps = len(frame_times) / sum(frame_times) if frame_times else 0
 
 
 
 
 
 
 
378
 
379
+ # Create and buffer frame
380
+ frame = create_optimized_frame(screen, env, avg_fps)
381
+
382
+ with buffer_lock:
383
+ frame_buffer.append(frame)
384
+
385
+ performance_stats['fps'] = avg_fps
386
+ performance_stats['frame_time'] = sum(frame_times) / len(frame_times) if frame_times else 0
387
+ last_stream_time = current_time
 
388
 
389
+ # Control simulation FPS
390
  clock.tick(SIMULATION_FPS)
391
+
392
  except Exception as e:
393
  print(f"Error in game loop: {e}")
394
+ game_running = False
395
 
396
  def get_frame():
397
+ """Get the latest frame from buffer"""
398
  try:
399
+ with buffer_lock:
400
+ if frame_buffer:
401
+ return frame_buffer[-1] # Get most recent frame
402
+ except Exception as e:
403
+ print(f"Error getting frame: {e}")
404
+
405
+ # Return black frame as fallback
406
+ return Image.new('RGB', (SCREEN_WIDTH, SCREEN_HEIGHT), (0, 0, 0))
407
 
408
+ def start_game():
409
+ """Start the game and return initial status"""
410
+ global game_running
411
+ if not game_running:
412
+ game_thread = threading.Thread(target=game_loop, daemon=True)
413
+ game_thread.start()
414
+ return "Game Started!"
415
+ return "Game Already Running!"
416
+
417
+ def stop_game():
418
+ """Stop the game"""
419
+ global game_running
420
+ game_running = False
421
+ return "Game Stopped!"
422
+
423
+ def update_difficulty(diff_level):
424
+ """Update difficulty level"""
425
+ global DIFFICULTY
426
+ DIFFICULTY = int(diff_level)
427
+ return f"Difficulty set to Level {DIFFICULTY}"
428
+
429
+ # Optimized CSS for better performance
430
+ css = """
431
+ #game-image {
432
+ height: 450px !important;
433
+ width: 800px !important;
434
  object-fit: contain;
435
  margin: 0 auto;
436
+ display: block;
437
+ image-rendering: pixelated;
438
+ image-rendering: -moz-crisp-edges;
439
+ image-rendering: crisp-edges;
440
+ }
441
+ .gradio-container {
442
+ max-width: 900px !important;
443
+ }
444
+ """
445
 
446
+ def create_interface():
447
+ """Create the Gradio interface with optimizations"""
448
+ with gr.Blocks(css=css, title="AI Car Game") as demo:
449
+ gr.Markdown("# 🚗 AI Car Racing Game")
450
+ gr.Markdown("Real-time AI car racing with PPO agent")
451
+
452
+ img = gr.Image(
453
+ type="pil",
454
+ label="Game Stream",
455
+ elem_id="game-image",
456
+ interactive=False,
457
+ show_download_button=False,
458
+ show_share_button=False
459
+ )
460
+
461
 
462
+ start_btn = gr.Button("🚀 Start Game", variant="primary")
463
+ stop_btn = gr.Button("⏹️ Stop Game", variant="secondary")
464
+
465
+ difficulty_slider = gr.Slider(
466
+ minimum=1,
467
+ maximum=2,
468
+ step=1,
469
+ value=2,
470
+ label="Difficulty Level"
471
+ )
472
+
473
+ status_text = gr.Textbox(
474
+ label="Status",
475
+ value="Click Start Game to begin",
476
+ interactive=False
477
+ )
478
+
479
+ # Event handlers
480
+ start_btn.click(fn=start_game, outputs=status_text)
481
+ stop_btn.click(fn=stop_game, outputs=status_text)
482
+ difficulty_slider.change(fn=update_difficulty, inputs=difficulty_slider, outputs=status_text)
483
+
484
+ # Optimized timer with longer interval
485
+ timer = gr.Timer(value=1/STREAM_FPS) # 20 FPS for streaming
486
+ timer.tick(fn=get_frame, outputs=img)
487
+
488
+ # Add queue for better performance on HF Spaces
489
+ demo.queue(
490
+ max_size=3, # Small queue
491
+ api_open=False
492
+ )
493
+
494
+ return demo
495
 
496
  if __name__ == "__main__":
497
+ # Enable queue and configure for HF Spaces
498
+ demo = create_interface()
499
+ demo.launch(
500
+ )
car_game_classes_gradio.py CHANGED
@@ -26,7 +26,6 @@ class MapGenerator:
26
  road_width = 100
27
 
28
  if self.difficulty == 2:
29
-
30
  border = 50
31
  num_ctrl = 5
32
  x_coords = [i * (self.width/(num_ctrl-1)) for i in range(num_ctrl)]
@@ -35,12 +34,18 @@ class MapGenerator:
35
  # Generate y coordinates with alternating high/low pattern
36
  y_coords = []
37
 
38
- # Define high and low ranges
39
- high_range = (self.height//2 + 2*border, self.height - int(border*5.5))
40
- low_range = (int(border*5.5), self.height//2 - 2*border)
41
-
42
- # Generate first point randomly
43
- first_y = np.random.randint(int(border*5.5), self.height-int(border*5.5))
 
 
 
 
 
 
44
  y_coords.append(first_y)
45
 
46
  # Determine if first point is high or low
@@ -52,19 +57,35 @@ class MapGenerator:
52
  y_previous = y_coords[index_prev]
53
  diff_min = 75
54
  diff_max = 225
 
55
  if is_first_high:
56
-
57
  if (i - 1) % 2 == 0: # Previous was high (first point)
58
- y = np.random.randint(y_previous-diff_max, y_previous-diff_min)
 
59
  else: # Previous was low
60
- y = np.random.randint(y_previous+diff_min, y_previous+diff_max)
 
61
  else:
62
- # If previous point was low, make this one high
63
  if (i - 1) % 2 == 0: # Previous was low (first point)
64
- y = np.random.randint(y_previous+diff_min, y_previous+diff_max)
 
65
  else: # Previous was high
66
- y = np.random.randint(y_previous-diff_max, y_previous-diff_min)
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  y_coords.append(y)
69
 
70
  ctrl_pts = list(zip(x_coords, y_coords))
 
26
  road_width = 100
27
 
28
  if self.difficulty == 2:
 
29
  border = 50
30
  num_ctrl = 5
31
  x_coords = [i * (self.width/(num_ctrl-1)) for i in range(num_ctrl)]
 
34
  # Generate y coordinates with alternating high/low pattern
35
  y_coords = []
36
 
37
+ # Generate first point randomly with safe bounds
38
+ min_y = int(border*5.5)
39
+ max_y = self.height - int(border*5.5)
40
+ # Ensure we have valid bounds
41
+ if min_y >= max_y:
42
+ min_y = border * 2
43
+ max_y = self.height - border * 2
44
+ if min_y >= max_y: # Still invalid, use fallback
45
+ min_y = 50
46
+ max_y = self.height - 50
47
+
48
+ first_y = np.random.randint(min_y, max_y)
49
  y_coords.append(first_y)
50
 
51
  # Determine if first point is high or low
 
57
  y_previous = y_coords[index_prev]
58
  diff_min = 75
59
  diff_max = 225
60
+
61
  if is_first_high:
 
62
  if (i - 1) % 2 == 0: # Previous was high (first point)
63
+ low_bound = max(border, y_previous - diff_max)
64
+ high_bound = min(self.height - border, y_previous - diff_min)
65
  else: # Previous was low
66
+ low_bound = max(border, y_previous + diff_min)
67
+ high_bound = min(self.height - border, y_previous + diff_max)
68
  else:
 
69
  if (i - 1) % 2 == 0: # Previous was low (first point)
70
+ low_bound = max(border, y_previous + diff_min)
71
+ high_bound = min(self.height - border, y_previous + diff_max)
72
  else: # Previous was high
73
+ low_bound = max(border, y_previous - diff_max)
74
+ high_bound = min(self.height - border, y_previous - diff_min)
75
 
76
+ # Ensure valid bounds
77
+ if low_bound >= high_bound:
78
+ # Fallback: use a safe range around the previous point
79
+ center = y_previous
80
+ safe_range = 50
81
+ low_bound = max(border, center - safe_range)
82
+ high_bound = min(self.height - border, center + safe_range)
83
+ if low_bound >= high_bound:
84
+ # Ultimate fallback
85
+ low_bound = border
86
+ high_bound = self.height - border
87
+
88
+ y = np.random.randint(low_bound, high_bound)
89
  y_coords.append(y)
90
 
91
  ctrl_pts = list(zip(x_coords, y_coords))