Загрузка данных


import math
import random
import sys
import pygame

# ==========================================
# КОНФИГУРАЦИЯ И КОНСТАНТЫ
# ==========================================
SCREEN_WIDTH = 1024
SCREEN_HEIGHT = 768
ROAD_WIDTH = 2000
SEGMENT_LENGTH = 200
CAMERA_HEIGHT = 1000
DRAW_DISTANCE = 300
FIELD_OF_VIEW = 60
CAMERA_DEPTH = 1.0 / math.tan((FIELD_OF_VIEW / 2.0) * math.pi / 180.0)

COLORS = {
    'SKY': (110, 160, 230),
    'GRASS_LIGHT': (16, 170, 16),
    'GRASS_DARK': (0, 145, 0),
    'ROAD_LIGHT': (105, 105, 105),
    'ROAD_DARK': (98, 98, 98),
    'RUMBLE_LIGHT': (255, 255, 255),
    'RUMBLE_DARK': (220, 20, 20),
    'LANE': (255, 255, 255),
    'WHITE': (255, 255, 255),
    'BLACK': (20, 20, 20),
    'YELLOW': (255, 220, 0),
    'RED': (220, 20, 20)
}

# ==========================================
# ЧАСТИЦЫ И ЭФФЕКТЫ
# ==========================================
class Particle:
    def __init__(self, x, y, dx, dy, color, size, life):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        self.color = color
        self.size = size
        self.life = life

    def update(self, dt):
        self.x += self.dx * dt * 60
        self.y += self.dy * dt * 60
        self.size = max(0.5, self.size - 0.1 * dt * 60)
        self.life -= 1 * dt * 60

    def draw(self, surface):
        if self.life > 0:
            pygame.draw.circle(surface, self.color, (int(self.x), int(self.y)), int(self.size))

# ==========================================
# СЕГМЕНТ ТРАССЫ И ПРОЕКЦИЯ
# ==========================================
class Segment:
    def __init__(self, index):
        self.index = index
        self.p1 = {'world': {'x': 0, 'y': 0, 'z': 0}, 'screen': {'x': 0, 'y': 0, 'w': 0}}
        self.p2 = {'world': {'x': 0, 'y': 0, 'z': 0}, 'screen': {'x': 0, 'y': 0, 'w': 0}}
        self.curve = 0
        self.clip = 0
        self.sprite = None  # Дерево, кактус или знак
        self.sprite_offset = 0
        self.color = self._get_colors()

    def _get_colors(self):
        dark = (self.index // 3) % 2
        return {
            'road': COLORS['ROAD_DARK'] if dark else COLORS['ROAD_LIGHT'],
            'grass': COLORS['GRASS_DARK'] if dark else COLORS['GRASS_LIGHT'],
            'rumble': COLORS['RUMBLE_DARK'] if dark else COLORS['RUMBLE_LIGHT'],
            'lane': COLORS['LANE'] if not dark else COLORS['ROAD_LIGHT']
        }

    def project(self, cam_x, cam_y, cam_z):
        self._project_point(self.p1, cam_x, cam_y, cam_z)
        self._project_point(self.p2, cam_x, cam_y, cam_z)

    def _project_point(self, p, cx, cy, cz):
        world = p['world']
        screen = p['screen']

        tx = world['x'] - cx
        ty = world['y'] - cy
        tz = world['z'] - cz

        if tz <= 0:
            tz = 0.001

        scale = (SCREEN_HEIGHT / 2) / tz
        screen['x'] = int((SCREEN_WIDTH / 2) + (scale * tx))
        screen['y'] = int((SCREEN_HEIGHT / 2) - (scale * ty))
        screen['w'] = int(scale * ROAD_WIDTH)

# ==========================================
# МАШИНЫ ТРАФИКА
# ==========================================
class NPCVehicle:
    def __init__(self, z, offset, speed):
        self.z = z
        self.offset = offset
        self.speed = speed
        self.width = 160
        self.height = 80
        self.color = random.choice([
            (65, 105, 225), (46, 139, 87), (218, 165, 32),
            (148, 0, 211), (255, 140, 0), (200, 200, 200)
        ])

    def update(self, dt, track_length):
        self.z = (self.z + self.speed * dt) % track_length

# ==========================================
# МАШИНА ИГРОКА
# ==========================================
class PlayerCar:
    def __init__(self):
        self.x = 0.0
        self.z = 0.0
        self.speed = 0.0
        self.max_speed = 13500.0
        self.accel = self.max_speed / 4.5
        self.breaking = -self.max_speed * 1.5
        self.decel = -self.max_speed / 5.0
        self.off_road_decel = -self.max_speed / 1.5
        self.turn_speed = 3.5

        self.gear = 1
        self.gears = {
            1: {'max': 3500, 'acc': 1.3},
            2: {'max': 7000, 'acc': 1.0},
            3: {'max': 10500, 'acc': 0.8},
            4: {'max': 13500, 'acc': 0.55}
        }

# ==========================================
# ОСНОВНОЙ КЛАСС ИГРЫ
# ==========================================
class Game:
    def __init__(self):
        pygame.init()
        pygame.font.init()
        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        pygame.display.set_caption("Retro Highway Racer")
        self.clock = pygame.time.Clock()

        self.font_small = pygame.font.SysFont("monospace", 18, bold=True)
        self.font_medium = pygame.font.SysFont("monospace", 28, bold=True)
        self.font_big = pygame.font.SysFont("monospace", 46, bold=True)

        self.segments = []
        self.track_length = 0
        self.build_track()

        self.player = PlayerCar()
        self.traffic = []
        self.spawn_traffic(45)

        self.particles = []
        self.lap_time = 0.0
        self.best_lap = 0.0
        self.checkpoint_time = 40.0
        self.game_over = False

    def build_track(self):
        self.segments.clear()

        def add_road(enter, hold, leave, curve, elevation):
            start_y = 0 if len(self.segments) == 0 else self.segments[-1].p2['world']['y']
            total = enter + hold + leave
            for i in range(total):
                idx = len(self.segments)
                seg = Segment(idx)
                seg.p1['world']['z'] = idx * SEGMENT_LENGTH
                seg.p2['world']['z'] = (idx + 1) * SEGMENT_LENGTH

                # Расчет кривизны поворота
                if i < enter:
                    c = curve * (i / enter)
                    seg_y = start_y + (elevation - start_y) * (i / enter)
                elif i < enter + hold:
                    c = curve
                    seg_y = elevation
                else:
                    c = curve * (1 - (i - enter - hold) / leave)
                    seg_y = elevation + (0 - elevation) * ((i - enter - hold) / leave)

                seg.curve = c
                seg.p1['world']['y'] = start_y if i == 0 else self.segments[-1].p2['world']['y']
                seg.p2['world']['y'] = seg_y

                # Генерация придорожных объектов
                if idx % 15 == 0 and random.random() < 0.8:
                    seg.sprite = 'tree'
                    seg.sprite_offset = -1.6 if random.random() < 0.5 else 1.6

                self.segments.append(seg)

        # Конструктор трассы (вход, ровно, выход, кривизна, высота холма)
        add_road(50, 100, 50, 0, 0)
        add_road(80, 100, 80, 4, 1800)
        add_road(60, 80, 60, -3.5, -1200)
        add_road(100, 120, 100, 0, 2200)
        add_road(70, 90, 70, 5, 0)
        add_road(80, 80, 80, -4.5, -1500)
        add_road(60, 60, 60, 0, 0)

        self.track_length = len(self.segments) * SEGMENT_LENGTH

    def spawn_traffic(self, count):
        for _ in range(count):
            z = random.uniform(2000, self.track_length - 1000)
            offset = random.choice([-0.7, -0.35, 0.35, 0.7])
            speed = random.uniform(3500, 7500)
            self.traffic.append(NPCVehicle(z, offset, speed))

    def find_segment(self, z):
        return self.segments[int(z / SEGMENT_LENGTH) % len(self.segments)]

    def handle_input(self, dt):
        if self.game_over:
            return

        keys = pygame.key.get_pressed()

        # Автоматическая КПП
        for g, data in self.player.gears.items():
            if self.player.speed <= data['max']:
                self.player.gear = g
                break

        current_gear = self.player.gears[self.player.gear]

        # Газ / Тормоз
        if keys[pygame.K_UP] or keys[pygame.K_w]:
            self.player.speed += self.player.accel * current_gear['acc'] * dt
        elif keys[pygame.K_DOWN] or keys[pygame.K_s]:
            self.player.speed += self.player.breaking * dt
        else:
            self.player.speed += self.player.decel * dt

        # Руление
        speed_factor = self.player.speed / self.player.max_speed
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            self.player.x -= self.player.turn_speed * speed_factor * dt
        if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            self.player.x += self.player.turn_speed * speed_factor * dt

        # Поведение на траве / обочине
        if (self.player.x < -1.0 or self.player.x > 1.0) and self.player.speed > 500:
            self.player.speed += self.player.off_road_decel * dt
            if random.random() < 0.4:
                self.particles.append(Particle(
                    SCREEN_WIDTH // 2 + random.randint(-40, 40),
                    SCREEN_HEIGHT - 30,
                    random.uniform(-1, 1), -1.5,
                    (170, 170, 170), random.randint(4, 8), 15
                ))

        self.player.speed = max(0.0, min(self.player.speed, self.player.max_speed))

    def update(self, dt):
        if self.game_over:
            return

        self.handle_input(dt)
        old_z = self.player.z
        self.player.z = (self.player.z + self.player.speed * dt) % self.track_length

        # Фиксация нового круга
        if self.player.z < old_z:
            if self.best_lap == 0 or self.lap_time < self.best_lap:
                self.best_lap = self.lap_time
            self.lap_time = 0.0
            self.checkpoint_time += 30.0  # Бонусное время за круг

        self.lap_time += dt
        self.checkpoint_time -= dt
        if self.checkpoint_time <= 0:
            self.game_over = True

        player_seg = self.find_segment(self.player.z)
        # Центробежный снос
        self.player.x -= (player_seg.curve * (self.player.speed / self.player.max_speed) * 1.8) * dt

        # Обновление NPC
        for car in self.traffic:
            car.update(dt, self.track_length)

            # Проверка коллизии с игроком
            dist_z = abs(car.z - self.player.z)
            if dist_z < 120 and abs(self.player.x - car.offset) < 0.3:
                self.player.speed = min(self.player.speed * 0.3, 2000)
                # Искры и дым от удара
                for _ in range(20):
                    self.particles.append(Particle(
                        SCREEN_WIDTH // 2, SCREEN_HEIGHT - 60,
                        random.uniform(-4, 4), random.uniform(-4, 1),
                        (255, random.randint(50, 200), 0), random.randint(4, 9), 25
                    ))

        # Обновление частиц
        for p in self.particles[:]:
            p.update(dt)
            if p.life <= 0:
                self.particles.remove(p)

    def draw_polygon(self, color, x1, y1, x2, y2, x3, y3, x4, y4):
        pygame.draw.polygon(self.screen, color, [(x1, y1), (x2, y2), (x3, y3), (x4, y4)])

    def draw_segment(self, seg):
        p1 = seg.p1['screen']
        p2 = seg.p2['screen']

        # Трава
        pygame.draw.rect(self.screen, seg.color['grass'], (0, p2['y'], SCREEN_WIDTH, p1['y'] - p2['y']))

        # Обочина
        r1 = p1['w'] / 6
        r2 = p2['w'] / 6
        self.draw_polygon(seg.color['rumble'],
                          p1['x'] - p1['w'] - r1, p1['y'],
                          p1['x'] - p1['w'], p1['y'],
                          p2['x'] - p2['w'], p2['y'],
                          p2['x'] - p2['w'] - r2, p2['y'])
        self.draw_polygon(seg.color['rumble'],
                          p1['x'] + p1['w'] + r1, p1['y'],
                          p1['x'] + p1['w'], p1['y'],
                          p2['x'] + p2['w'], p2['y'],
                          p2['x'] + p2['w'] + r2, p2['y'])

        # Полотно асфальта
        self.draw_polygon(seg.color['road'],
                          p1['x'] - p1['w'], p1['y'],
                          p1['x'] + p1['w'], p1['y'],
                          p2['x'] + p2['w'], p2['y'],
                          p2['x'] - p2['w'], p2['y'])

        # Разметка
        l1 = p1['w'] / 32
        l2 = p2['w'] / 32
        self.draw_polygon(seg.color['lane'],
                          p1['x'] - l1, p1['y'],
                          p1['x'] + l1, p1['y'],
                          p2['x'] + l2, p2['y'],
                          p2['x'] - l2, p2['y'])

    def draw_scenery(self, seg):
        if not seg.sprite:
            return

        scale = (SCREEN_HEIGHT / 2) / (seg.p1['world']['z'] - self.player.z if seg.p1['world']['z'] > self.player.z else (self.track_length - self.player.z + seg.p1['world']['z']))
        if scale <= 0:
            return

        sx = int((SCREEN_WIDTH / 2) + (scale * (seg.sprite_offset * ROAD_WIDTH - self.player.x * ROAD_WIDTH)))
        sy = seg.p1['screen']['y']

        w = int(120 * scale * 300)
        h = int(240 * scale * 300)

        if 0 <= sy < SCREEN_HEIGHT and -w < sx < SCREEN_WIDTH + w:
            # Ствол
            pygame.draw.rect(self.screen, (101, 67, 33), (sx - w // 8, sy - h, w // 4, h))
            # Крона
            pygame.draw.circle(self.screen, (34, 139, 34), (sx, sy - h), w // 2)

    def draw_player(self):
        x = SCREEN_WIDTH // 2
        y = SCREEN_HEIGHT - 35
        w = 150
        h = 75

        # Кузов спорткара
        pygame.draw.polygon(self.screen, COLORS['RED'], [
            (x - w // 2, y),
            (x - w // 3, y - h),
            (x + w // 3, y - h),
            (x + w // 2, y)
        ])
        # Заднее стекло / крыша
        pygame.draw.polygon(self.screen, (40, 40, 40), [
            (x - w // 3 + 8, y - h + 12),
            (x - w // 4 + 8, y - h * 1.3),
            (x + w // 4 - 8, y - h * 1.3),
            (x + w // 3 - 8, y - h + 12)
        ])
        # Задний диффузор и стоп-сигналы
        pygame.draw.rect(self.screen, COLORS['BLACK'], (x - w // 2 + 10, y - 25, w - 20, 18))
        pygame.draw.circle(self.screen, (255, 30, 30), (x - w // 2 + 25, y - 16), 8)
        pygame.draw.circle(self.screen, (255, 30, 30), (x + w // 2 - 25, y - 16), 8)

    def draw_traffic(self, base_seg):
        for car in self.traffic:
            car_seg = self.find_segment(car.z)
            if base_seg.index <= car_seg.index < base_seg.index + DRAW_DISTANCE:
                dz = car.z - self.player.z if car.z > self.player.z else (self.track_length - self.player.z + car.z)
                scale = (SCREEN_HEIGHT / 2) / dz
                if scale <= 0:
                    continue

                sx = int((SCREEN_WIDTH / 2) + (scale * (car.offset * ROAD_WIDTH - self.player.x * ROAD_WIDTH)))
                sy = int((SCREEN_HEIGHT / 2) - (scale * (car_seg.p1['world']['y'] - CAMERA_HEIGHT)))

                w = int(car.width * scale * 300)
                h = int(car.height * scale * 300)

                if 0 <= sy < SCREEN_HEIGHT and 0 <= sx < SCREEN_WIDTH:
                    # Корпус NPC
                    pygame.draw.rect(self.screen, car.color, (sx - w // 2, sy - h, w, h))
                    # Огни
                    pygame.draw.rect(self.screen, (255, 50, 50), (sx - w // 2 + 4, sy - h // 3, w // 5, h // 4))
                    pygame.draw.rect(self.screen, (255, 50, 50), (sx + w // 2 - 4 - w // 5, sy - h // 3, w // 5, h // 4))

    def render(self):
        # Отрисовка неба и градиента горизонта
        self.screen.fill(COLORS['SKY'])
        pygame.draw.rect(self.screen, (90, 130, 200), (0, SCREEN_HEIGHT // 2, SCREEN_WIDTH, SCREEN_HEIGHT // 2))

        base_seg = self.find_segment(self.player.z)
        max_y = SCREEN_HEIGHT

        x = 0
        dx = -(base_seg.curve * 0.1)

        # Отрисовка дороги от горизонта к игроку
        for n in range(DRAW_DISTANCE):
            seg = self.segments[(base_seg.index + n) % len(self.segments)]
            cam_z = self.player.z - (self.track_length if (base_seg.index + n) >= len(self.segments) else 0)

            seg.project(self.player.x * ROAD_WIDTH - x, CAMERA_HEIGHT, cam_z)
            x += dx
            dx += seg.curve

            if seg.p1['screen']['y'] >= max_y or seg.p2['screen']['y'] >= seg.p1['screen']['y']:
                continue

            self.draw_segment(seg)
            self.draw_scenery(seg)
            max_y = seg.p1['screen']['y']

        self.draw_traffic(base_seg)
        self.draw_player()

        for p in self.particles:
            p.draw(self.screen)

        # Рендеринг HUD
        speed_kmh = int((self.player.speed / self.player.max_speed) * 290)
        rpm = int((self.player.speed / self.player.gears[self.player.gear]['max']) * 7800)

        hud_speed = self.font_big.render(f"{speed_kmh} KM/H", True, COLORS['YELLOW'])
        hud_gear = self.font_small.render(f"GEAR: {self.player.gear}   RPM: {rpm}", True, COLORS['WHITE'])
        hud_timer = self.font_medium.render(f"TIME: {max(0.0, self.checkpoint_time):.1f}s", True, COLORS['WHITE'])
        hud_lap = self.font_small.render(f"LAP: {self.lap_time:.2f}s", True, (200, 200, 200))
        hud_best = self.font_small.render(f"BEST: {self.best_lap:.2f}s" if self.best_lap > 0 else "BEST: --:--", True, (200, 200, 200))

        self.screen.blit(hud_speed, (30, SCREEN_HEIGHT - 70))
        self.screen.blit(hud_gear, (35, SCREEN_HEIGHT - 100))
        self.screen.blit(hud_timer, (30, 30))
        self.screen.blit(hud_lap, (30, 70))
        self.screen.blit(hud_best, (30, 95))

        if self.game_over:
            over_text = self.font_big.render("TIME OVER", True, COLORS['RED'])
            sub_text = self.font_small.render("Press 'R' to Restart", True, COLORS['WHITE'])
            self.screen.blit(over_text, (SCREEN_WIDTH // 2 - over_text.get_width() // 2, SCREEN_HEIGHT // 2 - 40))
            self.screen.blit(sub_text, (SCREEN_WIDTH // 2 - sub_text.get_width() // 2, SCREEN_HEIGHT // 2 + 20))

        pygame.display.flip()

    def reset_game(self):
        self.player = PlayerCar()
        self.lap_time = 0.0
        self.checkpoint_time = 40.0
        self.game_over = False
        self.particles.clear()

    def run(self):
        while True:
            dt = self.clock.tick(60) / 1000.0
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                if event.type == pygame.KEYDOWN and event.key == pygame.K_r and self.game_over:
                    self.reset_game()

            self.update(dt)
            self.render()

if __name__ == "__main__":
    game = Game()
    game.run()