Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import math
- import random
- import sys
- import time
- import pygame
- def clamp(v, lo, hi):
- return lo if v < lo else hi if v > hi else v
- def lerp(a, b, t):
- return a + (b - a) * t
- def color_lerp(c1, c2, t):
- return (
- int(lerp(c1[0], c2[0], t)),
- int(lerp(c1[1], c2[1], t)),
- int(lerp(c1[2], c2[2], t)),
- )
- class Sparks:
- def __init__(self):
- self.items = []
- def spawn(self, x, y):
- # small glowing embers that rise then fade
- self.items.append(
- {
- "x": x + random.uniform(-50, 50),
- "y": y + random.uniform(-10, 10),
- "vx": random.uniform(-20, 20),
- "vy": random.uniform(-220, -120),
- "life": random.uniform(0.45, 1.0),
- "max": None,
- "r": random.uniform(1.3, 2.5),
- }
- )
- self.items[-1]["max"] = self.items[-1]["life"]
- def update_draw(self, surf, dt):
- new_items = []
- for sp in self.items:
- sp["life"] -= dt
- if sp["life"] <= 0:
- continue
- sp["x"] += sp["vx"] * dt
- sp["y"] += sp["vy"] * dt
- sp["vy"] += 320 * dt # gravity
- a = int(255 * clamp(sp["life"] / sp["max"], 0, 1))
- pygame.draw.circle(
- surf,
- (255, 200, 130, a),
- (int(sp["x"]), int(sp["y"])),
- int(sp["r"]),
- )
- new_items.append(sp)
- self.items = new_items
- def main():
- pygame.init()
- # Fullscreen, use desktop resolution
- screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
- pygame.display.set_caption("Fireplace Screensaver")
- clock = pygame.time.Clock()
- W, H = screen.get_size()
- # Screensaver behavior: exit on input movement
- pygame.mouse.set_visible(False)
- pygame.event.set_grab(True)
- # Baseline "no input" state
- last_mouse = pygame.mouse.get_pos()
- last_keys_state = pygame.key.get_pressed()
- start_time = time.time()
- # Fireplace layout (centered, responsive)
- opening_w = int(W * 0.55)
- opening_h = int(H * 0.38)
- opening_x = (W - opening_w) // 2
- opening_y = int(H * 0.50) - opening_h // 2
- frame_pad = int(min(W, H) * 0.06)
- frame_rect = pygame.Rect(
- opening_x - frame_pad,
- opening_y - frame_pad,
- opening_w + frame_pad * 2,
- opening_h + frame_pad * 2,
- )
- opening_rect = pygame.Rect(opening_x, opening_y, opening_w, opening_h)
- # Flame area inside opening
- flame_rect = opening_rect.inflate(-int(opening_w * 0.12), -int(opening_h * 0.10))
- flame_rect.y += int(opening_h * 0.04) # push flame slightly down
- # Pre-make a flame surface for alpha blending
- flame_surf = pygame.Surface((flame_rect.width, flame_rect.height), pygame.SRCALPHA)
- # A subtle vignette inside the opening
- vignette = pygame.Surface((opening_rect.width, opening_rect.height), pygame.SRCALPHA)
- # Colors
- bg = (12, 12, 16)
- frame_outer = (52, 24, 22)
- frame_inner = (32, 14, 14)
- soot = (6, 6, 8)
- # Flame palette
- c_hot = (255, 215, 90)
- c_mid = (255, 130, 35)
- c_cool = (135, 35, 14)
- sparks = Sparks()
- t = 0.0
- # Build vignette once (stacked rounded rects)
- vignette.fill((0, 0, 0, 0))
- for i in range(10):
- alpha = 16
- r = vignette.get_rect().inflate(-i * 18, -i * 14)
- pygame.draw.rect(vignette, (0, 0, 0, alpha), r, border_radius=16)
- running = True
- while running:
- dt = clock.tick(60) / 1000.0
- t += dt
- # Exit on any input (screensaver style)
- for event in pygame.event.get():
- if event.type in (pygame.QUIT,):
- running = False
- elif event.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.MOUSEWHEEL):
- running = False
- elif event.type == pygame.MOUSEMOTION:
- # if they actually moved the mouse, exit
- if event.rel != (0, 0):
- running = False
- keys = pygame.key.get_pressed()
- if keys[pygame.K_ESCAPE]:
- running = False
- # (extra) detect subtle changes
- cur_mouse = pygame.mouse.get_pos()
- if cur_mouse != last_mouse:
- running = False
- last_mouse = cur_mouse
- # Background
- screen.fill(bg)
- # Soft ambient glow around the fireplace
- glow_center = (opening_rect.centerx, opening_rect.bottom - int(opening_rect.height * 0.15))
- for rad, a in [(int(min(W, H) * 0.22), 22), (int(min(W, H) * 0.15), 34), (int(min(W, H) * 0.09), 52)]:
- g = pygame.Surface((rad * 2, rad * 2), pygame.SRCALPHA)
- pygame.draw.circle(g, (255, 120, 30, a), (rad, rad), rad)
- screen.blit(g, (glow_center[0] - rad, glow_center[1] - rad))
- # Frame + opening
- pygame.draw.rect(screen, frame_outer, frame_rect, border_radius=22)
- pygame.draw.rect(screen, frame_inner, frame_rect.inflate(-16, -16), border_radius=18)
- pygame.draw.rect(screen, soot, opening_rect, border_radius=16)
- # Logs (simple)
- log_y = opening_rect.bottom - int(opening_rect.height * 0.26)
- for dx in (-int(opening_rect.width * 0.18), 0, int(opening_rect.width * 0.18)):
- lr = pygame.Rect(0, 0, int(opening_rect.width * 0.34), int(opening_rect.height * 0.11))
- lr.center = (opening_rect.centerx + dx, log_y)
- pygame.draw.ellipse(screen, (54, 28, 18), lr)
- pygame.draw.ellipse(screen, (34, 18, 12), lr.inflate(-8, -6))
- # Flame rendering
- flame_surf.fill((0, 0, 0, 0))
- cols = 120 # more cols = smoother flame
- base_y = flame_rect.height - 6
- # Flicker intensity (slow breathing + noise)
- breath = 0.92 + 0.06 * math.sin(t * 0.8)
- for i in range(cols):
- x = (i + 0.5) / cols * flame_rect.width
- wave = (
- 0.58
- + 0.18 * math.sin(t * 3.2 + i * 0.33)
- + 0.16 * math.sin(t * 5.1 + i * 0.14)
- + 0.06 * (random.random() - 0.5)
- )
- wave = clamp(wave * breath, 0.12, 0.95)
- h = int(wave * flame_rect.height)
- blobs = 7
- for b in range(blobs):
- y = base_y - int((b / blobs) * h)
- r = int(24 * (1.0 - (b / blobs)) + 5)
- tcol = b / blobs
- c1 = color_lerp(c_hot, c_mid, tcol)
- c2 = color_lerp(c_mid, c_cool, tcol)
- col = color_lerp(c1, c2, tcol * 0.85)
- alpha = int(190 * (1.0 - tcol) + 30)
- pygame.draw.circle(flame_surf, (*col, alpha), (int(x), y), r)
- # Occasionally spawn sparks from above the logs
- if random.random() < 0.35:
- sparks.spawn(opening_rect.centerx, log_y - 18)
- # Blit flame into opening
- screen.blit(flame_surf, flame_rect.topleft)
- # Draw sparks on the main screen (so they can rise beyond flame slightly)
- sparks.update_draw(screen, dt)
- # Vignette inside opening for depth
- screen.blit(vignette, opening_rect.topleft)
- pygame.display.flip()
- pygame.event.set_grab(False)
- pygame.mouse.set_visible(True)
- pygame.quit()
- sys.exit()
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment