IncreasingLoss commited on
Commit
2a46292
·
verified ·
1 Parent(s): 8fac572

Upload 3 files

Browse files
Files changed (2) hide show
  1. app.py +448 -406
  2. car_game_classes_gradio.py +1 -1
app.py CHANGED
@@ -1,407 +1,449 @@
1
- import gradio as gr
2
- import numpy as np
3
- import pygame
4
- import threading
5
- import queue
6
- from PIL import Image
7
- from car_game_classes_gradio import Car, RayCasting, MapGenerator
8
- from ppo_agent_gradio import PPOAgent
9
- 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
-
17
- # Define constants
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
- DIFFICULTY = 2
24
-
25
- # Frame queue for Gradio
26
- frame_queue = queue.Queue(maxsize=1)
27
-
28
- def point_to_line_distance(point, line_p0, line_p1):
29
- p = pygame.math.Vector2(point)
30
- a = pygame.math.Vector2(line_p0)
31
- b = pygame.math.Vector2(line_p1)
32
- ap = p - a
33
- ab = b - a
34
- magnitude_ab = ab.length_squared()
35
- if magnitude_ab == 0:
36
- return ap.length()
37
- projection = ap.dot(ab) / magnitude_ab
38
- projection = max(0, min(1, projection))
39
- closest = a + projection * ab
40
- return p.distance_to(closest)
41
-
42
- class TrainingEnvironment:
43
- def __init__(self, action_space=8):
44
- self.action_space = action_space
45
- self.map_generator = MapGenerator(SCREEN_WIDTH, SCREEN_HEIGHT, DIFFICULTY, 18) # Difficulty 2 = lvl 3 in training
46
- car_surf = pygame.image.load("assets/car_transparent.png").convert_alpha()
47
- w, h = car_surf.get_size()
48
- self.car_image = pygame.transform.scale(car_surf, (w//18, h//18))
49
- self.start_pos = None
50
- self.start_dir = None
51
- self.previous_speed = 0.0
52
- self.speed_history = deque(maxlen=5)
53
- self.steering_history = deque(maxlen=10)
54
- self.steering_persistence = {'direction': 0, 'count': 0}
55
- self.last_steering_reward = 0.0
56
- self.prev_pos = None
57
- self.passed_goals = []
58
- self.last_action = 0
59
- self.reset()
60
-
61
- def generate_new_map(self):
62
- start_pos, start_dir = self.map_generator.update()
63
- self.start_pos = pygame.math.Vector2(start_pos)
64
- self.start_dir = pygame.math.Vector2(start_dir).normalize()
65
-
66
- def reset_car(self):
67
- if self.start_pos is None or self.start_dir is None:
68
- raise ValueError("Map not generated yet")
69
- self.car = Car(self.car_image, self.start_pos)
70
- self.car.direction = self.start_dir
71
- self.rays = RayCasting(
72
- self.car.pos, self.car.direction, self.map_generator.surface,
73
- [-60, -30, -15, 0, 15, 30, 60],
74
- [14, 22, 27, 26, 27, 22, 14],
75
- draw_rays=False)
76
- self.current_goal_index = 0
77
- self.passed_goals = []
78
- self.prev_pos = self.car.pos.copy()
79
- self.previous_speed = 0.0
80
- self.speed_history.clear()
81
- self.steering_history.clear()
82
- self.steering_persistence = {'direction': 0, 'count': 0}
83
- self.last_steering_reward = 0.0
84
- self.last_action = 0
85
-
86
- def reset(self):
87
- self.map_generator.difficulty = DIFFICULTY
88
- self.generate_new_map()
89
- self.reset_car()
90
-
91
- def detect_steering_need(self, ray_distances):
92
- norm_distances = [min(d / self.rays.max_distance, 1.0) for d in ray_distances]
93
- left_rays = norm_distances[0:3]
94
- center_ray = norm_distances[3]
95
- right_rays = norm_distances[4:7]
96
- left_avg = sum(left_rays) / len(left_rays)
97
- right_avg = sum(right_rays) / len(right_rays)
98
- space_difference = right_avg - left_avg
99
- steering_needed = False
100
- preferred_direction = 0
101
- urgency = 0.0
102
- min_side_distance = min(min(left_rays), min(right_rays))
103
- if min_side_distance < 0.3:
104
- steering_needed = True
105
- urgency = 1.0 - min_side_distance
106
- preferred_direction = 1 if left_avg < right_avg else -1
107
- elif abs(space_difference) > 0.15:
108
- steering_needed = True
109
- urgency = min(abs(space_difference), 1.0)
110
- preferred_direction = 1 if space_difference > 0 else -1
111
- elif center_ray < 0.4:
112
- steering_needed = True
113
- urgency = 1.0 - center_ray
114
- preferred_direction = 1 if left_avg < right_avg else -1
115
- return {
116
- 'needed': steering_needed,
117
- 'direction': preferred_direction,
118
- 'urgency': urgency,
119
- 'space_diff': space_difference,
120
- 'left_space': left_avg,
121
- 'right_space': right_avg,
122
- 'center_distance': center_ray
123
- }
124
-
125
- def get_state(self):
126
- self.rays.update(self.car.pos, self.car.direction)
127
- ray_distances = self.rays.get_ray_distances()
128
- normalized_ray_distances = [min(distance / self.rays.max_distance, 1.0) for distance in ray_distances]
129
- steering_info = self.detect_steering_need(ray_distances)
130
- state = [
131
- self.car.velocity / self.car.max_velocity,
132
- self.car.direction.x,
133
- self.car.direction.y,
134
- int(self.car.is_braking),
135
- ]
136
- state.extend(normalized_ray_distances)
137
- state.extend([
138
- float(steering_info['needed']),
139
- steering_info['direction'],
140
- steering_info['urgency'],
141
- steering_info['space_diff'],
142
- steering_info['left_space'],
143
- steering_info['right_space'],
144
- steering_info['center_distance']
145
- ])
146
- total_goals = len(self.map_generator.goals)
147
- progress = self.current_goal_index / max(total_goals, 1)
148
- state.append(progress)
149
- state.append(self.last_action / 4.0)
150
- speed_change = self.car.velocity - self.previous_speed
151
- self.speed_history.append(speed_change)
152
- avg_speed_change = sum(self.speed_history) / max(len(self.speed_history), 1)
153
- state.extend([speed_change / 100.0, avg_speed_change / 100.0])
154
- self.previous_speed = self.car.velocity
155
- return np.array(state, dtype=np.float32)
156
-
157
- def execute_action_dispatch(self, action, dt):
158
- action_int = int(action)
159
- self.last_action = action_int
160
- keys = {'w': False, 'a': False, 'd': False, 's': False}
161
- steer_factor = 1.0
162
- if action_int == 0:
163
- keys['w'] = True
164
- elif action_int == 1:
165
- keys['a'] = True
166
- steer_factor = 1.0
167
- elif action_int == 2:
168
- keys['d'] = True
169
- steer_factor = 1.0
170
- elif action_int == 3:
171
- keys['w'] = True
172
- keys['a'] = True
173
- steer_factor = 0.8
174
- elif action_int == 4:
175
- keys['w'] = True
176
- keys['d'] = True
177
- steer_factor = 0.8
178
- elif action_int == 5:
179
- keys['s'] = True
180
- original_brake = self.car.brake_deceleration
181
- self.car.brake_deceleration *= 0.6
182
- self.car.update(dt, keys, steer_factor)
183
- self.car.brake_deceleration = original_brake
184
- return
185
- elif action_int == 6:
186
- keys['s'] = True
187
- keys['a'] = True
188
- steer_factor = 1.2
189
- elif action_int == 7:
190
- keys['s'] = True
191
- keys['d'] = True
192
- steer_factor = 1.2
193
- self.car.update(dt, keys, steer_factor)
194
-
195
- def detect_curve_severity(self, ray_distances, steering_info):
196
- normalized_ray_distances = [min(distance / self.rays.max_distance, 1.0) for distance in ray_distances]
197
- longest_distance = max(normalized_ray_distances)
198
- longest_index = normalized_ray_distances.index(longest_distance)
199
- main_ray_index = 3
200
- if longest_index == main_ray_index:
201
- return 0.0
202
- left_adjacent_index = max(0, longest_index - 1)
203
- right_adjacent_index = min(len(normalized_ray_distances) - 1, longest_index + 1)
204
- left_adjacent_distance = normalized_ray_distances[left_adjacent_index]
205
- right_adjacent_distance = normalized_ray_distances[right_adjacent_index]
206
- if left_adjacent_distance > right_adjacent_distance:
207
- longer_adjacent_index = left_adjacent_index
208
- longer_adjacent_distance = left_adjacent_distance
209
- else:
210
- longer_adjacent_index = right_adjacent_index
211
- longer_adjacent_distance = right_adjacent_distance
212
- weight = longer_adjacent_distance / longest_distance
213
- ray_angles = [-60, -30, -15, 0, 15, 30, 60]
214
- longest_ray_angle = ray_angles[longest_index]
215
- longer_adjacent_angle = ray_angles[longer_adjacent_index]
216
- longest_pos_angle = longest_ray_angle + weight * (longer_adjacent_angle - longest_ray_angle)
217
- front_ray_angle = ray_angles[main_ray_index]
218
- angle_difference = abs(longest_pos_angle - front_ray_angle)
219
- if angle_difference >= 50:
220
- curve_severity = 1.0
221
- else:
222
- curve_severity = angle_difference / 40.0
223
- return curve_severity
224
-
225
- def calculate_reward(self):
226
- self.rays.update(self.car.pos, self.car.direction)
227
- ray_distances = self.rays.get_ray_distances()
228
- collision = self.rays.has_zero_length_ray()
229
- if collision:
230
- return -100.0, collision, False
231
- steering_info = self.detect_steering_need(ray_distances)
232
- reward = 0.0
233
- curve_severity = self.detect_curve_severity(ray_distances, steering_info)
234
- center_distance = ray_distances[3] / self.rays.max_distance
235
- if center_distance > 0.25:
236
- reward += 0.25 * self.car.velocity - 6
237
- if curve_severity > 0.1:
238
- if curve_severity > 0.5:
239
- if 0.125 < self.car.velocity < 0.25:
240
- reward += self.car.velocity / 10
241
- else:
242
- reward += -2
243
- elif curve_severity > 0.25:
244
- if 0.15 < self.car.velocity < 0.35:
245
- reward += self.car.velocity / 12
246
- else:
247
- reward += -2
248
- else:
249
- if 0.35 < self.car.velocity < 0.7:
250
- reward += self.car.velocity / 10
251
- else:
252
- reward += -2
253
- else:
254
- if self.car.velocity > 40:
255
- reward += self.car.velocity / 20
256
- elif self.car.velocity < 25:
257
- reward += -0.5
258
- if hasattr(self, 'speed_consistency_tracker'):
259
- self.speed_consistency_tracker = {'appropriate': False, 'count': 0}
260
- if steering_info['needed']:
261
- steering_urgency = steering_info['urgency']
262
- base_steering_reward = 5.0 + (steering_urgency * 4.0)
263
- correct_action = False
264
- if steering_info['direction'] == -1:
265
- if self.last_action in [1, 3, 6]:
266
- correct_action = True
267
- if self.steering_persistence['direction'] == -1:
268
- self.steering_persistence['count'] += 2
269
- persistence_bonus = min(3.0, self.steering_persistence['count'] * 0.5)
270
- base_steering_reward += persistence_bonus
271
- else:
272
- self.steering_persistence = {'direction': -1, 'count': 1}
273
- if curve_severity > 0.4 and self.last_action == 6:
274
- base_steering_reward += 5.0
275
- elif steering_info['direction'] == 1:
276
- if self.last_action in [2, 4, 7]:
277
- correct_action = True
278
- if self.steering_persistence['direction'] == 1:
279
- self.steering_persistence['count'] += 2
280
- persistence_bonus = min(3.0, self.steering_persistence['count'] * 0.5)
281
- base_steering_reward += persistence_bonus
282
- else:
283
- self.steering_persistence = {'direction': 1, 'count': 1}
284
- if curve_severity > 0.4 and self.last_action == 7:
285
- base_steering_reward += 5.0
286
- if correct_action:
287
- reward += base_steering_reward
288
- else:
289
- self.steering_persistence = {'direction': 0, 'count': 0}
290
- if steering_urgency > 0.6:
291
- penalty = -4.0 * steering_urgency
292
- reward += penalty
293
- else:
294
- self.steering_persistence = {'direction': 0, 'count': 0}
295
- speed_factor = self.car.velocity / self.car.max_velocity
296
- min_side_distance = min([ray_distances[i] for i in [0, 1, 2, 4, 5, 6]]) / self.rays.max_distance
297
- center_distance = ray_distances[3] / self.rays.max_distance
298
- if min_side_distance < 0.01:
299
- danger_penalty = -5.0 * (1.0 - min_side_distance * 12.5) * (0.5 + speed_factor * 0.5)
300
- reward += danger_penalty
301
- elif min_side_distance < 0.0166:
302
- if speed_factor > 0.7:
303
- minor_penalty = -2 * (1.0 - min_side_distance * 6.67)
304
- reward += minor_penalty
305
- if 0.01666 <= min_side_distance and center_distance > 0.2:
306
- safety_efficiency_bonus = 0.3
307
- if 0.3 <= speed_factor <= 0.8:
308
- safety_efficiency_bonus += 0.2
309
- reward += safety_efficiency_bonus
310
- total_goals = len(self.map_generator.goals)
311
- if self.current_goal_index < total_goals:
312
- goal = self.map_generator.goals[self.current_goal_index]
313
- dist = point_to_line_distance(self.car.pos, goal[0], goal[1])
314
- if dist < 8:
315
- reward += 7.5
316
- self.passed_goals.append(self.current_goal_index)
317
- self.current_goal_index += 1
318
- all_goals_completed = self.current_goal_index >= total_goals
319
- if all_goals_completed:
320
- reward += 20.0
321
- reward = max(-15.0, min(reward, 12.0))
322
- self.prev_pos = pygame.math.Vector2(self.car.pos)
323
- self.last_steering_reward = reward
324
- return reward, collision, all_goals_completed
325
-
326
- def game_loop():
327
- try:
328
- pygame.init()
329
- pygame.display.set_mode((1, 1), pygame.NOFRAME)
330
- screen = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
331
- env = TrainingEnvironment(ACTION_SPACE)
332
- env.reset()
333
- agent = PPOAgent(state_size, ACTION_SPACE)
334
- agent.load(LOADED_MODEL)
335
- steps = 0
336
- action_counts = [0] * ACTION_SPACE
337
- while True:
338
- for event in pygame.event.get():
339
- if event.type == pygame.QUIT:
340
- pygame.quit()
341
- return
342
- state = env.get_state()
343
- action, _ = agent.select_action(state)
344
- action_int = int(action)
345
- action_counts[action_int] += 1
346
- env.execute_action_dispatch(action_int, DT)
347
- reward, collision, all_goals_completed = env.calculate_reward()
348
- env.rays.update(env.car.pos, env.car.direction)
349
- if collision or all_goals_completed:
350
- env.reset()
351
- steps += 1
352
- screen.fill((255, 255, 255))
353
- env.map_generator.draw(screen)
354
- env.car.draw(screen)
355
- for i, (angle, offset) in enumerate(zip([-60, -30, -15, 0, 15, 30, 60], [14, 22, 27, 26, 27, 22, 14])):
356
- if i < len(env.rays.last_collisions):
357
- ray_dir = env.car.direction.rotate(angle).normalize()
358
- origin = env.car.pos + ray_dir * offset
359
- end_point = env.rays.last_collisions[i]
360
- pygame.draw.line(screen, (255, 255, 0),
361
- (int(origin.x), int(origin.y)),
362
- (int(end_point.x), int(end_point.y)),
363
- width=2)
364
- # Optimized frame generation
365
- raw_str = pygame.image.tostring(screen, 'RGB')
366
- pil_image = Image.frombytes('RGB', (SCREEN_WIDTH, SCREEN_HEIGHT), raw_str)
367
- try:
368
- frame_queue.put(pil_image, block=False)
369
- except queue.Full:
370
- pass
371
- clock.tick(SIMULATION_FPS)
372
- except Exception as e:
373
- print(f"Error in game loop: {e}")
374
-
375
- def get_frame():
376
- try:
377
- return frame_queue.get_nowait()
378
- except queue.Empty:
379
- return Image.new('RGB', (SCREEN_WIDTH, SCREEN_HEIGHT), (0, 0, 0))
380
-
381
- css = """#game-image {
382
- height: calc(100vw * 9 / 16) !important;
383
- object-fit: contain;
384
- margin: 0 auto;
385
- display: block;
386
- }"""
387
-
388
-
389
- def update_difficulty(input_diff_lvl):
390
- global DIFFICULTY
391
- DIFFICULTY = input_diff_lvl
392
-
393
- def execute_interference():
394
- game_thread = threading.Thread(target=game_loop)
395
- game_thread.daemon = True
396
- game_thread.start()
397
- with gr.Blocks(css=css) as demo:
398
- with gr.Column():
399
- img = gr.Image(type="pil", label="Game Stream", elem_id="game-image", scale=1, min_width=0)
400
- difficulty_slider = gr.Slider(minimum=1, maximum=2, step=1, value=2, label="Difficulty Level")
401
- difficulty_slider.change(fn=update_difficulty, inputs=difficulty_slider)
402
- timer = gr.Timer(value=0.033) # ~30 FPS
403
- timer.tick(fn=get_frame, inputs=None, outputs=img)
404
- demo.launch()
405
-
406
- if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  execute_interference()
 
1
+ import gradio as gr
2
+ 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
9
+ 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
+
17
+ # Define constants
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)
31
+ a = pygame.math.Vector2(line_p0)
32
+ b = pygame.math.Vector2(line_p1)
33
+ ap = p - a
34
+ ab = b - a
35
+ magnitude_ab = ab.length_squared()
36
+ if magnitude_ab == 0:
37
+ return ap.length()
38
+ projection = ap.dot(ab) / magnitude_ab
39
+ projection = max(0, min(1, projection))
40
+ closest = a + projection * ab
41
+ return p.distance_to(closest)
42
+
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
53
+ self.speed_history = deque(maxlen=5)
54
+ self.steering_history = deque(maxlen=10)
55
+ self.steering_persistence = {'direction': 0, 'count': 0}
56
+ self.last_steering_reward = 0.0
57
+ self.prev_pos = None
58
+ self.passed_goals = []
59
+ self.last_action = 0
60
+ self.background = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
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)
67
+ self.start_dir = pygame.math.Vector2(start_dir).normalize()
68
+
69
+ def reset_car(self):
70
+ if self.start_pos is None or self.start_dir is None:
71
+ raise ValueError("Map not generated yet")
72
+ self.car = Car(self.car_image, self.start_pos)
73
+ self.car.direction = self.start_dir
74
+ self.rays = RayCasting(
75
+ self.car.pos, self.car.direction, self.map_generator.surface,
76
+ [-60, -30, -15, 0, 15, 30, 60],
77
+ [14, 22, 27, 26, 27, 22, 14],
78
+ draw_rays=False)
79
+ self.current_goal_index = 0
80
+ self.passed_goals = []
81
+ self.prev_pos = self.car.pos.copy()
82
+ self.previous_speed = 0.0
83
+ self.speed_history.clear()
84
+ self.steering_history.clear()
85
+ self.steering_persistence = {'direction': 0, 'count': 0}
86
+ self.last_steering_reward = 0.0
87
+ self.last_action = 0
88
+
89
+ def reset(self):
90
+ self.map_generator.difficulty = DIFFICULTY
91
+ self.generate_new_map()
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]
98
+ center_ray = norm_distances[3]
99
+ right_rays = norm_distances[4:7]
100
+ left_avg = sum(left_rays) / len(left_rays)
101
+ right_avg = sum(right_rays) / len(right_rays)
102
+ space_difference = right_avg - left_avg
103
+ steering_needed = False
104
+ preferred_direction = 0
105
+ urgency = 0.0
106
+ min_side_distance = min(min(left_rays), min(right_rays))
107
+ if min_side_distance < 0.3:
108
+ steering_needed = True
109
+ urgency = 1.0 - min_side_distance
110
+ preferred_direction = 1 if left_avg < right_avg else -1
111
+ elif abs(space_difference) > 0.15:
112
+ steering_needed = True
113
+ urgency = min(abs(space_difference), 1.0)
114
+ preferred_direction = 1 if space_difference > 0 else -1
115
+ elif center_ray < 0.4:
116
+ steering_needed = True
117
+ urgency = 1.0 - center_ray
118
+ preferred_direction = 1 if left_avg < right_avg else -1
119
+ return {
120
+ 'needed': steering_needed,
121
+ 'direction': preferred_direction,
122
+ 'urgency': urgency,
123
+ 'space_diff': space_difference,
124
+ 'left_space': left_avg,
125
+ 'right_space': right_avg,
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
164
+ keys = {'w': False, 'a': False, 'd': False, 's': False}
165
+ steer_factor = 1.0
166
+ if action_int == 0:
167
+ keys['w'] = True
168
+ elif action_int == 1:
169
+ keys['a'] = True
170
+ steer_factor = 1.0
171
+ elif action_int == 2:
172
+ keys['d'] = True
173
+ steer_factor = 1.0
174
+ elif action_int == 3:
175
+ keys['w'] = True
176
+ keys['a'] = True
177
+ steer_factor = 0.8
178
+ elif action_int == 4:
179
+ keys['w'] = True
180
+ keys['d'] = True
181
+ steer_factor = 0.8
182
+ elif action_int == 5:
183
+ keys['s'] = True
184
+ original_brake = self.car.brake_deceleration
185
+ self.car.brake_deceleration *= 0.6
186
+ self.car.update(dt, keys, steer_factor)
187
+ self.car.brake_deceleration = original_brake
188
+ return
189
+ elif action_int == 6:
190
+ keys['s'] = True
191
+ keys['a'] = True
192
+ steer_factor = 1.2
193
+ elif action_int == 7:
194
+ keys['s'] = True
195
+ keys['d'] = True
196
+ steer_factor = 1.2
197
+ self.car.update(dt, keys, steer_factor)
198
+
199
+ def detect_curve_severity(self, ray_distances, steering_info):
200
+ normalized_ray_distances = [min(distance / self.rays.max_distance, 1.0) for distance in ray_distances]
201
+ longest_distance = max(normalized_ray_distances)
202
+ longest_index = normalized_ray_distances.index(longest_distance)
203
+ main_ray_index = 3
204
+ if longest_index == main_ray_index:
205
+ return 0.0
206
+ left_adjacent_index = max(0, longest_index - 1)
207
+ right_adjacent_index = min(len(normalized_ray_distances) - 1, longest_index + 1)
208
+ left_adjacent_distance = normalized_ray_distances[left_adjacent_index]
209
+ right_adjacent_distance = normalized_ray_distances[right_adjacent_index]
210
+ if left_adjacent_distance > right_adjacent_distance:
211
+ longer_adjacent_index = left_adjacent_index
212
+ longer_adjacent_distance = left_adjacent_distance
213
+ else:
214
+ longer_adjacent_index = right_adjacent_index
215
+ longer_adjacent_distance = right_adjacent_distance
216
+ weight = longer_adjacent_distance / longest_distance
217
+ ray_angles = [-60, -30, -15, 0, 15, 30, 60]
218
+ longest_ray_angle = ray_angles[longest_index]
219
+ longer_adjacent_angle = ray_angles[longer_adjacent_index]
220
+ longest_pos_angle = longest_ray_angle + weight * (longer_adjacent_angle - longest_ray_angle)
221
+ front_ray_angle = ray_angles[main_ray_index]
222
+ angle_difference = abs(longest_pos_angle - front_ray_angle)
223
+ if angle_difference >= 50:
224
+ curve_severity = 1.0
225
+ else:
226
+ curve_severity = angle_difference / 40.0
227
+ return curve_severity
228
+
229
+ def calculate_reward(self):
230
+ self.rays.update(self.car.pos, self.car.direction)
231
+ ray_distances = self.rays.get_ray_distances()
232
+ collision = self.rays.has_zero_length_ray()
233
+ if collision:
234
+ return -100.0, collision, False
235
+ steering_info = self.detect_steering_need(ray_distances)
236
+ reward = 0.0
237
+ curve_severity = self.detect_curve_severity(ray_distances, steering_info)
238
+ center_distance = ray_distances[3] / self.rays.max_distance
239
+ if center_distance > 0.25:
240
+ reward += 0.25 * self.car.velocity - 6
241
+ if curve_severity > 0.1:
242
+ if curve_severity > 0.5:
243
+ if 0.125 < self.car.velocity < 0.25:
244
+ reward += self.car.velocity / 10
245
+ else:
246
+ reward += -2
247
+ elif curve_severity > 0.25:
248
+ if 0.15 < self.car.velocity < 0.35:
249
+ reward += self.car.velocity / 12
250
+ else:
251
+ reward += -2
252
+ else:
253
+ if 0.35 < self.car.velocity < 0.7:
254
+ reward += self.car.velocity / 10
255
+ else:
256
+ reward += -2
257
+ else:
258
+ if self.car.velocity > 40:
259
+ reward += self.car.velocity / 20
260
+ elif self.car.velocity < 25:
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]
317
+ dist = point_to_line_distance(self.car.pos, goal[0], goal[1])
318
+ if dist < 8:
319
+ reward += 7.5
320
+ self.passed_goals.append(self.current_goal_index)
321
+ self.current_goal_index += 1
322
+ all_goals_completed = self.current_goal_index >= total_goals
323
+ if all_goals_completed:
324
+ reward += 20.0
325
+ reward = max(-15.0, min(reward, 12.0))
326
+ self.prev_pos = pygame.math.Vector2(self.car.pos)
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)
334
+ screen = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
335
+ env = TrainingEnvironment(ACTION_SPACE)
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)
358
+ action_counts[action_int] += 1
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/2), int(SCREEN_HEIGHT/2)))
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()
car_game_classes_gradio.py CHANGED
@@ -325,7 +325,7 @@ class RayCasting:
325
  self.draw_rays = draw_rays
326
 
327
  # NEW: Performance optimization variables
328
- self.coarse_step = 5 # Check every 5 pixels first
329
  self.fine_step = 1 # Then refine with 1 pixel precision
330
 
331
  def cast_ray(self, start_pos, direction):
 
325
  self.draw_rays = draw_rays
326
 
327
  # NEW: Performance optimization variables
328
+ self.coarse_step = 15 # Check every x pixels first
329
  self.fine_step = 1 # Then refine with 1 pixel precision
330
 
331
  def cast_ray(self, start_pos, direction):