Initial commit: CRT photobooth

This commit is contained in:
2026-07-18 15:17:37 -04:00
commit 8af59ac11f
11 changed files with 1373 additions and 0 deletions

106
screensaver.py Normal file
View File

@ -0,0 +1,106 @@
"""Fish tank screensaver.
Burn-in safe for the CRT: the background is pure black and every drawn
element is always in motion. Fish images are loaded from the fish folder
and are assumed to face right; a fish swimming left gets flipped
horizontally. Bubbles rise from each fish's mouth.
"""
import math
import random
import pygame
FISH_MIN_H, FISH_MAX_H = 60, 140
FISH_MIN_SPEED, FISH_MAX_SPEED = 40, 130 # px/sec
BUBBLE_COLOR = (150, 200, 255)
class Bubble:
def __init__(self, x, y):
self.x = x
self.y = y
self.radius = random.uniform(2, 6)
self.speed = random.uniform(30, 70)
self.wobble_phase = random.uniform(0, math.tau)
self.wobble_amp = random.uniform(2, 8)
def update(self, dt):
self.y -= self.speed * dt
self.wobble_phase += dt * 3
def draw(self, screen):
x = self.x + math.sin(self.wobble_phase) * self.wobble_amp
pygame.draw.circle(screen, BUBBLE_COLOR, (int(x), int(self.y)),
int(self.radius), width=1)
class Fish:
def __init__(self, image, screen_size):
self.screen_w, self.screen_h = screen_size
height = random.randint(FISH_MIN_H, FISH_MAX_H)
width = int(image.get_width() * height / image.get_height())
self.image_right = pygame.transform.smoothscale(image, (width, height))
self.image_left = pygame.transform.flip(self.image_right, True, False)
self.w, self.h = width, height
self.speed = random.uniform(FISH_MIN_SPEED, FISH_MAX_SPEED)
self.direction = random.choice([-1, 1])
self.x = random.uniform(0, self.screen_w - width)
self.base_y = random.uniform(0, self.screen_h - height)
self.bob_phase = random.uniform(0, math.tau)
self.bob_amp = random.uniform(5, 20)
self.bubble_timer = random.uniform(1, 4)
@property
def y(self):
return self.base_y + math.sin(self.bob_phase) * self.bob_amp
def mouth_pos(self):
mouth_x = self.x + self.w if self.direction > 0 else self.x
return mouth_x, self.y + self.h * 0.45
def update(self, dt, bubbles):
self.x += self.speed * self.direction * dt
self.bob_phase += dt * random.uniform(0.8, 1.2)
# Wrap around: swim fully off one edge, re-enter from the other
# at a fresh depth so fish never sit still or trace fixed lines.
if self.direction > 0 and self.x > self.screen_w:
self.x = -self.w
self.base_y = random.uniform(0, self.screen_h - self.h)
elif self.direction < 0 and self.x < -self.w:
self.x = self.screen_w
self.base_y = random.uniform(0, self.screen_h - self.h)
self.bubble_timer -= dt
if self.bubble_timer <= 0:
self.bubble_timer = random.uniform(1.5, 5)
mx, my = self.mouth_pos()
for _ in range(random.randint(1, 3)):
bubbles.append(Bubble(mx + random.uniform(-4, 4),
my + random.uniform(-4, 4)))
def draw(self, screen):
image = self.image_right if self.direction > 0 else self.image_left
screen.blit(image, (int(self.x), int(self.y)))
class FishTank:
def __init__(self, screen_size, fish_images, num_fish):
self.fish = [Fish(random.choice(fish_images), screen_size)
for _ in range(num_fish)]
self.bubbles = []
def update(self, dt):
for fish in self.fish:
fish.update(dt, self.bubbles)
for bubble in self.bubbles:
bubble.update(dt)
self.bubbles = [b for b in self.bubbles if b.y > -10]
def draw(self, screen):
screen.fill((0, 0, 0))
for bubble in self.bubbles:
bubble.draw(screen)
for fish in self.fish:
fish.draw(screen)