Fireworks, Physics, and AI: A Coding Battle Between ChatGPT & DeepSeek

·

·

Two AI robot fighting under a firework show

Recently, DeepSeek has been making waves in the AI community with its r1 model. Many developers and AI enthusiasts have been experimenting with it, sharing their thoughts on its ability to generate code and reason through complex tasks. At the same time, OpenAI released its latest ChatGPT o3 models on January 31, 2025, boasting significant improvements in advanced mathematics, logical reasoning, and overall performance.

Curious about how these models compare in a real-world coding challenge, I decided to put them to the test. I asked both ChatGPT o3-mini-high and DeepSeek r1 to generate a Python program to render a firework animation. This blog post details my approach, my observations, and how each model performed in this experiment.

Testing Approach

For this test, I used a straightforward prompt: “Write a Python program to render a firework.”

I ran this prompt through both ChatGPT o3-mini-high and DeepSeek r1 and analyzed their responses, focusing on three key aspects: execution time, correctness, and the realism of the generated animation.

The Results

Firework similation generated by ChatGPT o3-mini-high
Firework similation generated by DeepSeek r1

ChatGPT o3-mini-high’s Response

ChatGPT o3-mini-high impressed me with its speed and accuracy. In just about 15 seconds, it generated a fully functional Python script using the Pygame library. The resulting animation was smooth and visually appealing. The firework particles launched realistically, accelerating as they ascended, then slowing down due to gravity before exploding in multiple directions. Additionally, the explosion effect included fading tails, making the animation closely resemble real fireworks.

Response from ChatGPT o3-mini-high
import pygame
import random
import math

# Screen dimensions and frame rate
WIDTH, HEIGHT = 800, 600
FPS = 60

def random_color():
    """Return a random bright color."""
    return (random.randint(150, 255), random.randint(150, 255), random.randint(150, 255))

class Particle:
    """Represents a particle from an exploded firework with a tail."""
    def __init__(self, x, y, color, velocity, life):
        self.x = x
        self.y = y
        self.vx, self.vy = velocity
        self.color = color
        self.life = life  # Lifetime in frames
        self.trail = []  # List to store previous positions for the tail
        self.max_trail_length = 10  # Maximum length of the tail

    def update(self):
        """Update the particle's position, velocity, and its trail."""
        # Append current position to trail
        self.trail.append((self.x, self.y))
        if len(self.trail) > self.max_trail_length:
            self.trail.pop(0)

        # Apply gravity to the particle's vertical velocity
        self.vy += 0.1
        # Update position
        self.x += self.vx
        self.y += self.vy
        # Decrease life
        self.life -= 1

    def draw(self, screen):
        """Draw the particle along with its tail."""
        if self.life > 0 and len(self.trail) > 0:
            # Base radius based on remaining life (for a fading size effect)
            current_radius = max(1, self.life // 5)
            # Draw the trail: older positions will be drawn fainter and smaller.
            trail_length = len(self.trail)
            for i, pos in enumerate(self.trail):
                # Compute a factor for fading based on the position's index in the trail.
                # The newest point (last in the list) has factor ~1.0.
                factor = (i + 1) / trail_length
                # Compute the radius for this segment of the tail.
                radius = max(1, int(current_radius * factor))
                # Fade the color by scaling the RGB components.
                faded_color = (
                    int(self.color[0] * factor),
                    int(self.color[1] * factor),
                    int(self.color[2] * factor)
                )
                pygame.draw.circle(screen, faded_color, (int(pos[0]), int(pos[1])), radius)

class Firework:
    """Represents a firework that launches upward and explodes into particles."""
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
        # The rocket's initial upward velocity (negative means upward)
        self.vy = -random.uniform(7, 9)
        self.exploded = False
        self.particles = []

    def update(self):
        """Update the firework's state."""
        if not self.exploded:
            # Update the rocket's position
            self.y += self.vy
            # Gravity slows the rocket down
            self.vy += 0.1
            # When upward speed stops, explode!
            if self.vy >= 0:
                self.exploded = True
                self.explode()
        else:
            # Update each explosion particle
            for particle in self.particles:
                particle.update()
            # Remove particles that have faded out
            self.particles = [p for p in self.particles if p.life > 0]

    def explode(self):
        """Generate explosion particles in all directions."""
        num_particles = random.randint(30, 50)
        for _ in range(num_particles):
            # Random angle and speed for each particle
            angle = random.uniform(0, 2 * math.pi)
            speed = random.uniform(1, 5)
            vx = math.cos(angle) * speed
            vy = math.sin(angle) * speed
            # Particle lifetime in frames
            life = random.randint(30, 50)
            self.particles.append(Particle(self.x, self.y, self.color, (vx, vy), life))

    def draw(self, screen):
        """Draw the firework or its explosion particles."""
        if not self.exploded:
            # Draw the rocket as a small circle
            pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), 3)
        else:
            for particle in self.particles:
                particle.draw(screen)

def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Firework Simulation")
    clock = pygame.time.Clock()

    fireworks = []
    running = True

    while running:
        clock.tick(FPS)
        # Event handling
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # Randomly add a new firework
        if random.randint(0, 50) == 0:
            x = random.randint(50, WIDTH - 50)
            fireworks.append(Firework(x, HEIGHT, random_color()))

        # Update all fireworks
        for firework in fireworks:
            firework.update()
        # Remove fireworks that have exploded and whose particles have all faded
        fireworks = [f for f in fireworks if not (f.exploded and len(f.particles) == 0)]

        # Clear the screen (black background)
        screen.fill((0, 0, 0))
        # Draw fireworks
        for firework in fireworks:
            firework.draw(screen)

        pygame.display.flip()

    pygame.quit()

if __name__ == "__main__":
    main()
Expand

DeepSeek r1’s Response

DeepSeek r1 also produced a firework animation using Pygame, but it took a staggering 275 seconds to generate the code. While it successfully created an explosion effect, the physics of the firework were not as refined. The particles launched into the sky at a constant velocity, which is not how real fireworks behave. Additionally, the explosion lacked fading tails, making the animation appear less dynamic and lifelike when compared to ChatGPT’s output.

Response from DeepSeek r1
import pygame
import random
import math

# Initialize Pygame
pygame.init()

# Constants
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
GRAVITY = 0.2
FPS = 30

# Set up the screen
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Firework Display")
clock = pygame.time.Clock()

class Firework:
    def __init__(self):
        self.x = random.randint(0, SCREEN_WIDTH)
        self.y = SCREEN_HEIGHT  # Start at the bottom
        self.target_y = random.randint(SCREEN_HEIGHT // 4, SCREEN_HEIGHT // 2)
        self.velocity = -random.randint(5, 10)  # Move upwards
        self.color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
        self.exploded = False

    def explode(self, particles):
        # Create multiple particles
        for _ in range(100):
            particles.append(Particle(self.x, self.y, self.color))
        self.exploded = True

    def update(self, particles):
        if not self.exploded:
            self.y += self.velocity
            if self.y <= self.target_y:
                self.explode(particles)

class Particle:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        angle = random.uniform(0, 2 * math.pi)
        speed = random.uniform(1, 5)
        self.vx = math.cos(angle) * speed
        self.vy = math.sin(angle) * speed
        self.color = color
        self.timer = 255  # Fade-out timer
        self.radius = 3

    def update(self):
        self.vy += GRAVITY
        self.x += self.vx
        self.y += self.vy
        self.timer -= 5  # Decrease alpha (fade out)
        self.radius = max(0, int(self.timer / 255 * 3))

    def is_alive(self):
        return self.timer > 0

def main():
    running = True
    fireworks = []
    particles = []
    last_launch_time = pygame.time.get_ticks()

    while running:
        # Handle events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # Launch new fireworks periodically
        current_time = pygame.time.get_ticks()
        if current_time - last_launch_time > 1500:  # 1.5 seconds
            fireworks.append(Firework())
            last_launch_time = current_time

        # Update fireworks
        for firework in fireworks[:]:
            firework.update(particles)
            if firework.exploded:
                fireworks.remove(firework)

        # Update particles
        for particle in particles[:]:
            particle.update()
            if not particle.is_alive():
                particles.remove(particle)

        # Draw everything
        screen.fill((0, 0, 0))  # Black background

        # Draw fireworks (pre-explosion)
        for firework in fireworks:
            if not firework.exploded:
                pygame.draw.circle(screen, firework.color, (int(firework.x), int(firework.y)), 3)

        # Draw particles
        for particle in particles:
            # Calculate faded color
            faded_color = (
                particle.color[0] * particle.timer // 255,
                particle.color[1] * particle.timer // 255,
                particle.color[2] * particle.timer // 255
            )
            pygame.draw.circle(screen, faded_color, (int(particle.x), int(particle.y)), particle.radius)

        pygame.display.flip()
        clock.tick(FPS)

    pygame.quit()

if __name__ == "__main__":
    main()
Expand

Key Observations and Comparison

When visually comparing the animations, the differences were striking. ChatGPT o3-mini-high produced an animation that followed real-world physics principles, including acceleration, deceleration, and realistic particle dispersion. The explosion effect, complete with fading tails, made it more visually appealing and natural.

DeepSeek r1, on the other hand, generated a functional but less polished output. The lack of acceleration and deceleration made the firework launch appear unnatural, and the explosion, while visually interesting, lacked the subtle details that make fireworks look real. The absence of particle tails further diminished the realism of the effect.

FeatureChatGPT o3-mini-highDeepSeek r1
Execution TimeAbout 15 secondsAbout 275 seconds
Library UsedPygamePygame
Firework LaunchAcceleration, decelerationConstant velocity (acks physics accuracy)
Explosion ParticlesRandom, with tailsRandom, no tails

How Other ChatGPT Models Compared?

To get a broader sense of how AI models handle this task, I tested a few other versions of ChatGPT:

  • GPT-4o: Chose to use Matplotlib for rendering the firework. While Matplotlib is great for data visualization, it is not ideal for real-time animations, making the output static and uninspiring.
  • ChatGPT o1: Used Pygame, but the generated code only drew simple lines to represent fireworks, making it look quite basic.
  • ChatGPT o3-mini: Produced results similar to DeepSeek r1, indicating that the more advanced o3-mini-high version had a noticeable improvement in rendering animations.
Firework similation generated by ChatGPT 4o
Firework similation generated by ChatGPT o1
Firework similation generated by ChatGPT o3-mini

For those interested, I have uploaded all the AI-generated source code to my GitHub repository for further review.

https://github.com/henryphchan/henryc48.sg-host.com/tree/main/AI-MachineLearning/gen_ai_compare_firework

Conclusion

This experiment highlights the ongoing evolution of AI-generated code and the differences between models. As of February 1, 2025, ChatGPT o3-mini-high clearly outperformed DeepSeek r1 in this specific test, generating a more realistic and visually engaging firework animation. The way it simulated acceleration, deceleration, and explosion dynamics made it the superior choice for this task.

One important takeaway from this experiment is that AI-generated code is not always consistent. The same prompt can produce vastly different results depending on the AI model and version. This means that developers should always test multiple models when working on complex coding tasks to ensure the best possible outcome.

AI models are rapidly evolving, and this test is just one example of how different models approach the same problem in unique ways. While ChatGPT o3-mini-high delivered the best result in this case, the landscape of generative AI is changing fast. Future iterations of these models may further refine their capabilities, bringing even more realism and accuracy to AI-generated code.