Skip to content

Instantly share code, notes, and snippets.

@maduck
Created February 24, 2018 15:44
Show Gist options
  • Save maduck/3ef9a27d6571c4f66726067ddb8bacad to your computer and use it in GitHub Desktop.
Save maduck/3ef9a27d6571c4f66726067ddb8bacad to your computer and use it in GitHub Desktop.
a very small learning project to have a snowing screensaver.
#!/usr/bin/env python
"""a very small learning project to have a snowing screensaver."""
import random
import pygame.gfxdraw
class Snowflake:
"""Representation of a single snowflake."""
MEAN_SIZE = 3
MAX_SIZE = 15
def __init__(self, max_x, max_y):
"""
create a new snow flake
:param max_x: screen width to draw on
:param max_y: screen height to draw on
"""
self.x = None
self.y = None
self.r = None
self.color = None
self.max_x = max_x
self.max_y = max_y
self.generate_new_parameters()
def generate_new_parameters(self):
"""
reposition a snowflake above the actual screen,
and resize it randomly
:return: None
"""
self.x = random.randrange(self.max_x)
self.y = random.randrange(-self.max_y, 0)
self.r = random.randrange(1, self.MAX_SIZE)
self.r = int(min(random.expovariate(1 / self.MEAN_SIZE) + 1, self.MAX_SIZE))
intensity = random.randint(50, 255)
self.color = pygame.Color(intensity, intensity, intensity, 0)
def progress_color(self):
"""
increase opacity as the flake falls down
:return: None
"""
alpha = int(255 * self.y / self.max_y)
alpha = sorted([50, alpha, 255])[1]
self.color.a = alpha
def move(self, wind, max_x, max_y):
"""
move a snowflake downwards, respecting horizontal wind
:param wind: wind speed for horizontal movement
:param max_x: screen width to draw on
:param max_y: screen height to draw on
:return: None
"""
self.max_x = max_x
self.max_y = max_y
self.x += int(wind / self.r)
self.y += self.MAX_SIZE + 1 - self.r
self.enforce_periodic_boundaries()
self.progress_color()
def enforce_periodic_boundaries(self):
"""
let a snowflake appear on one side of the screen
if it disappears on the other.
Also let it "respawn" if it fell past the screen
:return: None
"""
if (self.x < 0 - self.r) or (self.x > (self.max_x + self.r)):
self.x = self.max_x - self.x
if self.y > self.max_y + self.r:
self.generate_new_parameters()
def __getitem__(self, key):
"""
Makes the class iterable.
Enables the use of *snowflake for the current parameters
:param key: index to get: 0=>x, 1=>y, 2=>r, 3=>color
:return: the value connected to the index
"""
current_state = (self.x, self.y, self.r, self.color)
return current_state[key]
class SnowingApp:
"""
provides the pygame abstraction to have a little snowstorm
"""
BACKGROUND_COLOR = pygame.color.THECOLORS['black']
FPS = 30
def __init__(self, canvas, snow_amount, wind_speed):
self.canvas = canvas
self.wind_speed = wind_speed
screen_size = pygame.display.Info()
self.screen_width = screen_size.current_w
self.screen_height = screen_size.current_h
self.clock = pygame.time.Clock()
self.snowflakes = [Snowflake(self.screen_width, self.screen_height)
for _ in range(snow_amount)]
self.pause = False
def run(self):
"""
runs the game loop, and starts the snow
:return: None
"""
snowing = True
while snowing:
snowing = self.handle_events()
self.canvas.fill(self.BACKGROUND_COLOR)
self.wind_speed += random.random() - 0.5
self.draw_and_move_snowflakes()
self.refresh_screen()
def draw_and_move_snowflakes(self):
"""
iterates over all flakes, and renders them to the screen
:return: None
"""
for flake in self.snowflakes:
pygame.gfxdraw.filled_circle(self.canvas, *flake)
if not self.pause:
flake.move(self.wind_speed, self.screen_width, self.screen_height)
def refresh_screen(self):
"""
redraw the screen, respects the configured FPS
:return: None
"""
pygame.display.update()
self.clock.tick(self.FPS)
def handle_events(self):
"""
handle keypress and external signals
coming from pygame
:return: Boolean status telling whether the app is still running or not
"""
snowing = True
for event in pygame.event.get():
if event.type == pygame.QUIT:
snowing = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
snowing = False
elif event.key == pygame.K_SPACE:
self.pause = not self.pause
elif event.type == pygame.VIDEORESIZE:
self.screen_width = event.w
self.screen_height = event.h
return snowing
def main():
"""
main invoker for the above classes
:return: None
"""
pygame.init()
screen_mode = pygame.RESIZABLE | pygame.DOUBLEBUF | pygame.SRCALPHA | pygame.HWSURFACE
screen = pygame.display.set_mode((0, 0), screen_mode, 32)
app = SnowingApp(screen, snow_amount=4000, wind_speed=25)
app.run()
pygame.quit()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment