Initial commit: CRT photobooth
This commit is contained in:
106
screensaver.py
Normal file
106
screensaver.py
Normal 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)
|
||||
Reference in New Issue
Block a user