wobbl_sand/falling_sand.py
2024-02-26 13:08:00 +01:00

236 lines
6.3 KiB
Python

#!/usr/bin/python3
import pygame
from pygame._sdl2 import Window
from dataclasses import dataclass
from wobbl_tools.data_file import load_dataclass_json, save_dataclass_json
from random import choice
def true_false_random():
return choice([True, False])
@dataclass
class Settings:
fps: int = 60
window_size: tuple = (1000, 600)
class FallingSandParticle:
def __init__(self, app, start_pos: tuple, color: tuple=(50, 50, 200)):
self.app = app
self.pos = start_pos
self.color = color
self.not_moving = 0
x, y = start_pos
self.app.matrix[x, y] = self.color
def update(self):
old_pos = self.pos
ox, oy = old_pos
if oy >= self.app.sand_surface.get_height() - 4:
self.app.falling_sand_particles.remove(self)
return
x, y = self.pos
if self.app.matrix[ox, oy + 1] == self.app.sand_surface.map_rgb(self.app.gray):
y += 1
elif (
self.app.matrix[ox - 1, oy + 2] == self.app.sand_surface.map_rgb(self.app.gray)
and self.app.matrix[ox + 1, oy + 2] == self.app.sand_surface.map_rgb(self.app.gray)
):
if true_false_random():
x -= 1
y += 2
else:
x += 1
y += 2
elif self.app.matrix[ox - 1, oy + 2] == self.app.sand_surface.map_rgb(self.app.gray):
x -= 1
y += 2
elif self.app.matrix[ox + 1, oy + 2] == self.app.sand_surface.map_rgb(self.app.gray):
x += 1
y += 2
else:
if self.not_moving == 32:
if self.app.mouse_pressed[0]:
self.not_moving = 0
else:
self.app.falling_sand_particles.remove(self)
return
else:
if self.app.mouse_pressed[0]:
self.not_moving = 0
else:
self.not_moving += 1
return
self.not_moving = 0
self.pos = (x, y)
self.app.matrix[ox, oy] = self.app.gray
self.app.matrix[x, y] = self.color
class FallingSand:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((1000, 600), pygame.RESIZABLE)
pygame.display.set_caption("Wobbl Sand")
self.window = Window.from_display_module()
self.loading_surface = self.generate_loading_surface()
self.screen.blit(self.loading_surface, (0, 0))
pygame.display.update()
self.settings = load_dataclass_json(Settings, "settings.json")
setattr(self.settings, "save", lambda: save_dataclass_json(self.settings, "settings.json"))
self.fps = self.settings.fps
self.falling_sand_particles = []
self.rainbow_data = [[255, 40, 40], 0]
self.rainbow_steps = [(1, True), (0, False), (2, True), (1, False), (0, True), (2, False)]
# colors
self.gray = (20, 20, 20)
# pygame objects
self.clock = pygame.time.Clock()
self.mouse = pygame.mouse
self.mouse_pressed = self.mouse.get_pressed()
self.mouse_pos = self.mouse.get_pos()
self.sand_surface = pygame.Surface(self.screen.get_size())
self.sand_surface.fill(self.gray)
self.matrix = pygame.PixelArray(self.sand_surface)
# loading finished
self.window.size = self.settings.window_size
self.running = True
def generate_loading_surface(self):
default_font = pygame.font.SysFont("ubuntu", 32, True)
loading_text = default_font.render("Loading...", True, (240, 240, 240))
w, h = self.screen.get_size()
loading_surface = pygame.Surface((w, h), flags=pygame.SRCALPHA)
loading_surface.fill((40, 40, 40))
loading_surface.blit(loading_text, (w // 2 - loading_text.get_width() // 2, h // 2 - loading_text.get_height() // 2))
return loading_surface
def loop(self):
self.screen.fill(self.gray)
self.matrix = pygame.PixelArray(self.sand_surface)
self.mouse_pressed = self.mouse.get_pressed()
self.mouse_pos = self.mouse.get_pos()
if self.mouse_pressed[0]:
self.spawn_sand(self.mouse_pos)
self.get_events()
if not self.running:
return
for particle in self.falling_sand_particles:
particle.update()
self.matrix.close()
self.screen.blit(self.sand_surface, (0, 0))
if self.loading_surface.get_alpha() > 0:
self.screen.blit(self.loading_surface, (0, 0))
self.loading_surface.set_alpha(self.loading_surface.get_alpha() - 4)
pygame.display.update()
self.clock.tick(self.fps)
def get_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.exit()
return
elif event.type == pygame.VIDEORESIZE:
self.window_update(event.size)
def exit(self):
self.running = False
self.settings.save()
print("Bye!")
def window_update(self, size):
self.settings.window_size = size
def spawn_sand(self, position):
for ax in range(-8, 9):
color = self.rainbow()
for ay in range(-8, 9):
bx, by = position
bx += ax
by += ay
if self.matrix[bx, by] == self.sand_surface.map_rgb(self.gray) and bx % 2 == 0 and by % 2 == 0:
self.falling_sand_particles.append(FallingSandParticle(self, (bx, by), color))
def rainbow(self):
color = self.rainbow_data[0]
step_index = self.rainbow_data[1]
step = self.rainbow_steps[step_index]
if step[1]:
if color[step[0]] == 255:
if step_index == 5:
step_index = 0
else:
step_index += 1
else:
color[step[0]] += 1
else:
if color[step[0]] == 40:
if step_index == 5:
step_index = 0
else:
step_index += 1
else:
color[step[0]] -= 1
self.rainbow_data = [color, step_index]
return tuple(color)
if __name__ == "__main__":
sand = FallingSand()
while sand.running:
sand.loop()