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


import pygame
import random
import math
import array

pygame.init()
pygame.mixer.init(frequency=22050, size=-16, channels=1, buffer=512)

WIDTH = 900
HEIGHT = 700
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("CYBER VOID // NEON OVERDRIVE: ULTIMATE")
clock = pygame.time.Clock()
FPS = 60

# Цветовая гамма
BG_COLOR = (6, 7, 14)
CYAN = (0, 245, 255)
PINK = (255, 30, 140)
YELLOW = (255, 235, 60)
ORANGE = (255, 120, 20)
PURPLE = (180, 50, 255)
GREEN = (40, 255, 130)
RED = (255, 60, 60)
WHITE = (255, 255, 255)

font_xs = pygame.font.SysFont("consolas", 14, bold=True)
font_sm = pygame.font.SysFont("consolas", 18, bold=True)
font_md = pygame.font.SysFont("consolas", 24, bold=True)
font_lg = pygame.font.SysFont("consolas", 56, bold=True)

# Процедурный генератор 8-bit звуков без внешних файлов
def make_sound(freq_start, freq_end, duration_sec=0.1, wave_type="square", vol=0.15):
    sample_rate = 22050
    n_samples = int(sample_rate * duration_sec)
    buf = array.array('h')
    for i in range(n_samples):
        t = i / sample_rate
        cur_freq = freq_start + (freq_end - freq_start) * (i / n_samples)
        if wave_type == "square":
            val = 1.0 if math.sin(2 * math.pi * cur_freq * t) > 0 else -1.0
        elif wave_type == "noise":
            val = random.uniform(-1.0, 1.0)
        else: # sine
            val = math.sin(2 * math.pi * cur_freq * t)
        
        envelope = 1.0 - (i / n_samples)
        sample = int(val * envelope * 32767 * vol)
        buf.append(sample)
    return pygame.mixer.Sound(buf)

try:
    snd_pew = make_sound(880, 440, 0.08, "square", 0.08)
    snd_rocket = make_sound(200, 60, 0.22, "noise", 0.18)
    snd_boom = make_sound(160, 40, 0.35, "noise", 0.25)
    snd_power = make_sound(400, 1200, 0.2, "sine", 0.2)
    snd_emp = make_sound(300, 80, 0.6, "square", 0.3)
except Exception:
    class DummySound:
        def play(self): pass
    snd_pew = snd_rocket = snd_boom = snd_power = snd_emp = DummySound()

def draw_glow_circle(surf, color, center, radius, glow_mult=2):
    r_g = radius * glow_mult
    g_surf = pygame.Surface((r_g * 2, r_g * 2), pygame.SRCALPHA)
    pygame.draw.circle(g_surf, (*color, 35), (r_g, r_g), r_g)
    pygame.draw.circle(g_surf, (*color, 110), (r_g, r_g), int(radius * 1.3))
    pygame.draw.circle(g_surf, color, (r_g, r_g), radius)
    surf.blit(g_surf, (center[0] - r_g, center[1] - r_g), special_flags=pygame.BLEND_ADD)

class DamageText:
    def __init__(self, x, y, text, color=WHITE):
        self.x = x + random.randint(-15, 15)
        self.y = y
        self.text = str(text)
        self.color = color
        self.life = 35

    def update(self):
        self.y -= 1.3
        self.life -= 1

    def draw(self, surf):
        alpha = min(255, self.life * 8)
        rendered = font_sm.render(self.text, True, self.color)
        rendered.set_alpha(alpha)
        surf.blit(rendered, (self.x, self.y))

class Player:
    def __init__(self):
        self.x = WIDTH // 2
        self.y = HEIGHT - 100
        self.speed = 6.5
        self.cooldown = 0
        self.missile_cooldown = 0
        self.shield = 2
        self.max_shield = 3
        self.bombs = 2
        self.weapon_level = 1
        self.angle = 0
        self.trail = []

    def update(self, keys, mouse_pos):
        # Движение
        if (keys[pygame.K_a] or keys[pygame.K_LEFT]) and self.x > 30:
            self.x -= self.speed
        if (keys[pygame.K_d] or keys[pygame.K_RIGHT]) and self.x < WIDTH - 30:
            self.x += self.speed
        if (keys[pygame.K_w] or keys[pygame.K_UP]) and self.y > 40:
            self.y -= self.speed
        if (keys[pygame.K_s] or keys[pygame.K_DOWN]) and self.y < HEIGHT - 40:
            self.y += self.speed

        # Вращение корпуса к курсору
        dx = mouse_pos[0] - self.x
        dy = mouse_pos[1] - self.y
        self.angle = math.degrees(math.atan2(-dy, dx)) - 90

        # Реактивный хвост
        self.trail.append((self.x, self.y, 14))
        for i in range(len(self.trail) - 1, -1, -1):
            x, y, r = self.trail[i]
            if r <= 1:
                self.trail.pop(i)
            else:
                self.trail[i] = (x + random.uniform(-0.8, 0.8), y + 2.5, r - 1.2)

        if self.cooldown > 0:
            self.cooldown -= 1
        if self.missile_cooldown > 0:
            self.missile_cooldown -= 1

    def draw(self, surf):
        for x, y, r in self.trail:
            pygame.draw.circle(surf, ORANGE, (int(x), int(y)), int(r))

        ship_surf = pygame.Surface((60, 60), pygame.SRCALPHA)
        pts = [(30, 6), (10, 50), (30, 40), (50, 50)]
        pygame.draw.polygon(ship_surf, CYAN, pts, 3)
        pygame.draw.line(ship_surf, WHITE, (30, 10), (30, 36), 2)

        rot_surf = pygame.transform.rotate(ship_surf, self.angle)
        rect = rot_surf.get_rect(center=(self.x, self.y))
        surf.blit(rot_surf, rect.topleft)

        if self.shield > 0:
            col = CYAN if self.shield == 1 else (PURPLE if self.shield == 2 else GREEN)
            draw_glow_circle(surf, col, (int(self.x), int(self.y)), 30, glow_mult=2)

class Bullet:
    def __init__(self, x, y, vx, vy, color=YELLOW, is_enemy=False, is_missile=False, dmg=1):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.color = color
        self.is_enemy = is_enemy
        self.is_missile = is_missile
        self.dmg = dmg
        self.radius = 8 if is_missile else (5 if is_enemy else 4)

    def update(self):
        self.x += self.vx
        self.y += self.vy

    def draw(self, surf):
        pygame.draw.circle(surf, self.color, (int(self.x), int(self.y)), self.radius)
        if self.is_missile:
            draw_glow_circle(surf, RED, (int(self.x), int(self.y)), 6, 2)

class Enemy:
    def __init__(self, enemy_type, mult=1.0):
        self.type = enemy_type  # 'drone', 'interceptor', 'assault'
        self.x = random.randint(50, WIDTH - 50)
        self.y = -30
        self.timer = 0
        if self.type == "drone":
            self.hp = 2
            self.speed = 3.0 * mult
            self.radius = 18
            self.color = PINK
        elif self.type == "interceptor":
            self.hp = 1
            self.speed = 5.2 * mult
            self.radius = 14
            self.color = ORANGE
        elif self.type == "assault":
            self.hp = 6
            self.speed = 1.8 * mult
            self.radius = 24
            self.color = RED

    def update(self, bullets, player_x, player_y):
        self.y += self.speed
        self.timer += 1
        # Assault стреляет прицельным лазером
        if self.type == "assault" and self.timer % 65 == 0:
            angle = math.atan2(player_y - self.y, player_x - self.x)
            bullets.append(Bullet(self.x, self.y, math.cos(angle)*5, math.sin(angle)*5, RED, True))

    def draw(self, surf):
        if self.type == "drone":
            pts = [(self.x, self.y - self.radius), (self.x - self.radius, self.y + self.radius),
                   (self.x, self.y + 8), (self.x + self.radius, self.y + self.radius)]
            pygame.draw.polygon(surf, self.color, pts, 2)
        elif self.type == "interceptor":
            pts = [(self.x, self.y + self.radius), (self.x - self.radius//2, self.y - self.radius),
                   (self.x + self.radius//2, self.y - self.radius)]
            pygame.draw.polygon(surf, self.color, pts, 2)
        else:
            pygame.draw.circle(surf, self.color, (int(self.x), int(self.y)), self.radius, 2)
            pygame.draw.circle(surf, WHITE, (int(self.x), int(self.y)), 6)

class Boss:
    def __init__(self, btype=1):
        self.btype = btype
        self.x = WIDTH // 2
        self.y = -120
        self.target_y = 125
        self.timer = 0
        self.dir = 2.8
        self.spiral = 0.0

        if btype == 1:
            self.name = "CORVETTE PROMETHEUS"
            self.max_hp = 100
            self.radius = 55
            self.color = PINK
        elif btype == 2:
            self.name = "DREADNOUGHT VORTEX"
            self.max_hp = 180
            self.radius = 65
            self.color = PURPLE
        else:
            self.name = "CYBER CORE TITAN"
            self.max_hp = 260
            self.radius = 75
            self.color = RED

        self.hp = self.max_hp

    def update(self, bullets, player_x, player_y):
        if self.y < self.target_y:
            self.y += 2
        else:
            self.timer += 1
            if self.btype == 1:
                self.x += self.dir
                if self.x < 110 or self.x > WIDTH - 110:
                    self.dir = -self.dir
                if self.timer % 32 == 0:
                    for a in [-0.4, -0.2, 0, 0.2, 0.4]:
                        bullets.append(Bullet(self.x, self.y + 35, math.sin(a)*6, math.cos(a)*6, PINK, True))
            elif self.btype == 2:
                self.x = WIDTH // 2 + math.sin(self.timer * 0.035) * 260
                if self.timer % 5 == 0:
                    self.spiral += 0.42
                    bullets.append(Bullet(self.x, self.y, math.cos(self.spiral)*5.5, math.sin(self.spiral)*5.5, PURPLE, True))
            else: # Titan
                self.x = WIDTH // 2 + math.cos(self.timer * 0.02) * 180
                if self.timer % 40 == 0:
                    ang = math.atan2(player_y - self.y, player_x - self.x)
                    for spread in [-0.2, 0, 0.2]:
                        bullets.append(Bullet(self.x, self.y, math.cos(ang+spread)*7, math.sin(ang+spread)*7, RED, True, dmg=2))

    def draw(self, surf):
        draw_glow_circle(surf, self.color, (int(self.x), int(self.y)), self.radius - 10, glow_mult=2)
        pygame.draw.circle(surf, WHITE, (int(self.x), int(self.y)), self.radius, 3)

class PowerUp:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.type = random.choice(["weapon", "shield", "bomb"])
        self.radius = 13
        self.pulse = 0
        if self.type == "weapon":
            self.color = YELLOW
            self.label = "W"
        elif self.type == "shield":
            self.color = CYAN
            self.label = "S"
        else:
            self.color = ORANGE
            self.label = "B"

    def update(self):
        self.y += 2
        self.pulse += 0.1

    def draw(self, surf):
        r = int(self.radius + math.sin(self.pulse) * 2.5)
        draw_glow_circle(surf, self.color, (int(self.x), int(self.y)), r, glow_mult=2)
        txt = font_sm.render(self.label, True, WHITE)
        surf.blit(txt, (self.x - txt.get_width() // 2, self.y - txt.get_height() // 2))

class Particle:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
        ang = random.uniform(0, math.pi * 2)
        spd = random.uniform(2, 7.5)
        self.vx = math.cos(ang) * spd
        self.vy = math.sin(ang) * spd
        self.life = random.randint(20, 36)

    def update(self):
        self.x += self.vx
        self.y += self.vy
        self.life -= 1

    def draw(self, surf):
        if self.life > 0:
            pygame.draw.circle(surf, self.color, (int(self.x), int(self.y)), max(1, self.life // 6))

def main():
    player = Player()
    bullets = []
    enemies = []
    particles = []
    powerups = []
    dmg_texts = []
    boss = None

    score = 0
    combo = 0
    combo_timer = 0
    boss_cycle = 1
    next_boss_score = 250
    shake = 0
    game_over = False

    stars = [[random.randint(0, WIDTH), random.randint(0, HEIGHT), random.choice([1, 2, 3])] for _ in range(110)]

    running = True
    while running:
        clock.tick(FPS)
        keys = pygame.key.get_pressed()
        mouse_pos = pygame.mouse.get_pos()
        mouse_pressed = pygame.mouse.get_pressed()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if game_over and event.key == pygame.K_r:
                    main()
                    return
                # EMP Бомба
                if not game_over and event.key == pygame.K_e and player.bombs > 0:
                    player.bombs -= 1
                    shake = 30
                    snd_emp.play()
                    bullets = [b for b in bullets if not b.is_enemy]
                    for e in enemies:
                        score += 20
                        for _ in range(12):
                            particles.append(Particle(e.x, e.y, ORANGE))
                    enemies.clear()
                    if boss:
                        boss.hp -= 35
                        dmg_texts.append(DamageText(boss.x, boss.y, 35, YELLOW))

        if not game_over:
            player.update(keys, mouse_pos)

            # Стрельба на ЛКМ (Primary Gun)
            if mouse_pressed[0] and player.cooldown == 0:
                snd_pew.play()
                ang = math.atan2(mouse_pos[1] - player.y, mouse_pos[0] - player.x)
                spd = 16

                if player.weapon_level == 1:
                    bullets.append(Bullet(player.x, player.y, math.cos(ang)*spd, math.sin(ang)*spd, YELLOW))
                elif player.weapon_level == 2:
                    p1 = (player.x + math.cos(ang + math.pi/2)*10, player.y + math.sin(ang + math.pi/2)*10)
                    p2 = (player.x + math.cos(ang - math.pi/2)*10, player.y + math.sin(ang - math.pi/2)*10)
                    bullets.append(Bullet(p1[0], p1[1], math.cos(ang)*spd, math.sin(ang)*spd, YELLOW))
                    bullets.append(Bullet(p2[0], p2[1], math.cos(ang)*spd, math.sin(ang)*spd, YELLOW))
                elif player.weapon_level == 3:
                    for spread in [-0.18, 0, 0.18]:
                        bullets.append(Bullet(player.x, player.y, math.cos(ang+spread)*spd, math.sin(ang+spread)*spd, GREEN))
                else: # Level 4 Overdrive
                    for spread in [-0.3, -0.15, 0, 0.15, 0.3]:
                        bullets.append(Bullet(player.x, player.y, math.cos(ang+spread)*spd, math.sin(ang+spread)*spd, CYAN))
                player.cooldown = 9

            # Тяжёлая ракета на ПКМ (Secondary Weapon)
            if mouse_pressed[2] and player.missile_cooldown == 0:
                snd_rocket.play()
                ang = math.atan2(mouse_pos[1] - player.y, mouse_pos[0] - player.x)
                bullets.append(Bullet(player.x, player.y, math.cos(ang)*11, math.sin(ang)*11, ORANGE, is_missile=True, dmg=5))
                player.missile_cooldown = 35

            # Таймер комбо
            if combo_timer > 0:
                combo_timer -= 1
            else:
                combo = 0

            # Спавн боссов
            if score >= next_boss_score and boss is None:
                b_type = (boss_cycle % 3)
                boss = Boss(3 if b_type == 0 else b_type)
                next_boss_score += 350
                boss_cycle += 1

            # Спавн врагов волнами
            if boss is None and random.randint(1, 35) == 1:
                e_choice = random.choices(["drone", "interceptor", "assault"], weights=[60, 25, 15])[0]
                enemies.append(Enemy(e_choice, 1.0 + score / 800))

            # Обновление снарядов
            for b in bullets[:]:
                b.update()
                if b.y < -30 or b.y > HEIGHT + 30 or b.x < -30 or b.x > WIDTH + 30:
                    bullets.remove(b)
                    continue

                if b.is_enemy:
                    if math.hypot(b.x - player.x, b.y - player.y) < 24:
                        bullets.remove(b)
                        if player.shield > 0:
                            player.shield -= 1
                            shake = 10
                            snd_boom.play()
                        else:
                            game_over = True
                            shake = 35
                            snd_boom.play()
                else:
                    # Попадание по мобам
                    for e in enemies[:]:
                        if math.hypot(b.x - e.x, b.y - e.y) < e.radius + b.radius:
                            e.hp -= b.dmg
                            dmg_texts.append(DamageText(e.x, e.y, b.dmg, YELLOW if not b.is_missile else ORANGE))
                            if not b.is_missile and b in bullets:
                                bullets.remove(b)
                            if e.hp <= 0:
                                snd_boom.play()
                                for _ in range(16):
                                    particles.append(Particle(e.x, e.y, e.color))
                                if random.random() < 0.25:
                                    powerups.append(PowerUp(e.x, e.y))
                                enemies.remove(e)
                                combo += 1
                                combo_timer = 90
                                score += 10 * min(5, combo)
                                shake = 4
                            break

                    # Попадание по боссу
                    if boss and b in bullets:
                        if math.hypot(b.x - boss.x, b.y - boss.y) < boss.radius + b.radius:
                            boss.hp -= b.dmg
                            dmg_texts.append(DamageText(boss.x, boss.y, b.dmg, RED))
                            if not b.is_missile:
                                bullets.remove(b)
                            if boss.hp <= 0:
                                snd_boom.play()
                                for _ in range(65):
                                    particles.append(Particle(boss.x, boss.y, boss.color))
                                score += 300
                                shake = 35
                                boss = None
                            break

            if boss:
                boss.update(bullets, player.x, player.y)

            # Обновление мобов
            for e in enemies[:]:
                e.update(bullets, player.x, player.y)
                if math.hypot(e.x - player.x, e.y - player.y) < e.radius + 20:
                    if player.shield > 0:
                        player.shield -= 1
                        enemies.remove(e)
                        shake = 12
                    else:
                        game_over = True
                        shake = 35
                elif e.y > HEIGHT + 40:
                    enemies.remove(e)

            # Подбор бонусов
            for p in powerups[:]:
                p.update()
                if math.hypot(p.x - player.x, p.y - player.y) < 32:
                    snd_power.play()
                    if p.type == "weapon":
                        player.weapon_level = min(4, player.weapon_level + 1)
                    elif p.type == "shield":
                        player.shield = min(player.max_shield, player.shield + 1)
                    elif p.type == "bomb":
                        player.bombs = min(3, player.bombs + 1)
                    powerups.remove(p)
                elif p.y > HEIGHT:
                    powerups.remove(p)

        # Тексты урона и частицы
        for t in dmg_texts[:]:
            t.update()
            if t.life <= 0:
                dmg_texts.remove(t)
        for pt in particles[:]:
            pt.update()
            if pt.life <= 0:
                particles.remove(pt)

        # Движение звёзд
        for s in stars:
            s[1] += s[2] * 1.1
            if s[1] > HEIGHT:
                s[1] = 0
                s[0] = random.randint(0, WIDTH)

        # Холст с эффектом тряски экрана
        canvas = pygame.Surface((WIDTH, HEIGHT))
        canvas.fill(BG_COLOR)

        for s in stars:
            col = (90, 100, 140) if s[2] == 1 else (180, 210, 255)
            pygame.draw.circle(canvas, col, (int(s[0]), int(s[1])), s[2] // 2 + 1)

        for pt in particles: pt.draw(canvas)
        for p in powerups: p.draw(canvas)
        for b in bullets: b.draw(canvas)
        for e in enemies: e.draw(canvas)
        if boss: boss.draw(canvas)
        if not game_over: player.draw(canvas)
        for t in dmg_texts: t.draw(canvas)

        # Прицел на позиции мыши
        mx, my = mouse_pos
        pygame.draw.circle(canvas, CYAN, (mx, my), 10, 1)
        pygame.draw.line(canvas, CYAN, (mx - 14, my), (mx + 14, my), 1)
        pygame.draw.line(canvas, CYAN, (mx, my - 14), (mx, my + 14), 1)

        # HUD / Интерфейс
        canvas.blit(font_md.render(f"SCORE: {score}", True, CYAN), (25, 20))
        if combo > 1:
            canvas.blit(font_sm.render(f"COMBO: x{combo}", True, YELLOW), (25, 52))

        canvas.blit(font_sm.render(f"WEAPON LVL: {player.weapon_level}/4", True, GREEN), (25, 80))
        canvas.blit(font_sm.render(f"SHIELDS: {'▲ ' * player.shield}", True, CYAN), (25, 105))
        canvas.blit(font_sm.render(f"EMP BOMBS [E]: {'■ ' * player.bombs}", True, ORANGE), (25, 130))

        # Перезарядка ракеты (ПКМ)
        m_bar_w = 100
        m_fill = int((1.0 - (player.missile_cooldown / 35)) * m_bar_w)
        pygame.draw.rect(canvas, (40, 40, 50), (25, 160, m_bar_w, 8))
        pygame.draw.rect(canvas, ORANGE, (25, 160, m_fill, 8))
        canvas.blit(font_xs.render("ROCKET [RMB]", True, WHITE), (135, 156))

        # Полоса HP босса
        if boss:
            bar_w = 460
            bx = WIDTH // 2 - bar_w // 2
            by = 25
            ratio = max(0, boss.hp / boss.max_hp)
            pygame.draw.rect(canvas, (40, 20, 30), (bx, by, bar_w, 14))
            pygame.draw.rect(canvas, boss.color, (bx, by, int(bar_w * ratio), 14))
            pygame.draw.rect(canvas, WHITE, (bx, by, bar_w, 14), 2)
            btxt = font_sm.render(f"{boss.name} [{int(boss.hp)}/{boss.max_hp}]", True, WHITE)
            canvas.blit(btxt, (WIDTH // 2 - btxt.get_width() // 2, by + 18))

        if game_over:
            t1 = font_lg.render("SYSTEM FAILURE", True, PINK)
            t2 = font_md.render("PRESS 'R' TO RESTART OPERATION", True, WHITE)
            canvas.blit(t1, (WIDTH // 2 - t1.get_width() // 2, HEIGHT // 2 - 50))
            canvas.blit(t2, (WIDTH // 2 - t2.get_width() // 2, HEIGHT // 2 + 25))

        offset = [0, 0]
        if shake > 0:
            offset = [random.randint(-shake, shake), random.randint(-shake, shake)]
            shake = max(0, shake - 2)

        screen.blit(canvas, offset)
        pygame.display.flip()

    pygame.quit()

if __name__ == "__main__":
    main()