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
|
2024-02-25 21:42:56 +01:00
|
|
|
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()
|
2024-02-26 13:27:00 +01:00
|
|
|
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):
|
2024-02-26 13:27:00 +01:00
|
|
|
w, h = size
|
|
|
|
|
2024-02-25 21:32:34 +01:00
|
|
|
self.settings.window_size = size
|
2024-02-26 13:27:00 +01:00
|
|
|
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):
|
2024-02-25 21:52:57 +01:00
|
|
|
for ax in range(-8, 9):
|
2024-02-26 13:06:01 +01:00
|
|
|
color = self.rainbow()
|
|
|
|
|
2024-02-25 21:52:57 +01:00
|
|
|
for ay in range(-8, 9):
|
2024-02-25 21:32:34 +01:00
|
|
|
bx, by = position
|
|
|
|
bx += ax
|
|
|
|
by += ay
|
|
|
|
|
2024-07-15 19:59:10 +02:00
|
|
|
if not bx > self.sand_surface.get_width() - 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()
|