Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 332 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,332 @@
# Racing-game
"""
Top-down Racing Game (single file)
Requires: pygame
Install: pip install pygame
Run: python racing_game.py

Controls:
- Up / W : accelerate
- Down / S : brake / reverse
- Left / A : steer left
- Right / D : steer right
- R : restart race
- ESC or window close : quit
"""
# Ensure pygame is installed: pip install pygame
import sys
import math
import random
import pygame
from pygame import Vector2 # type: ignore

pygame.init()
WIDTH, HEIGHT = 1000, 700
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Top-down Racing Demo")
CLOCK = pygame.time.Clock()
FONT = pygame.font.SysFont(None, 24)

# Colors
GREEN = (61, 174, 64)
ROAD = (50, 50, 50)
LINE = (255, 255, 255)
START_LINE = (255, 0, 0)
SKY = (120, 200, 255)

# Track parameters
TRACK_CENTER = Vector2(WIDTH // 2, HEIGHT // 2 - 20)
OUTER_RX, OUTER_RY = 380, 220
INNER_RX, INNER_RY = 200, 90

# Game settings
NUM_OPPONENTS = 3
LAPS_TO_FINISH = 3

def create_track_surface():
surf = pygame.Surface((WIDTH, HEIGHT))
surf.fill(GREEN)
# draw outer track (road)
pygame.draw.ellipse(surf, ROAD, (
TRACK_CENTER.x - OUTER_RX, TRACK_CENTER.y - OUTER_RY, OUTER_RX * 2, OUTER_RY * 2
))
# cut out inner grass to make ring
pygame.draw.ellipse(surf, GREEN, (
TRACK_CENTER.x - INNER_RX, TRACK_CENTER.y - INNER_RY, INNER_RX * 2, INNER_RY * 2
))
# draw finish/start line at top center of inner ellipse
line_w = 4
# compute top point of inner ellipse
top_x = TRACK_CENTER.x
top_y = TRACK_CENTER.y - INNER_RY
pygame.draw.line(surf, START_LINE, (top_x - 20, top_y), (top_x + 20, top_y), line_w)
# small white dashes on outer boundary for visuals
for a in range(0, 360, 12):
ra = math.radians(a)
x = TRACK_CENTER.x + math.cos(ra) * (OUTER_RX + INNER_RX) / 2
y = TRACK_CENTER.y + math.sin(ra) * (OUTER_RY + INNER_RY) / 2
pygame.draw.circle(surf, (200, 200, 200), (int(x), int(y)), 2)
return surf

TRACK_SURF = create_track_surface()
TRACK_PIXELS = pygame.PixelArray(TRACK_SURF) # for quick color checks (read-only usage)

def is_on_road(pos):
x, y = int(pos.x), int(pos.y)
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
return TRACK_SURF.get_at((x, y))[:3] == ROAD
return False

def angle_to(target, current_pos):
v = target - current_pos
return math.degrees(math.atan2(-v.y, v.x)) % 360

class Car:
def __init__(self, color, pos, angle=0.0, max_speed=4.0):
self.color = color
self.pos = Vector2(pos)
self.angle = angle # degrees, 0 = right
self.speed = 0.0
self.max_speed = max_speed
self.length = 28
self.width = 14
self.acceleration = 0.12
self.brake_decel = 0.2
self.friction = 0.03
self.steer_speed = 3.5 # degrees per frame at full lock
self.laps = 0
self.previous_cross = False
self.progress = 0.0 # angular progress on track for ranking

def rect_points(self):
# return polygon points for rotated rectangle (for debugging/collision visuals)
rad = math.radians(self.angle)
cos, sin = math.cos(rad), math.sin(rad)
l, w = self.length / 2, self.width / 2
corners = [
Vector2(cos * l - sin * w, sin * l + cos * w),
Vector2(-cos * l - sin * w, -sin * l + cos * w),
Vector2(-cos * l + sin * w, -sin * l - cos * w),
Vector2(cos * l + sin * w, sin * l - cos * w),
]
return [self.pos + p for p in corners]

def update(self):
# movement integration
rad = math.radians(self.angle)
self.pos += Vector2(math.cos(rad), -math.sin(rad)) * self.speed

# friction
if self.speed > 0:
self.speed = max(0.0, self.speed - self.friction)
elif self.speed < 0:
self.speed = min(0.0, self.speed + self.friction)

# check road/grass slowdown
if not is_on_road(self.pos):
# on grass => heavy slowdown
self.speed *= 0.94
# reduce steering
# (we don't change steer here; controllers apply steer with penalty)
# keep on screen
self.pos.x = max(0, min(WIDTH - 1, self.pos.x))
self.pos.y = max(0, min(HEIGHT - 1, self.pos.y))

# compute angular progress around track center (for lap progress)
dx = self.pos.x - TRACK_CENTER.x
dy = self.pos.y - TRACK_CENTER.y
angle = math.degrees(math.atan2(-dy, dx)) % 360
# convert to a progress metric (0...1) around the track
self.progress = angle / 360.0

# finish line crossing detection: compare above/below top y of inner ellipse
top_y = TRACK_CENTER.y - INNER_RY
# crossing occurs when y <= top_y +/- a small tolerance and x near center
x_near = abs(self.pos.x - TRACK_CENTER.x) < INNER_RX * 0.6
crossing = (self.pos.y < top_y + 6) and x_near
if crossing and not self.previous_cross:
# when crossing heading roughly forward (y increasing after crossing)
self.laps += 1
self.previous_cross = crossing

def draw(self, surf):
# draw rotated rectangle
points = self.rect_points()
pg_points = [(p.x, p.y) for p in points]
pygame.draw.polygon(surf, self.color, pg_points)
# small nose to indicate forward
rad = math.radians(self.angle)
nose = self.pos + Vector2(math.cos(rad), -math.sin(rad)) * (self.length/2 + 4)
pygame.draw.circle(surf, (0,0,0), (int(nose.x), int(nose.y)), 3)

class PlayerCar(Car):
def handle_input(self, keys):
# acceleration / braking
if keys[pygame.K_UP] or keys[pygame.K_w]:
self.speed += self.acceleration
if keys[pygame.K_DOWN] or keys[pygame.K_s]:
self.speed -= self.brake_decel
# steering scaled by speed
steer_amount = self.steer_speed * (max(0.2, abs(self.speed) / self.max_speed))
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.angle += steer_amount
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.angle -= steer_amount
# clamp
self.speed = max(-self.max_speed/2, min(self.max_speed, self.speed))

class AICar(Car):
def __init__(self, color, pos, waypoints, angle=0.0, max_speed=3.7):
super().__init__(color, pos, angle, max_speed)
self.waypoints = waypoints
self.current_wp = 0

def update_ai(self):
target = self.waypoints[self.current_wp]
vec_to = target - self.pos
dist = vec_to.length()
desired_angle = math.degrees(math.atan2(-vec_to.y, vec_to.x)) % 360
# angle difference
diff = (desired_angle - self.angle + 180) % 360 - 180
# steer toward desired angle
steer = max(-self.steer_speed, min(self.steer_speed, -diff))
# steering effectiveness lowered on grass
if not is_on_road(self.pos):
steer *= 0.5
self.angle += steer * 0.6 # AI steering smoothing
# throttle control
if abs(diff) < 30:
self.speed += self.acceleration * (1.2 if is_on_road(self.pos) else 0.3)
else:
# slow down for sharp turns
self.speed -= self.brake_decel * 0.8
# reach waypoint
if dist < 30:
self.current_wp = (self.current_wp + 1) % len(self.waypoints)
# clamp speed
self.speed = max(-self.max_speed/3, min(self.max_speed, self.speed))

# create circular waypoints around TRACK_CENTER for AI
def generate_waypoints(n=24, radius_x=(OUTER_RX+INNER_RX)/2, radius_y=(OUTER_RY+INNER_RY)/2, jitter=10):
pts = []
for i in range(n):
a = math.radians(i * (360.0 / n))
r_x = radius_x + random.uniform(-jitter, jitter)
r_y = radius_y + random.uniform(-jitter, jitter)
x = TRACK_CENTER.x + math.cos(a) * r_x
y = TRACK_CENTER.y + math.sin(a) * r_y
pts.append(Vector2(x, y))
return pts

WAYPOINTS = generate_waypoints(32)

def reset_race():
# player start just above start line, facing right
player = PlayerCar((0, 120, 230), Vector2(TRACK_CENTER.x - 40, TRACK_CENTER.y - INNER_RY - 30), angle=0.0, max_speed=5.0)
opponents = []
for i in range(NUM_OPPONENTS):
offset = (i + 1) * 40
pos = Vector2(TRACK_CENTER.x + offset, TRACK_CENTER.y - INNER_RY - 30)
color = (200 - i*40, 50 + i*50, 50 + i*30)
ai = AICar(color, pos, WAYPOINTS, angle=0.0, max_speed=4.2 - i*0.4)
opponents.append(ai)
return player, opponents

player, opponents = reset_race()

def draw_hud(surf, player, opponents, finished=False):
lines = [
f"Speed: {player.speed:.1f}",
f"Laps: {min(player.laps, LAPS_TO_FINISH)}/{LAPS_TO_FINISH}",
]
for i, line in enumerate(lines):
txt = FONT.render(line, True, (0,0,0))
surf.blit(txt, (10, 10 + i*22))
# ranking by laps then progress
racers = [player] + opponents
def rank_key(c):
return (c.laps, c.progress)
racers_sorted = sorted(racers, key=rank_key, reverse=True)
for i, r in enumerate(racers_sorted):
label = "You" if r is player else f"CPU{i}"
stat = f"{i+1}. {label} L{r.laps} P{int(r.speed)}"
txt = FONT.render(stat, True, (0,0,0))
surf.blit(txt, (WIDTH - 220, 10 + i*22))
if finished:
msg = FONT.render("Race finished! Press R to restart.", True, (0,0,0))
surf.blit(msg, (WIDTH//2 - 120, 20))

def draw_instructions(surf):
lines = [
"Controls: Arrow keys or WASD to drive. R to restart.",
"Drive on the gray road. Red line = start/finish.",
f"Complete {LAPS_TO_FINISH} laps to finish."
]
for i, t in enumerate(lines):
txt = FONT.render(t, True, (10,10,10))
surf.blit(txt, (10, HEIGHT - 70 + i*20))

def mainloop():
global player, opponents
running = True
finished = False
finish_timer = 0
while running:
dt = CLOCK.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if event.key == pygame.K_r:
player, opponents = reset_race()
finished = False
finish_timer = 0

keys = pygame.key.get_pressed()
if not finished:
player.handle_input(keys)
player.update()
for ai in opponents:
ai.update_ai()
ai.update()

# check finish condition
if not finished and player.laps >= LAPS_TO_FINISH:
finished = True
finish_timer = pygame.time.get_ticks()

# draw scene
SCREEN.fill(SKY)
SCREEN.blit(TRACK_SURF, (0, 0))

# optional: draw waypoints for debug (comment out later)
# for wp in WAYPOINTS:
# pygame.draw.circle(SCREEN, (0,0,0), (int(wp.x), int(wp.y)), 3)

# draw racers in order by their y for simple painter's ordering
all_cars = opponents + [player]
all_cars.sort(key=lambda c: c.pos.y) # draws from top to bottom
for car in all_cars:
car.draw(SCREEN)

draw_hud(SCREEN, player, opponents, finished)
draw_instructions(SCREEN)

# if finished show winner info
if finished:
winner = sorted([player] + opponents, key=lambda c: (c.laps, c.progress), reverse=True)[0]
if winner is player:
msg = "You won! Press R to race again."
else:
msg = "CPU won. Press R to try again."
big = pygame.font.SysFont(None, 40).render(msg, True, (255, 0, 0))
SCREEN.blit(big, (WIDTH//2 - big.get_width()//2, HEIGHT//2 - 20))

pygame.display.flip()

pygame.quit()
sys.exit()

if __name__ == "__main__":
mainloop()
Binary file added __pycache__/README.cpython-312.pyc
Binary file not shown.