diff --git a/__pycache__/particle.cpython-311.pyc b/__pycache__/particle.cpython-311.pyc new file mode 100644 index 0000000..cd7a2be Binary files /dev/null and b/__pycache__/particle.cpython-311.pyc differ diff --git a/main.py b/main.py index 6a755c0..ccd4ff0 100644 --- a/main.py +++ b/main.py @@ -3,59 +3,74 @@ import random from particle import Particle pygame.init() - -# Set up display and clock. screen = pygame.display.set_mode((800, 600)) -pygame.display.set_caption("Particle Test") +pygame.display.set_caption("Fireplace Simulation") clock = pygame.time.Clock() particles = [] +# Define the fireplace area (a simple rectangle to simulate a brick fireplace) +fireplace_rect = pygame.Rect(300, 400, 200, 200) # x, y, width, height + running = True while running: - dt = clock.tick(60) / 1000.0 # Delta time in seconds. + dt = clock.tick() / 1000.0 # Delta time in seconds - # Event loop: quit on window close or spawn new particles on mouse click. for event in pygame.event.get(): if event.type == pygame.QUIT: running = False - elif event.type == pygame.MOUSEBUTTONDOWN: - # Create several particles at the mouse position. - for _ in range(10): - pos = pygame.mouse.get_pos() - vel = (random.uniform(-100, 100), random.uniform(-100, 100)) - p = Particle( - pos=pos, - vel=vel, - acceleration=(0, 0), - lifetime=2.0, - decay_rate=1, - color=( - random.randint(100, 255), - random.randint(100, 255), - random.randint(100, 255) - ), - size=5, - size_decay=2, - glow=True, - glow_intensity=1.0, - friction=0.1, - gravity=50, - bounce=True, - bounce_damping=0.7, - random_spread=10, - spin=0, - spin_rate=random.uniform(-180, 180), - spin_decay=0.5, - trail=True, - trail_length=15, - fade=True, - shape='circle' - ) - particles.append(p) # Clear the screen. screen.fill((0, 0, 0)) + + # Draw the fireplace background: + # Fill with a dark brick color and draw a border to simulate the fireplace frame. + # pygame.draw.rect(screen, (70, 20, 20), fireplace_rect) # Fireplace interior + # pygame.draw.rect(screen, (150, 50, 50), fireplace_rect, 5) # Fireplace border + + # Determine a random fire origin along the bottom edge inside the fireplace. + + + # Spawn fire particles from the fire origin. + particles_to_spawn = (16000 - len(particles)) + + + + for _ in range(particles_to_spawn): + fire_origin = ( + random.uniform(0, 800), + 600 + ) + fire_particle = Particle( + pos=(fire_origin[0] + random.uniform(-5, 5), fire_origin[1] + random.uniform(-2, 2)), + vel=(random.uniform(-20, 20), random.uniform(-80, -120)), # Upward motion + lifetime=1.5, + decay_rate=1, + size=random.uniform(5, 8), + size_decay=5, + glow=True, + glow_intensity=1.0, + friction=0.05, + gravity=-30, # Negative gravity makes the particle rise + fade=True, + particle_type="fire" + ) + particles.append(fire_particle) + + # smoke_particle = Particle( + # pos=(fire_origin[0] + random.uniform(-10, 10), fire_origin[1] + random.uniform(-2, 2)), + # vel=(random.uniform(-10, 10), random.uniform(-40, -60)), # Slower upward motion + # lifetime=2.5, + # decay_rate=0.8, + # size=random.uniform(8, 12), + # size_decay=1, + # glow=False, + # friction=0.02, + # gravity=-10, + # fade=True, + # particle_type="smoke" + # ) + # particles.append(smoke_particle) # Update and draw each particle. for particle in particles[:]: @@ -63,7 +78,7 @@ while running: particle.draw(screen) if particle.is_dead(): particles.remove(particle) - + pygame.display.flip() pygame.quit() diff --git a/particle.py b/particle.py index 711123a..3bde614 100644 --- a/particle.py +++ b/particle.py @@ -1,6 +1,5 @@ import pygame import random -import math class Particle: def __init__( @@ -8,15 +7,15 @@ class Particle: pos, vel=(0, 0), acceleration=(0, 0), - lifetime=2.0, # seconds the particle lives - decay_rate=1, # how fast the lifetime decreases + lifetime=1.0, # seconds the particle lives + decay_rate=1.0, # how fast the lifetime decreases color=(255, 255, 255), size=5, - size_decay=0, # rate at which the size decreases + size_decay=5, # rate at which the size decreases glow=False, glow_intensity=0, # multiplier for glow radius - friction=0, # slows down velocity over time - gravity=0, # constant acceleration downward + friction=0.05, # slows down velocity over time + gravity=0, # constant acceleration (negative makes it rise) bounce=False, bounce_damping=0.5, # energy loss when bouncing off boundaries random_spread=0, # randomness added to velocity each update @@ -25,20 +24,19 @@ class Particle: spin_decay=0, # how spin_rate slows down trail=False, trail_length=10, # how many previous positions to store - fade=False, # if True, the particle will fade (alpha decreases) - shape='circle' # could be 'circle' or 'square' + fade=True, # if True, the particle will fade (alpha decreases) + shape='circle', # could be 'circle' or 'square' + particle_type="generic" # "fire" or "smoke" ): - # Position, velocity, and acceleration are stored as vectors. + # Position, velocity, and acceleration as vectors. self.pos = pygame.math.Vector2(pos) self.vel = pygame.math.Vector2(vel) self.acceleration = pygame.math.Vector2(acceleration) - # Lifetime and decay self.lifetime = lifetime self.initial_lifetime = lifetime self.decay_rate = decay_rate - # Appearance settings self.color = color self.size = size self.initial_size = size @@ -46,7 +44,6 @@ class Particle: self.fade = fade self.alpha = 255 - # Effects options self.glow = glow self.glow_intensity = glow_intensity self.friction = friction @@ -55,44 +52,61 @@ class Particle: self.bounce_damping = bounce_damping self.random_spread = random_spread - # Spin (if using rotated images or shapes) self.spin = spin self.spin_rate = spin_rate self.spin_decay = spin_decay - # Trail settings: stores previous positions to create a trailing effect self.trail = trail self.trail_length = trail_length self.positions = [self.pos.copy()] if trail else [] - # Shape of the particle ('circle' or 'square') self.shape = shape + # Particle type for specialized behavior. + self.particle_type = particle_type + def update(self, dt, screen_rect): - """ - dt: Delta time in seconds. - screen_rect: The rectangle representing screen boundaries (for bounce). - """ - # Decrease lifetime + # Decrease lifetime. self.lifetime -= self.decay_rate * dt if self.lifetime < 0: self.lifetime = 0 - # Add random variation to velocity if enabled. + # Compute life ratio (1 at birth, 0 at death). + life_ratio = self.lifetime / self.initial_lifetime if self.initial_lifetime else 0 + + # Update color based on particle type. + if self.particle_type == "fire": + # Realistic fire uses a multi-stage color gradient: + # At birth: bright white-yellow, mid-life: vivid orange, end: dark red. + if life_ratio > 0.5: + factor = (life_ratio - 0.5) / 0.5 # factor from 1 to 0 as life_ratio goes from 1 -> 0.5 + r = 255 + g = int(255 * factor + 180 * (1 - factor)) + b = int(240 * factor + 50 * (1 - factor)) + else: + factor = life_ratio / 0.5 # factor from 1 to 0 as life_ratio goes from 0.5 -> 0 + r = int(255 * factor + 150 * (1 - factor)) + g = int(180 * factor) + b = int(50 * factor) + self.color = (r, g, b) + elif self.particle_type == "smoke": + # Smoke transitions from a semi-transparent dark gray to a light gray. + start_shade = 100 + end_shade = 230 + shade = int(start_shade * life_ratio + end_shade * (1 - life_ratio)) + self.color = (shade, shade, shade) + + # Optionally add random spread. if self.random_spread: self.vel.x += random.uniform(-self.random_spread, self.random_spread) * dt self.vel.y += random.uniform(-self.random_spread, self.random_spread) * dt - # Apply gravity (as an extra acceleration on the y-axis) + # Apply gravity (negative gravity makes particles rise). self.acceleration.y += self.gravity - # Update velocity using acceleration. + # Update velocity and position. self.vel += self.acceleration * dt - - # Apply friction to slow the particle over time. self.vel *= (1 - self.friction * dt) - - # Update the position based on velocity. self.pos += self.vel * dt # Bounce off screen edges if enabled. @@ -110,55 +124,57 @@ class Particle: elif self.pos.y + self.size > screen_rect.bottom: self.pos.y = screen_rect.bottom - self.size - # Update spin + # Update spin. self.spin += self.spin_rate * dt self.spin_rate *= (1 - self.spin_decay * dt) - # Decrease size if size_decay is set. + # Decrease size. if self.size_decay: self.size = max(0, self.size - self.size_decay * dt) - # Fade out by decreasing alpha based on lifetime. + # Fade out by adjusting alpha. if self.fade: - self.alpha = int(255 * (self.lifetime / self.initial_lifetime)) + self.alpha = int(255 * life_ratio) if self.alpha < 0: self.alpha = 0 - # Update trail history. + # Update trail positions if enabled. if self.trail: self.positions.append(self.pos.copy()) if len(self.positions) > self.trail_length: self.positions.pop(0) + # Reset acceleration for next update. + self.acceleration = pygame.math.Vector2(0, 0) + def draw(self, surface): - # Set drawing color with alpha if fading. + # Apply alpha to color if fading. if self.fade: draw_color = (*self.color, self.alpha) else: draw_color = self.color - # Draw the particle's trail if enabled. + # Optionally draw a trail. if self.trail and len(self.positions) > 1: for i in range(1, len(self.positions)): start = self.positions[i - 1] end = self.positions[i] pygame.draw.line(surface, draw_color, start, end, int(self.size)) - # Draw glow effect if enabled. + # Optionally draw glow. if self.glow: glow_radius = int(self.size * (1 + self.glow_intensity)) - glow_color = (*self.color, self.alpha) if self.fade else self.color + glow_color = (*self.color, self.alpha) glow_surface = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA) pygame.draw.circle(glow_surface, glow_color, (glow_radius, glow_radius), glow_radius) surface.blit(glow_surface, (self.pos.x - glow_radius, self.pos.y - glow_radius), special_flags=pygame.BLEND_ADD) - # Draw the particle shape. + # Draw the particle (default is circle). if self.shape == 'circle': pygame.draw.circle(surface, draw_color, (int(self.pos.x), int(self.pos.y)), int(self.size)) elif self.shape == 'square': rect = pygame.Rect(self.pos.x - self.size, self.pos.y - self.size, self.size * 2, self.size * 2) pygame.draw.rect(surface, draw_color, rect) - # Additional shapes (e.g., triangles, rotated images) can be added here. def is_dead(self): """Return True if the particle should be removed."""