tabnation

python fireplace 3

Dec 20th, 2025
24
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.44 KB | None | 0 0
  1. import math
  2. import random
  3. import sys
  4. import time
  5. import pygame
  6.  
  7.  
  8. def clamp(v, lo, hi):
  9. return lo if v < lo else hi if v > hi else v
  10.  
  11.  
  12. def lerp(a, b, t):
  13. return a + (b - a) * t
  14.  
  15.  
  16. def color_lerp(c1, c2, t):
  17. return (
  18. int(lerp(c1[0], c2[0], t)),
  19. int(lerp(c1[1], c2[1], t)),
  20. int(lerp(c1[2], c2[2], t)),
  21. )
  22.  
  23.  
  24. class Sparks:
  25. def __init__(self):
  26. self.items = []
  27.  
  28. def spawn(self, x, y):
  29. # small glowing embers that rise then fade
  30. self.items.append(
  31. {
  32. "x": x + random.uniform(-50, 50),
  33. "y": y + random.uniform(-10, 10),
  34. "vx": random.uniform(-20, 20),
  35. "vy": random.uniform(-220, -120),
  36. "life": random.uniform(0.45, 1.0),
  37. "max": None,
  38. "r": random.uniform(1.3, 2.5),
  39. }
  40. )
  41. self.items[-1]["max"] = self.items[-1]["life"]
  42.  
  43. def update_draw(self, surf, dt):
  44. new_items = []
  45. for sp in self.items:
  46. sp["life"] -= dt
  47. if sp["life"] <= 0:
  48. continue
  49.  
  50. sp["x"] += sp["vx"] * dt
  51. sp["y"] += sp["vy"] * dt
  52. sp["vy"] += 320 * dt # gravity
  53.  
  54. a = int(255 * clamp(sp["life"] / sp["max"], 0, 1))
  55. pygame.draw.circle(
  56. surf,
  57. (255, 200, 130, a),
  58. (int(sp["x"]), int(sp["y"])),
  59. int(sp["r"]),
  60. )
  61. new_items.append(sp)
  62.  
  63. self.items = new_items
  64.  
  65.  
  66. def main():
  67. pygame.init()
  68.  
  69. # Fullscreen, use desktop resolution
  70. screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
  71. pygame.display.set_caption("Fireplace Screensaver")
  72. clock = pygame.time.Clock()
  73.  
  74. W, H = screen.get_size()
  75.  
  76. # Screensaver behavior: exit on input movement
  77. pygame.mouse.set_visible(False)
  78. pygame.event.set_grab(True)
  79.  
  80. # Baseline "no input" state
  81. last_mouse = pygame.mouse.get_pos()
  82. last_keys_state = pygame.key.get_pressed()
  83. start_time = time.time()
  84.  
  85. # Fireplace layout (centered, responsive)
  86. opening_w = int(W * 0.55)
  87. opening_h = int(H * 0.38)
  88. opening_x = (W - opening_w) // 2
  89. opening_y = int(H * 0.50) - opening_h // 2
  90.  
  91. frame_pad = int(min(W, H) * 0.06)
  92. frame_rect = pygame.Rect(
  93. opening_x - frame_pad,
  94. opening_y - frame_pad,
  95. opening_w + frame_pad * 2,
  96. opening_h + frame_pad * 2,
  97. )
  98. opening_rect = pygame.Rect(opening_x, opening_y, opening_w, opening_h)
  99.  
  100. # Flame area inside opening
  101. flame_rect = opening_rect.inflate(-int(opening_w * 0.12), -int(opening_h * 0.10))
  102. flame_rect.y += int(opening_h * 0.04) # push flame slightly down
  103.  
  104. # Pre-make a flame surface for alpha blending
  105. flame_surf = pygame.Surface((flame_rect.width, flame_rect.height), pygame.SRCALPHA)
  106.  
  107. # A subtle vignette inside the opening
  108. vignette = pygame.Surface((opening_rect.width, opening_rect.height), pygame.SRCALPHA)
  109.  
  110. # Colors
  111. bg = (12, 12, 16)
  112. frame_outer = (52, 24, 22)
  113. frame_inner = (32, 14, 14)
  114. soot = (6, 6, 8)
  115.  
  116. # Flame palette
  117. c_hot = (255, 215, 90)
  118. c_mid = (255, 130, 35)
  119. c_cool = (135, 35, 14)
  120.  
  121. sparks = Sparks()
  122. t = 0.0
  123.  
  124. # Build vignette once (stacked rounded rects)
  125. vignette.fill((0, 0, 0, 0))
  126. for i in range(10):
  127. alpha = 16
  128. r = vignette.get_rect().inflate(-i * 18, -i * 14)
  129. pygame.draw.rect(vignette, (0, 0, 0, alpha), r, border_radius=16)
  130.  
  131. running = True
  132. while running:
  133. dt = clock.tick(60) / 1000.0
  134. t += dt
  135.  
  136. # Exit on any input (screensaver style)
  137. for event in pygame.event.get():
  138. if event.type in (pygame.QUIT,):
  139. running = False
  140. elif event.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.MOUSEWHEEL):
  141. running = False
  142. elif event.type == pygame.MOUSEMOTION:
  143. # if they actually moved the mouse, exit
  144. if event.rel != (0, 0):
  145. running = False
  146.  
  147. keys = pygame.key.get_pressed()
  148. if keys[pygame.K_ESCAPE]:
  149. running = False
  150.  
  151. # (extra) detect subtle changes
  152. cur_mouse = pygame.mouse.get_pos()
  153. if cur_mouse != last_mouse:
  154. running = False
  155. last_mouse = cur_mouse
  156.  
  157. # Background
  158. screen.fill(bg)
  159.  
  160. # Soft ambient glow around the fireplace
  161. glow_center = (opening_rect.centerx, opening_rect.bottom - int(opening_rect.height * 0.15))
  162. 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)]:
  163. g = pygame.Surface((rad * 2, rad * 2), pygame.SRCALPHA)
  164. pygame.draw.circle(g, (255, 120, 30, a), (rad, rad), rad)
  165. screen.blit(g, (glow_center[0] - rad, glow_center[1] - rad))
  166.  
  167. # Frame + opening
  168. pygame.draw.rect(screen, frame_outer, frame_rect, border_radius=22)
  169. pygame.draw.rect(screen, frame_inner, frame_rect.inflate(-16, -16), border_radius=18)
  170. pygame.draw.rect(screen, soot, opening_rect, border_radius=16)
  171.  
  172. # Logs (simple)
  173. log_y = opening_rect.bottom - int(opening_rect.height * 0.26)
  174. for dx in (-int(opening_rect.width * 0.18), 0, int(opening_rect.width * 0.18)):
  175. lr = pygame.Rect(0, 0, int(opening_rect.width * 0.34), int(opening_rect.height * 0.11))
  176. lr.center = (opening_rect.centerx + dx, log_y)
  177. pygame.draw.ellipse(screen, (54, 28, 18), lr)
  178. pygame.draw.ellipse(screen, (34, 18, 12), lr.inflate(-8, -6))
  179.  
  180. # Flame rendering
  181. flame_surf.fill((0, 0, 0, 0))
  182.  
  183. cols = 120 # more cols = smoother flame
  184. base_y = flame_rect.height - 6
  185.  
  186. # Flicker intensity (slow breathing + noise)
  187. breath = 0.92 + 0.06 * math.sin(t * 0.8)
  188.  
  189. for i in range(cols):
  190. x = (i + 0.5) / cols * flame_rect.width
  191.  
  192. wave = (
  193. 0.58
  194. + 0.18 * math.sin(t * 3.2 + i * 0.33)
  195. + 0.16 * math.sin(t * 5.1 + i * 0.14)
  196. + 0.06 * (random.random() - 0.5)
  197. )
  198. wave = clamp(wave * breath, 0.12, 0.95)
  199.  
  200. h = int(wave * flame_rect.height)
  201. blobs = 7
  202.  
  203. for b in range(blobs):
  204. y = base_y - int((b / blobs) * h)
  205. r = int(24 * (1.0 - (b / blobs)) + 5)
  206.  
  207. tcol = b / blobs
  208. c1 = color_lerp(c_hot, c_mid, tcol)
  209. c2 = color_lerp(c_mid, c_cool, tcol)
  210. col = color_lerp(c1, c2, tcol * 0.85)
  211.  
  212. alpha = int(190 * (1.0 - tcol) + 30)
  213. pygame.draw.circle(flame_surf, (*col, alpha), (int(x), y), r)
  214.  
  215. # Occasionally spawn sparks from above the logs
  216. if random.random() < 0.35:
  217. sparks.spawn(opening_rect.centerx, log_y - 18)
  218.  
  219. # Blit flame into opening
  220. screen.blit(flame_surf, flame_rect.topleft)
  221.  
  222. # Draw sparks on the main screen (so they can rise beyond flame slightly)
  223. sparks.update_draw(screen, dt)
  224.  
  225. # Vignette inside opening for depth
  226. screen.blit(vignette, opening_rect.topleft)
  227.  
  228. pygame.display.flip()
  229.  
  230. pygame.event.set_grab(False)
  231. pygame.mouse.set_visible(True)
  232. pygame.quit()
  233. sys.exit()
  234.  
  235.  
  236. if __name__ == "__main__":
  237. main()
  238.  
Advertisement
Add Comment
Please, Sign In to add comment