โ–ถ Run Project

๐Ÿ“„ fireworks.py โ† entry point
"""
๐ŸŽ† ASCII Fireworks Show ๐ŸŽ†
Run this in your terminal for a colorful fireworks display!
"""

import random
import time
import math

# ANSI color codes
COLORS = [
    "\033[91m",  # Red
    "\033[92m",  # Green
    "\033[93m",  # Yellow
    "\033[94m",  # Blue
    "\033[95m",  # Magenta
    "\033[96m",  # Cyan
    "\033[97m",  # White
]
RESET = "\033[0m"
CLEAR = "\033[2J\033[H"

PARTICLES = ["โ˜…", "โœฆ", "โ€ข", "โ—†", "โœธ", "โœบ", "โœป", "โ‹", "โœท", "*", "ยท", "+"]

WIDTH = 80
HEIGHT = 24


class Particle:
    def __init__(self, x, y, vx, vy, color, char, lifetime):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.color = color
        self.char = char
        self.lifetime = lifetime
        self.age = 0
        self.gravity = 0.15

    def update(self):
        self.x += self.vx
        self.y += self.vy
        self.vy += self.gravity
        self.vx *= 0.97
        self.age += 1

    def is_alive(self):
        return self.age < self.lifetime and 0 <= self.x < WIDTH and 0 <= self.y < HEIGHT


class Firework:
    def __init__(self):
        self.x = random.randint(10, WIDTH - 10)
        self.launch_y = HEIGHT - 1
        self.target_y = random.randint(3, HEIGHT // 2)
        self.vy = -random.uniform(0.8, 1.4)
        self.y = float(self.launch_y)
        self.color = random.choice(COLORS)
        self.exploded = False
        self.particles = []
        self.trail = []

    def update(self):
        if not self.exploded:
            self.y += self.vy
            self.trail.append((int(self.x), int(self.y)))
            if len(self.trail) > 4:
                self.trail.pop(0)
            if self.y <= self.target_y:
                self.explode()
        else:
            self.particles = [p for p in self.particles if p.is_alive()]
            for p in self.particles:
                p.update()

    def explode(self):
        self.exploded = True
        num_particles = random.randint(20, 35)
        char = random.choice(PARTICLES)
        second_color = random.choice(COLORS)
        for i in range(num_particles):
            angle = (2 * math.pi / num_particles) * i + random.uniform(-0.2, 0.2)
            speed = random.uniform(0.4, 1.6)
            vx = math.cos(angle) * speed
            vy = math.sin(angle) * speed * 0.6
            color = self.color if i % 2 == 0 else second_color
            lifetime = random.randint(12, 22)
            self.particles.append(
                Particle(self.x, self.y, vx, vy, color, char, lifetime)
            )

    def is_alive(self):
        if not self.exploded:
            return True
        return len(self.particles) > 0


def render(fireworks, frame):
    grid = [[" " for _ in range(WIDTH)] for _ in range(HEIGHT)]
    color_grid = [["" for _ in range(WIDTH)] for _ in range(HEIGHT)]

    # Draw ground
    ground_msg = "  ๐ŸŽ† ASCII FIREWORKS SHOW โ€” Press Ctrl+C to exit ๐ŸŽ†  "
    for i, ch in enumerate(ground_msg):
        if i < WIDTH:
            grid[HEIGHT - 1][i] = ch
            color_grid[HEIGHT - 1][i] = "\033[93m"

    for fw in fireworks:
        if not fw.exploded:
            # Draw trail
            trail_chars = ["ยท", "ยท", ":", "|"]
            for idx, (tx, ty) in enumerate(fw.trail):
                if 0 <= ty < HEIGHT and 0 <= tx < WIDTH:
                    grid[ty][tx] = trail_chars[min(idx, len(trail_chars) - 1)]
                    color_grid[ty][tx] = fw.color
            # Draw rocket
            rx, ry = int(fw.x), int(fw.y)
            if 0 <= ry < HEIGHT and 0 <= rx < WIDTH:
                grid[ry][rx] = "^"
                color_grid[ry][rx] = fw.color
        else:
            for p in fw.particles:
                px, py = int(p.x), int(p.y)
                if 0 <= py < HEIGHT and 0 <= px < WIDTH:
                    # Fade effect: use dimmer char near end of life
                    fade = p.age / p.lifetime
                    ch = p.char if fade < 0.6 else ("ยท" if fade < 0.85 else ".")
                    grid[py][px] = ch
                    color_grid[py][px] = p.color

    # Build output string
    lines = []
    for row in range(HEIGHT):
        line = ""
        for col in range(WIDTH):
            ch = grid[row][col]
            color = color_grid[row][col]
            if color:
                line += f"{color}{ch}{RESET}"
            else:
                line += ch
        lines.append(line)

    # Twinkling stars in background
    stars = ["ยท", ".", "*", " "]
    random.seed(frame // 3)  # Slow twinkle
    star_positions = [(random.randint(0, WIDTH - 1), random.randint(0, HEIGHT - 3)) for _ in range(15)]

    output = CLEAR
    for row_idx, line in enumerate(lines):
        output += line + "\n"

    print(output, end="", flush=True)


def main():
    fireworks = []
    frame = 0
    launch_countdown = 0

    # Hide cursor
    print("\033[?25l", end="")

    try:
        while True:
            # Launch new fireworks periodically
            if launch_countdown <= 0:
                count = random.randint(1, 2)
                for _ in range(count):
                    fireworks.append(Firework())
                launch_countdown = random.randint(8, 20)
            else:
                launch_countdown -= 1

            # Update
            fireworks = [fw for fw in fireworks if fw.is_alive()]
            for fw in fireworks:
                fw.update()

            # Render
            render(fireworks, frame)
            frame += 1
            time.sleep(0.06)

    except KeyboardInterrupt:
        print("\033[?25h")  # Restore cursor
        print(CLEAR)
        print("\033[93m๐ŸŽ† Thanks for watching! ๐ŸŽ†\033[0m\n")


if __name__ == "__main__":
    main()