wobbl_sand/falling_sand.py

261 lines
8.1 KiB
Python
Raw Normal View History

2024-02-25 21:32:34 +01:00
#!/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
2024-07-16 08:56:51 +02:00
from random import getrandbits
2024-02-25 21:32:34 +01:00
def true_false_random():
2024-07-16 08:56:51 +02:00
return getrandbits(1) == 0
2024-02-25 21:32:34 +01:00
@dataclass
class Settings:
fps: int = 60
window_size: tuple = (1000, 600)
2024-07-16 13:06:27 +02:00
slipperiness_left: int = 1
slipperiness_right: int = 1
2024-02-25 21:32:34 +01:00
class FallingSandParticle:
2024-02-26 13:06:01 +01:00
def __init__(self, app, start_pos: tuple, color: tuple=(50, 50, 200)):
2024-02-25 21:32:34 +01:00
self.app = app
self.pos = start_pos
2024-02-26 13:06:01 +01:00
self.color = color
2024-02-25 21:32:34 +01:00
self.not_moving = 0
x, y = start_pos
2024-07-15 19:59:10 +02:00
2024-02-25 21:32:34 +01:00
self.app.matrix[x, y] = self.color
def update(self):
old_pos = self.pos
ox, oy = old_pos
2024-07-16 13:06:27 +02:00
if oy >= self.app.sand_surface.get_height() - self.app.max_height:
2024-02-25 21:32:34 +01:00
self.app.falling_sand_particles.remove(self)
return
x, y = self.pos
2024-07-16 10:31:34 +02:00
if self.app.matrix[ox, oy + 1] == self.app.sand_surface.map_rgb(self.app.gray): # fall down
2024-02-25 21:32:34 +01:00
y += 1
elif (
2024-07-16 13:06:27 +02:00
self.app.matrix[ox - 1, oy + self.app.slipperiness_left] == self.app.sand_surface.map_rgb(self.app.gray)
and self.app.matrix[ox + 1, oy + self.app.slipperiness_right] == self.app.sand_surface.map_rgb(self.app.gray)
2024-07-16 10:31:34 +02:00
): # when the sand can slide in two directions
2024-02-25 21:32:34 +01:00
if true_false_random():
x -= 1
2024-07-16 13:06:27 +02:00
y += self.app.slipperiness_left
2024-02-25 21:32:34 +01:00
else:
x += 1
2024-07-16 13:06:27 +02:00
y += self.app.slipperiness_right
2024-02-25 21:32:34 +01:00
2024-07-16 13:06:27 +02:00
elif self.app.matrix[ox - 1, oy + self.app.slipperiness_left] == self.app.sand_surface.map_rgb(self.app.gray): # can only slide left
2024-02-25 21:32:34 +01:00
x -= 1
2024-07-16 13:06:27 +02:00
y += self.app.slipperiness_left
2024-02-25 21:32:34 +01:00
2024-07-16 13:06:27 +02:00
elif self.app.matrix[ox + 1, oy + self.app.slipperiness_right] == self.app.sand_surface.map_rgb(self.app.gray): # can only slide right
2024-02-25 21:32:34 +01:00
x += 1
2024-07-16 13:06:27 +02:00
y += self.app.slipperiness_right
2024-02-25 21:32:34 +01:00
else:
2024-07-16 10:31:34 +02:00
if self.not_moving == 32: # counter that increases when the particle is inactive and if it is 32,
if self.app.mouse_pressed[0]: # the particle's physics get deactivated for better performance.
2024-02-26 13:06:01 +01:00
self.not_moving = 0
else:
self.app.falling_sand_particles.remove(self)
2024-02-25 21:32:34 +01:00
return
else:
2024-02-26 13:06:01 +01:00
if self.app.mouse_pressed[0]:
self.not_moving = 0
else:
self.not_moving += 1
2024-02-25 21:32:34 +01:00
return
2024-07-16 10:31:34 +02:00
if not x > self.app.sand_surface.get_width() - 2: # game would crash when the particle goes outside the border.
2024-07-15 19:59:10 +02:00
self.not_moving = 0
2024-02-25 21:32:34 +01:00
2024-07-15 19:59:10 +02:00
self.pos = (x, y)
2024-02-25 21:32:34 +01:00
2024-07-15 19:59:10 +02:00
self.app.matrix[ox, oy] = self.app.gray
self.app.matrix[x, y] = self.color
2024-02-25 21:32:34 +01:00
class FallingSand:
def __init__(self):
pygame.init()
2024-07-16 10:31:34 +02:00
self.screen = pygame.display.set_mode((1000, 600), pygame.RESIZABLE) # pygame init default shit
pygame.display.set_caption("Wobbl Sand")
2024-02-25 21:32:34 +01:00
self.window = Window.from_display_module()
2024-07-16 10:31:34 +02:00
self.loading_surface = self.generate_loading_surface() # just the loading screen
2024-02-25 21:32:34 +01:00
self.screen.blit(self.loading_surface, (0, 0))
pygame.display.update()
2024-07-16 10:31:34 +02:00
self.settings = load_dataclass_json(Settings, "settings.json") # load settings
2024-02-25 21:32:34 +01:00
setattr(self.settings, "save", lambda: save_dataclass_json(self.settings, "settings.json"))
self.fps = self.settings.fps
2024-07-16 13:06:27 +02:00
self.slipperiness_left = self.settings.slipperiness_left
self.slipperiness_right = self.settings.slipperiness_right
self.max_height = self.slipperiness_left if self.slipperiness_left > self.slipperiness_right else self.slipperiness_right
2024-02-25 21:32:34 +01:00
self.falling_sand_particles = []
2024-07-16 10:31:34 +02:00
self.rainbow_data = [[255, 40, 40], 0] # color calculation stuff
2024-02-26 13:06:01 +01:00
self.rainbow_steps = [(1, True), (0, False), (2, True), (1, False), (0, True), (2, False)]
2024-02-25 21:32:34 +01:00
# colors
self.gray = (20, 20, 20)
# pygame objects
self.clock = pygame.time.Clock()
self.mouse = pygame.mouse
2024-02-26 13:06:01 +01:00
self.mouse_pressed = self.mouse.get_pressed()
self.mouse_pos = self.mouse.get_pos()
self.sand_surface = pygame.Surface(self.settings.window_size)
2024-02-25 21:32:34 +01:00
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)
2024-07-16 10:31:34 +02:00
self.matrix = pygame.PixelArray(self.sand_surface) # matrix to draw the sand particles on
2024-02-25 21:32:34 +01:00
2024-02-26 13:06:01 +01:00
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)
2024-02-25 21:32:34 +01:00
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)
2024-02-26 13:12:20 +01:00
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.reset_sand_particles()
2024-02-25 21:32:34 +01:00
def exit(self):
self.running = False
self.settings.save()
print("Bye!")
def window_update(self, size):
w, h = size
2024-02-25 21:32:34 +01:00
self.settings.window_size = size
self.matrix.close()
old_sand_surface = self.sand_surface
self.sand_surface = pygame.Surface(size)
self.sand_surface.fill(self.gray)
self.sand_surface.blit(old_sand_surface, (0, 0))
self.matrix = pygame.PixelArray(self.sand_surface)
2024-02-25 21:32:34 +01:00
def spawn_sand(self, position):
for ax in range(-8, 9):
2024-02-26 13:06:01 +01:00
color = self.rainbow()
for ay in range(-8, 9):
2024-02-25 21:32:34 +01:00
bx, by = position
bx += ax
by += ay
2024-07-16 13:11:22 +02:00
if bx < self.sand_surface.get_width() - 2 and by < self.sand_surface.get_height() -2 and self.matrix[bx, by] == self.sand_surface.map_rgb(self.gray) and bx % 2 == 0 and by % 2 == 0:
2024-02-26 13:06:01 +01:00
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)
2024-02-25 21:32:34 +01:00
2024-02-26 13:12:20 +01:00
def reset_sand_particles(self):
self.falling_sand_particles.clear()
self.sand_surface.fill(self.gray)
self.rainbow_data = [[255, 40, 40], 0]
2024-02-25 21:32:34 +01:00
if __name__ == "__main__":
sand = FallingSand()
while sand.running:
sand.loop()