RL_Car_Agent / car_game_classes_gradio.py
IncreasingLoss's picture
Upload 3 files
9c0402d verified
import pygame
import math
import numpy as np
class MapGenerator:
def __init__(self, width, height, difficulty, goals):
self.surface = pygame.Surface((width, height))
self.width = width
self.height = height
self.difficulty = difficulty
self.goals = []
self.num_goals = goals if isinstance(goals, int) else len(goals) if goals else 12
self.curve_pts = [] # Added to store curve points
def update(self):
self.surface.fill((255, 255, 255))
if self.difficulty == 1:
border = 50
num_ctrl = 4
x_coords = [i * (self.width/(num_ctrl-1)) for i in range(num_ctrl)]
x_coords[0], x_coords[-1] = border*2, self.width-border*2
y_coords = np.random.randint(border*4, self.height-border*4, num_ctrl)
ctrl_pts = list(zip(x_coords, y_coords))
curve_pts = self.compute_catmull_rom(ctrl_pts, 50)
road_width = 100
if self.difficulty == 2:
border = 50
num_ctrl = 5
x_coords = [i * (self.width/(num_ctrl-1)) for i in range(num_ctrl)]
x_coords[0], x_coords[-1] = border*2, self.width-border*2
# Generate y coordinates with alternating high/low pattern
y_coords = []
# Generate first point randomly with safe bounds
min_y = int(border*5.5)
max_y = self.height - int(border*5.5)
# Ensure we have valid bounds
if min_y >= max_y:
min_y = border * 2
max_y = self.height - border * 2
if min_y >= max_y: # Still invalid, use fallback
min_y = 50
max_y = self.height - 50
first_y = np.random.randint(min_y, max_y)
y_coords.append(first_y)
# Determine if first point is high or low
is_first_high = first_y > self.height // 2
# Generate remaining points alternating between high and low
for i in range(1, num_ctrl):
index_prev = i-1
y_previous = y_coords[index_prev]
diff_min = 75
diff_max = 225
if is_first_high:
if (i - 1) % 2 == 0: # Previous was high (first point)
low_bound = max(border, y_previous - diff_max)
high_bound = min(self.height - border, y_previous - diff_min)
else: # Previous was low
low_bound = max(border, y_previous + diff_min)
high_bound = min(self.height - border, y_previous + diff_max)
else:
if (i - 1) % 2 == 0: # Previous was low (first point)
low_bound = max(border, y_previous + diff_min)
high_bound = min(self.height - border, y_previous + diff_max)
else: # Previous was high
low_bound = max(border, y_previous - diff_max)
high_bound = min(self.height - border, y_previous - diff_min)
# Ensure valid bounds
if low_bound >= high_bound:
# Fallback: use a safe range around the previous point
center = y_previous
safe_range = 50
low_bound = max(border, center - safe_range)
high_bound = min(self.height - border, center + safe_range)
if low_bound >= high_bound:
# Ultimate fallback
low_bound = border
high_bound = self.height - border
y = np.random.randint(low_bound, high_bound)
y_coords.append(y)
ctrl_pts = list(zip(x_coords, y_coords))
curve_pts = self.compute_catmull_rom(ctrl_pts, 25)
curve_pts = self.compute_catmull_rom(curve_pts, 50)
road_width = 100
self.curve_pts = curve_pts # Store curve points
# Create goals
goals_pts = self.create_goals(curve_pts)
self.goals = goals_pts
# Draw roads as connected segments
self.draw_road_segments(curve_pts, road_width)
# Draw goals
for goal_index in goals_pts:
pygame.draw.line(self.surface, (0,0,255), goal_index[0], goal_index[1], width=2)
# Get car start
if len(curve_pts) >= 2:
start_pos = curve_pts[0]
xs, ys = start_pos
xe, ye = curve_pts[1]
x = xe - xs
y = ye - ys
start_dir = (x, y)
else:
start_pos = (100, self.height//2)
start_dir = (1, 0)
return pygame.math.Vector2(start_pos), pygame.math.Vector2(start_dir)
def draw_road_segments(self, points, road_width):
if len(points) < 2:
return
for i in range(len(points) - 1):
start_pos = (int(points[i][0]), int(points[i][1]))
end_pos = (int(points[i+1][0]), int(points[i+1][1]))
pygame.draw.line(self.surface, (0, 0, 0), start_pos, end_pos, width=road_width)
cap_radius = road_width // 2
for point in points:
pygame.draw.circle(self.surface, (0, 0, 0), (int(point[0]), int(point[1])), cap_radius)
def compute_catmull_rom(self, control_points, steps_per_segment=20):
try:
is_loop = control_points[0] == control_points[-1]
if hasattr(is_loop, '__len__') and len(is_loop) > 1:
is_loop = np.allclose(control_points[0], control_points[-1])
except:
first = np.array(control_points[0])
last = np.array(control_points[-1])
is_loop = np.allclose(first, last)
pts = control_points[:-1] if is_loop else control_points
n = len(pts)
points = []
def get(idx):
if is_loop:
return pts[idx % n]
else:
return pts[min(max(idx, 0), n - 1)]
for i in range(n if is_loop else n - 1):
p0 = get(i - 1)
p1 = get(i)
p2 = get(i + 1)
p3 = get(i + 2)
for j in range(steps_per_segment):
t = j / steps_per_segment
t2 = t * t
t3 = t2 * t
f1 = -0.5 * t3 + t2 - 0.5 * t
f2 = 1.5 * t3 - 2.5 * t2 + 1.0
f3 = -1.5 * t3 + 2.0 * t2 + 0.5 * t
f4 = 0.5 * t3 - 0.5 * t2
x = f1 * p0[0] + f2 * p1[0] + f3 * p2[0] + f4 * p3[0]
y = f1 * p0[1] + f2 * p1[1] + f3 * p2[1] + f4 * p3[1]
points.append((x, y))
if not is_loop:
points.append(tuple(pts[-1]))
return points
def resample_by_distance(self, points, desired_spacing):
if len(points) < 2:
return points
dists = [0.0]
for i in range(1, len(points)):
prev, curr = np.array(points[i-1]), np.array(points[i])
dist = float(np.linalg.norm(curr - prev))
dists.append(dists[-1] + dist)
total_len = dists[-1]
if total_len == 0:
return points
n_points = int(total_len // desired_spacing)
new_pts = []
for k in range(n_points+1):
target = k * desired_spacing
for j in range(1, len(dists)):
if dists[j] >= target:
ratio = (target - dists[j-1])/(dists[j] - dists[j-1])
A, B = np.array(points[j-1]), np.array(points[j])
P = A + ratio*(B - A)
new_pts.append(tuple(P))
break
return new_pts if new_pts else points
def create_goals(self, pts, goal_number=None):
n_pts = len(pts)
if goal_number is not None:
num_goals = goal_number
else:
num_goals = self.num_goals
if n_pts == 0 or num_goals == 0:
return []
actual_goals = min(num_goals, n_pts // 10)
if actual_goals == 0:
actual_goals = 1
lines = []
last_dir = None
for k in range(actual_goals):
if actual_goals == 1:
idx = n_pts // 2
else:
idx = int((k * (n_pts - 1)) / (actual_goals - 1))
idx = max(0, min(idx, n_pts - 1))
idx2 = idx
look_ahead = 5
if idx + look_ahead < n_pts:
idx2 = idx + look_ahead
elif idx >= look_ahead:
idx2 = idx - look_ahead
elif idx + 1 < n_pts:
idx2 = idx + 1
elif idx > 0:
idx2 = idx - 1
x, y = pts[idx]
if idx2 != idx:
x1, y1 = pts[idx2]
dir_vec = pygame.math.Vector2(x1 - x, y1 - y)
if idx2 < idx:
dir_vec *= -1
else:
dir_vec = last_dir if last_dir else pygame.math.Vector2(0, -1)
if dir_vec.length_squared() < 1e-8:
dir_vec = pygame.math.Vector2(0, -1)
else:
dir_vec = dir_vec.normalize()
if last_dir and dir_vec.dot(last_dir) < -0.5:
dir_vec *= -1
last_dir = dir_vec
normal = dir_vec.rotate(90)
p0 = pygame.math.Vector2(x, y) + normal * 50
p1 = pygame.math.Vector2(x, y) - normal * 50
lines.append((p0, p1))
return lines
def draw(self, surface):
surface.blit(self.surface, (0,0))
class Car:
def __init__(self, image, pos, max_velocity=275, turn_speed=77.5):
self.original_image = image.convert_alpha()
self.image = self.original_image
self.pos = pygame.math.Vector2(pos)
self.velocity = 0.0
self.max_velocity = max_velocity
self.direction = pygame.math.Vector2(1, 0)
self.turn_speed = turn_speed
self.angle = 0.0
self.is_braking = False
self.acceleration = 55.0
self.deceleration = 16.0
self.brake_deceleration = 55.0
self.min_velocity_threshold = 5.0
def update(self, dt, keys=None, steer_factor=1.0):
if keys is None:
pressed = pygame.key.get_pressed()
keys = {
'w': pressed[pygame.K_w],
'a': pressed[pygame.K_a],
's': pressed[pygame.K_s],
'd': pressed[pygame.K_d]
}
self.is_braking = False
if keys.get('w', False):
acceleration_multiplier = 1.0
if self.velocity > 50:
acceleration_multiplier = 1.2
elif self.velocity < 20:
acceleration_multiplier = 1.5
if self.velocity < self.max_velocity:
self.velocity += self.acceleration * acceleration_multiplier * dt
if self.velocity > self.max_velocity:
self.velocity = self.max_velocity
if keys.get('s', False):
self.is_braking = True
if self.velocity > 0:
brake_multiplier = 1.0 + (self.velocity / self.max_velocity) * 0.5
self.velocity -= self.brake_deceleration * brake_multiplier * dt
if self.velocity < 0:
self.velocity = 0
else:
if not keys.get('w', False) and self.velocity > 0:
decel_rate = self.deceleration
if self.velocity > 100:
decel_rate *= 0.7
self.velocity -= decel_rate * dt
if self.velocity < self.min_velocity_threshold:
self.velocity = max(0, self.velocity - 2.0 * dt)
if self.velocity > 0:
self.pos += self.direction.normalize() * self.velocity * dt
if self.velocity > 10:
angular_velocity = 0
speed_factor = min(1.0, self.velocity / 200.0)
effective_turn_speed = self.turn_speed * (0.5 + 0.5 * speed_factor)
if keys.get('a', False):
angular_velocity = -effective_turn_speed * steer_factor
elif keys.get('d', False):
angular_velocity = effective_turn_speed * steer_factor
if angular_velocity != 0:
angle_change = angular_velocity * dt
target_direction = self.direction.rotate(angle_change)
base_drift = 0.15
velocity_factor = min(1.0, self.velocity / self.max_velocity)
drift_factor = base_drift * (0.5 + 0.5 * velocity_factor)
self.direction = (self.direction * drift_factor +
target_direction * (1 - drift_factor)).normalize()
self.angle = -math.degrees(math.atan2(self.direction.y, self.direction.x))
self.image = pygame.transform.rotate(self.original_image, self.angle)
def draw(self, surface):
rect = self.image.get_rect(center=self.pos)
surface.blit(self.image, rect.topleft)
def reset(self, pos, direction):
self.pos = pygame.math.Vector2(pos)
self.direction = pygame.math.Vector2(direction).normalize()
self.velocity = 0.0
self.angle = -math.degrees(math.atan2(self.direction.y, self.direction.x))
class RayCasting:
def __init__(self, car_pos, car_dir, track_surf,
ray_angles, ray_offsets, max_distance=1600, step_pixels=1, draw_rays=True):
self.car_pos = pygame.math.Vector2(car_pos)
self.car_dir = pygame.math.Vector2(car_dir).normalize()
self.track_surf = track_surf
self.ray_angles = list(ray_angles)
self.ray_offsets = list(ray_offsets)
self.max_distance = max_distance
self.step_pixels = step_pixels
self.last_collisions = [self.car_pos.copy()] * len(self.ray_angles)
self.ray_distances = [0.0] * len(self.ray_angles)
self.draw_rays = draw_rays
# NEW: Performance optimization variables
self.coarse_step = 15 # Check every x pixels first
self.fine_step = 1 # Then refine with 1 pixel precision
def cast_ray(self, start_pos, direction):
"""Optimized ray casting with coarse-to-fine approach"""
pos = pygame.math.Vector2(start_pos)
dir_norm = pygame.math.Vector2(direction).normalize()
# Calculate step size based on direction
inv = max(abs(dir_norm.x), abs(dir_norm.y))
base_step_size = (1.0 / inv) if inv != 0 else 1.0
# Phase 1: Coarse search with 5x step size
coarse_step_size = base_step_size * self.coarse_step
traveled = 0.0
w, h = self.track_surf.get_size()
last_safe_pos = pos.copy()
last_safe_distance = 0.0
while traveled < self.max_distance:
ix, iy = int(pos.x), int(pos.y)
if 0 <= ix < w and 0 <= iy < h:
color = self.track_surf.get_at((ix, iy))
if color[:3] == (255, 255, 255):
# Collision found, break for fine search
break
# Store last safe position
last_safe_pos = pos.copy()
last_safe_distance = traveled
pos += dir_norm * coarse_step_size
traveled += coarse_step_size
# Phase 2: Fine search from last safe position
if traveled < self.max_distance:
# Reset to last safe position and do fine search
pos = last_safe_pos.copy()
traveled = last_safe_distance
fine_step_size = base_step_size * self.fine_step
while traveled < self.max_distance:
ix, iy = int(pos.x), int(pos.y)
if 0 <= ix < w and 0 <= iy < h:
color = self.track_surf.get_at((ix, iy))
if color[:3] == (255, 255, 255):
return pos, traveled
pos += dir_norm * fine_step_size
traveled += fine_step_size
return pos, traveled
def draw_ray(self, start, end, color=(255, 255, 0)):
pygame.draw.line(self.track_surf, color,
(int(start.x), int(start.y)),
(int(end.x), int(end.y)),
width=2)
def update(self, car_pos, car_dir):
"""Updated to use optimized ray casting"""
self.car_pos = pygame.math.Vector2(car_pos)
self.car_dir = pygame.math.Vector2(car_dir).normalize()
self.last_collisions = []
self.ray_distances = []
any_origin_in_white = False
for angle, offset in zip(self.ray_angles, self.ray_offsets):
ray_dir = self.car_dir.rotate(angle).normalize()
origin = self.car_pos + ray_dir * offset
ix, iy = int(origin.x), int(origin.y)
w, h = self.track_surf.get_size()
if 0 <= ix < w and 0 <= iy < h:
if self.track_surf.get_at((ix, iy))[:3] == (255, 255, 255):
any_origin_in_white = True
self.last_collisions.append(origin)
self.ray_distances.append(0.0)
continue
# Use optimized ray casting
hit_pt, distance = self.cast_ray(origin, ray_dir)
self.last_collisions.append(hit_pt)
self.ray_distances.append(distance)
if self.draw_rays:
self.draw_ray(origin, hit_pt)
return not any_origin_in_white
def get_ray_distances(self):
return self.ray_distances.copy()
def has_zero_length_ray(self):
return any(distance <= 0.0 for distance in self.ray_distances)