#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 🌟 20种豪华烟花造型｜双层连环爆炸｜旋转+缩放+超长渐变拖尾｜星空流星完整版
import pygame
import random
import math
import sys
from collections import deque
from enum import Enum

# ==================== 全局常量 ====================
SCREEN_WIDTH = 1366
SCREEN_HEIGHT = 768
BACKGROUND = (0, 0, 0)
FPS = 60
GRAVITY = 0.12
DRAG = 0.97
MAX_PARTICLES = 6000
STAR_COUNT = 450
METEOR_RATE = 0.008
TRAIL_LENGTH = 26
ROTATE_SPEED_MIN = -0.45
ROTATE_SPEED_MAX = 0.45

# ==================== 颜色库 ====================
COLORS = [
    (255,40,40),(40,255,40),(40,40,255),
    (255,255,60),(255,60,255),(60,255,255),
    (255,140,0),(140,0,255),(0,220,180),
    (255,200,100),(200,100,255),(100,255,200),
    (255,180,220),(180,220,255),(255,220,180),
    (255,90,140),(90,200,255),(255,180,0)
]

# ==================== 20种烟花造型 ====================
class ExplosionType(Enum):
    NORMAL = 1
    RING = 2
    SPIRAL = 3
    HEART = 4
    STAR = 5
    CASCADE = 6
    PALM = 7
    CHRYSANTHEMUM = 8
    DOUBLE_SPIRAL = 9
    CROSS = 10
    GRID = 11
    SNOW = 12
    BUTTERFLY = 13
    RAY = 14
    BUBBLE = 15
    SHOCKWAVE = 16
    FLAKE = 17
    CROWN = 18
    COMET = 19
    FIRE_BALL = 20

# ==================== 高级粒子（旋转+缩放+拖尾） ====================
class AdvancedParticle:
    __slots__ = [
        'x','y','vx','vy','color','size','lifetime','max_life',
        'rotate','angle','scale_speed','trail','glow','is_second'
    ]
    def __init__(self, x, y, vx, vy, color, lifetime, is_second=False):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.color = color
        self.size = random.uniform(2.2,5.5) if not is_second else random.uniform(1.0,3.0)
        self.lifetime = lifetime
        self.max_life = lifetime
        self.rotate = random.uniform(ROTATE_SPEED_MIN, ROTATE_SPEED_MAX)
        self.angle = random.uniform(0, math.pi*2)
        self.scale_speed = random.uniform(-0.018, -0.006)
        self.trail = deque(maxlen=TRAIL_LENGTH)
        self.glow = random.choice([True,False,False,False])
        self.is_second = is_second

    def update(self):
        self.vx *= DRAG
        self.vy *= DRAG
        self.vy += GRAVITY
        self.x += self.vx
        self.y += self.vy
        self.angle += self.rotate
        self.size = max(0.3, self.size + self.scale_speed)
        self.lifetime -= 1
        self.trail.append((self.x, self.y))
        return self.lifetime > 0 and -150 < self.x < SCREEN_WIDTH+150 and -150 < self.y < SCREEN_HEIGHT+300

    def draw(self, surface):
        t = self.lifetime / self.max_life
        r = int(self.color[0] * t)
        g = int(self.color[1] * t)
        b = int(self.color[2] * t)
        current_size = self.size * t
        cx, cy = int(self.x), int(self.y)

        for i, (tx, ty) in enumerate(self.trail):
            a = int(90 * (i/len(self.trail)) * t)
            pygame.draw.circle(surface, (max(0,r-60),max(0,g-60),max(0,b-60)),
                               (int(tx),int(ty)), max(0.25, current_size*0.45))
        if self.glow and t>0.4:
            pygame.draw.circle(surface, (r//3,g//3,b//3), (cx,cy), current_size+2.5,1)
        pygame.draw.circle(surface, (r,g,b), (cx,cy), current_size)

# ==================== 烟花火箭 ====================
class Rocket:
    def __init__(self, x, target_y, color, etype):
        self.x = x
        self.y = SCREEN_HEIGHT + 10
        self.target_y = target_y
        self.color = color
        self.etype = etype
        self.speed = random.uniform(-9.5,-6.5)
        self.trail = deque(maxlen=18)
        self.exploded = False

    def update(self):
        self.y += self.speed
        self.speed += 0.085
        self.trail.append((self.x,self.y))
        if self.y <= self.target_y or self.y < 0:
            self.exploded = True
        return not self.exploded

    def draw(self, surface):
        for i,(x,y) in enumerate(self.trail):
            a = int(110*(i/len(self.trail)))
            pygame.draw.circle(surface, (self.color[0]//2,self.color[1]//2,self.color[2]//2),
                               (int(x),int(y)), 1.8+i*0.09)
        pygame.draw.circle(surface, self.color, (int(self.x),int(self.y)), 3.5)

# ==================== 星空 + 流星 ====================
class Star:
    def __init__(self):
        self.x = random.randint(0,SCREEN_WIDTH)
        self.y = random.randint(0,SCREEN_HEIGHT)
        self.vx = random.uniform(-0.35,0.35)
        self.vy = random.uniform(-0.35,0.35)
        self.bright = random.randint(100,255)
        self.speed = random.uniform(0.02,0.09)
        self.dir = 1
        self.size = random.uniform(0.6,2.2)

    def update(self):
        self.x += self.vx
        self.y += self.vy
        self.bright += self.dir*self.speed
        if self.bright>255:self.dir=-1
        if self.bright<100:self.dir=1
        if self.x<0:self.x=SCREEN_WIDTH
        if self.x>SCREEN_WIDTH:self.x=0
        if self.y<0:self.y=SCREEN_HEIGHT
        if self.y>SCREEN_HEIGHT:self.y=0

    def draw(self, surface):
        c=int(self.bright)
        pygame.draw.circle(surface,(c,c,c),(int(self.x),int(self.y)),self.size)

class Meteor:
    def __init__(self):
        self.x=random.choice([0,SCREEN_WIDTH])
        self.y=random.randint(0,SCREEN_HEIGHT//2)
        self.vx=random.uniform(-9,9)if self.x>0 else random.uniform(4,10)
        self.vy=random.uniform(2.2,5.2)
        self.life=95
        self.trail=deque(maxlen=22)

    def update(self):
        self.x+=self.vx
        self.y+=self.vy
        self.trail.append((self.x,self.y))
        self.life-=1
        return self.life>0 and 0<self.x<SCREEN_WIDTH and 0<self.y<SCREEN_HEIGHT

    def draw(self, surface):
        for i,(x,y) in enumerate(self.trail):
            a=int(130*(1-i/len(self.trail))*(self.life/95))
            pygame.draw.line(surface,(a,a,a),(int(x),int(y)),
                             (int(self.trail[i-1][0]),int(self.trail[i-1][1]))if i>0 else(int(x),int(y)),2)
        pygame.draw.circle(surface,(255,255,220),(int(self.x),int(self.y)),2.5)

# ==================== 核心管理器：20种造型 + 双层必爆炸 ====================
class FireworkManager:
    def __init__(self):
        self.rockets=[]
        self.particles=[]
        self.stars=[Star()for _ in range(STAR_COUNT)]
        self.meteors=[]
        self.timer=0

    def launch(self,x=None):
        x=x or random.randint(80,SCREEN_WIDTH-80)
        ty=random.randint(80,SCREEN_HEIGHT//2+180)
        c=random.choice(COLORS)
        e=random.choice(list(ExplosionType))
        self.rockets.append(Rocket(x,ty,c,e))

    def first_explode(self,x,y,etype,color):
        c2=random.choice(COLORS)
        if etype==ExplosionType.NORMAL:
            for _ in range(180):
                a=random.uniform(0,360)
                s=random.uniform(3,9)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(80,160)))
        elif etype==ExplosionType.RING:
            for i in range(240):
                a=360*i/240
                s=random.uniform(5,9)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(90,170)))
        elif etype==ExplosionType.SPIRAL:
            for i in range(320):
                a=math.radians(i*4)
                r=i*0.12
                vx=math.cos(a)*(3+r)
                vy=math.sin(a)*(3+r)
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(80,160)))
        elif etype==ExplosionType.HEART:
            for i in range(240):
                t=math.pi*2*i/240
                sx=16*math.sin(t)**3
                sy=-(13*math.cos(t)-5*math.cos(2*t)-2*math.cos(3*t)-math.cos(4*t))
                vx=sx*0.25+random.uniform(-1,1)
                vy=sy*0.25+random.uniform(-1,1)
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(100,180)))
        elif etype==ExplosionType.STAR:
            for _ in range(220):
                a=random.choice([0,72,144,216,288])+random.uniform(-15,15)
                s=random.uniform(4,10)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(90,160)))
        elif etype==ExplosionType.CASCADE:
            for _ in range(260):
                a=random.uniform(-70,70)
                s=random.uniform(3,8)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s+2
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(80,150)))
        elif etype==ExplosionType.PALM:
            for _ in range(220):
                a=random.uniform(-40,40)
                s=random.uniform(5,11)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s-1
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(100,180)))
        elif etype==ExplosionType.CHRYSANTHEMUM:
            for _ in range(130):
                a=random.uniform(0,360)
                s=random.uniform(6,11)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(110,190)))
            for _ in range(190):
                a=random.uniform(0,360)
                s=random.uniform(2,5)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(60,120)))
        elif etype==ExplosionType.DOUBLE_SPIRAL:
            for i in range(360):
                a=math.radians(i*5)
                r=i*0.1
                vx=math.cos(a)*(2+r)
                vy=math.sin(a)*(2+r)
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(70,140)))
                vx2=math.cos(a+math.pi)*(2+r)
                vy2=math.sin(a+math.pi)*(2+r)
                self.particles.append(AdvancedParticle(x,y,vx2,vy2,color,random.randint(70,140)))
        elif etype==ExplosionType.CROSS:
            for ang in [0,90,180,270]:
                for i in range(60):
                    a=ang+random.uniform(-8,8)
                    s=random.uniform(4,8)
                    vx=math.cos(math.radians(a))*s
                    vy=math.sin(math.radians(a))*s
                    self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(80,150)))
        elif etype==ExplosionType.GRID:
            for dx in [-30,-15,0,15,30]:
                for dy in [-30,-15,0,15,30]:
                    vx=dx*0.2+random.uniform(-1,1)
                    vy=dy*0.2+random.uniform(-1,1)
                    self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(60,120)))
        elif etype==ExplosionType.SNOW:
            for _ in range(200):
                a=random.uniform(0,360)
                s=random.uniform(2,6)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s+0.5
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(90,160)))
        elif etype==ExplosionType.BUTTERFLY:
            for i in range(200):
                t=math.pi*2*i/200
                xd=math.sin(t)*12*math.cos(t)**3
                yd=-math.cos(t)*12*math.sin(t)**3
                vx=xd*0.25+random.uniform(-0.8,0.8)
                vy=yd*0.25+random.uniform(-0.8,0.8)
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(90,170)))
        elif etype==ExplosionType.RAY:
            for i in range(16):
                base=360*i/16
                for j in range(12):
                    a=base+random.uniform(-6,6)
                    s=random.uniform(5,10)
                    vx=math.cos(math.radians(a))*s
                    vy=math.sin(math.radians(a))*s
                    self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(80,150)))
        elif etype==ExplosionType.BUBBLE:
            for i in range(3):
                r=1+i*2
                for j in range(120):
                    a=360*j/120
                    s=r+random.uniform(0,1)
                    vx=math.cos(math.radians(a))*s
                    vy=math.sin(math.radians(a))*s
                    self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(70,130)))
        elif etype==ExplosionType.SHOCKWAVE:
            for i in range(360):
                a=math.radians(i)
                vx=math.cos(a)*random.uniform(7,11)
                vy=math.sin(a)*random.uniform(7,11)
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(60,110)))
        elif etype==ExplosionType.FLAKE:
            for i in range(6):
                base=60*i
                for j in range(25):
                    a=base+random.uniform(-10,10)
                    s=random.uniform(3,7)
                    vx=math.cos(math.radians(a))*s
                    vy=math.sin(math.radians(a))*s
                    self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(80,140)))
        elif etype==ExplosionType.CROWN:
            for i in range(180):
                a=180+i/2
                s=random.uniform(4,8)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s-1
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(90,160)))
        elif etype==ExplosionType.COMET:
            for _ in range(200):
                a=random.uniform(-40,40)
                s=random.uniform(6,12)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s-1
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(90,170)))
        elif etype==ExplosionType.FIRE_BALL:
            for _ in range(250):
                a=random.uniform(0,360)
                s=random.uniform(2,7)
                vx=math.cos(math.radians(a))*s
                vy=math.sin(math.radians(a))*s
                self.particles.append(AdvancedParticle(x,y,vx,vy,color,random.randint(100,180)))
        self.second_explode(x,y,c2)

    def second_explode(self,cx,cy,color):
        for _ in range(140):
            angle=random.uniform(0,360)
            speed=random.uniform(2,6)
            ox=random.uniform(-30,30)
            oy=random.uniform(-30,30)
            vx=math.cos(math.radians(angle))*speed
            vy=math.sin(math.radians(angle))*speed+0.5
            self.particles.append(AdvancedParticle(cx+ox,cy+oy,vx,vy,color,random.randint(50,110),is_second=True))

    def update(self):
        self.timer+=1
        if self.timer%random.randint(20,60)==0:self.launch()
        if random.random()<METEOR_RATE and len(self.meteors)<4:self.meteors.append(Meteor())
        for s in self.stars:s.update()
        for m in self.meteors[:]:
            if not m.update():self.meteors.remove(m)
        for r in self.rockets[:]:
            if not r.update():
                self.first_explode(r.x,r.y,r.etype,r.color)
                self.rockets.remove(r)
        for p in self.particles[:]:
            if not p.update():self.particles.remove(p)
        while len(self.particles)>MAX_PARTICLES:self.particles.pop(0)

    def draw(self,surface):
        for s in self.stars:s.draw(surface)
        for m in self.meteors:m.draw(surface)
        for r in self.rockets:r.draw(surface)
        for p in self.particles:p.draw(surface)

# ==================== 主程序 ====================
def main():
    pygame.init()
    screen=pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))
    pygame.display.set_caption("🌟 20种豪华烟花｜双层连环爆炸｜星空完整版 🌟")
    clock=pygame.time.Clock()
    manager=FireworkManager()
    running=True
    while running:
        clock.tick(FPS)
        screen.fill(BACKGROUND)
        for event in pygame.event.get():
            if event.type==pygame.QUIT:running=False
            if event.type==pygame.MOUSEBUTTONDOWN:
                manager.launch(pygame.mouse.get_pos()[0])
            if event.type==pygame.KEYDOWN:
                if event.key==pygame.K_ESCAPE:running=False
        manager.update()
        manager.draw(screen)
        pygame.display.flip()
    pygame.quit()
    sys.exit()

if __name__=="__main__":
    main()