Added water splash simulator, made settings saving work for all simulators, moved all the simulators into their respective directories, improved the README, fixed too much logging

This commit is contained in:
csd4ni3l
2025-09-14 22:27:54 +02:00
parent 9c632d1bec
commit c010c41ccd
12 changed files with 395 additions and 54 deletions

View File

@@ -0,0 +1,77 @@
import arcade, arcade.math, random, math
class Boid(arcade.Sprite):
def __init__(self, boid_num, x, y):
super().__init__(arcade.load_texture("assets/graphics/boid.png"), center_x=x, center_y=y)
random_angle = random.randint(0, 361)
self.boid_num = boid_num
self.direction = arcade.math.Vec2(math.cos(random_angle), math.sin(random_angle))
self.velocity = 5
self.radius = 10
self.w_separation = 1.0
self.w_cohesion = 1.0
self.w_alignment = 1.0
self.small_radius = 100
self.large_radius = 250
def calculate_separation(self, neighbours: list[int, arcade.math.Vec2, arcade.math.Vec2]):
steeraway_vectors = [arcade.math.Vec2(*self.position) - neighbour[2] for neighbour in neighbours]
if not steeraway_vectors:
return self.direction
return (sum(steeraway_vectors) / len(steeraway_vectors)).normalize()
def calculate_alignment(self, neighbours: list[int, arcade.math.Vec2, arcade.math.Vec2]):
directions = [neighbour[1] for neighbour in neighbours]
if not directions:
return self.direction
return (sum(directions) / len(directions)).normalize()
def calculate_cohesion(self, neighbours: list[int, arcade.math.Vec2, arcade.math.Vec2]):
positions = [neighbour[2] for neighbour in neighbours]
if not positions:
return self.direction
return ((sum(positions) / len(positions)) - self.position).normalize()
def update(self, window_width, window_height, boids):
small_radius_neighbours, large_radius_neighbours = [], []
for boid_data in boids:
distance = boid_data[2].distance(arcade.math.Vec2(*self.position))
if boid_data[0] == self.boid_num or distance > self.large_radius:
continue
if distance <= self.small_radius:
small_radius_neighbours.append(boid_data)
large_radius_neighbours.append(boid_data)
self.direction = self.w_separation * self.calculate_separation(small_radius_neighbours) + self.w_alignment * self.calculate_alignment(large_radius_neighbours) + self.w_cohesion * self.calculate_cohesion(large_radius_neighbours)
if self.direction.length() > 1:
self.direction = self.direction.normalize()
self.position += self.direction * self.velocity
self.angle = 90 - math.degrees(self.direction.heading())
if self.center_x <= self.radius:
self.center_x = self.radius
self.direction = self.direction.reflect(arcade.math.Vec2(1, 0))
elif self.center_x >= (window_width * 0.8) - self.radius:
self.center_x = (window_width * 0.8) - self.radius
self.direction = self.direction.reflect(arcade.math.Vec2(-1, 0))
if self.center_y <= self.radius:
self.center_y = self.radius
self.direction = self.direction.reflect(arcade.math.Vec2(0, 1))
elif self.center_y >= window_height - self.radius:
self.center_y = window_height - self.radius
self.direction = self.direction.reflect(arcade.math.Vec2(0, -1))

View File

@@ -0,0 +1,88 @@
import arcade, arcade.gui, random, os, json
from game.boid_simulator.boid import Boid
class Game(arcade.gui.UIView):
def __init__(self, pypresence_client):
super().__init__()
self.pypresence_client = pypresence_client
self.pypresence_client.update(state='Playing a simulator', details='Boids simulator', start=self.pypresence_client.start_time)
self.boid_sprites = arcade.SpriteList()
self.current_boid_num = 1
if os.path.exists("data.json"):
with open("data.json", "r") as file:
self.settings = json.load(file)
else:
self.settings = {}
if not "boid_simulator" in self.settings:
self.settings["boid_simulator"] = {
"w_separation": 1.0,
"w_alignment": 1.0,
"w_cohesion": 1.0,
"small_radius": 100,
"large_radius": 250
}
self.anchor = self.add_widget(arcade.gui.UIAnchorLayout(size_hint=(1, 1)))
self.settings_box = self.anchor.add(arcade.gui.UIBoxLayout(space_between=5, align="center", size_hint=(0.2, 1)).with_background(color=arcade.color.GRAY), anchor_x="right", anchor_y="bottom")
self.settings_label = self.settings_box.add(arcade.gui.UILabel(text="Settings", font_size=24))
self.add_setting("Separation Weight: {value}", 0.1, 5, 0.1, "w_separation")
self.add_setting("Alignment Weight: {value}", 0.1, 5, 0.1, "w_alignment")
self.add_setting("Cohesion Weight: {value}", 0.1, 5, 0.1, "w_cohesion")
self.add_setting("Small Radius: {value}", 25, 250, 25, "small_radius")
self.add_setting("Large Radius: {value}", 50, 500, 50, "large_radius")
def save_data(self):
with open("data.json", "w") as file:
file.write(json.dumps(self.settings, indent=4))
def add_setting(self, text, min_value, max_value, step, boid_variable):
label = self.settings_box.add(arcade.gui.UILabel(text.format(value=self.settings["boid_simulator"][boid_variable])))
slider = self.settings_box.add(arcade.gui.UISlider(value=self.settings["boid_simulator"][boid_variable], min_value=min_value, max_value=max_value, step=step, size_hint=(1, 0.05)))
slider._render_steps = lambda surface: None
slider.on_change = lambda event, label=label: self.change_value(label, text, boid_variable, event.new_value)
def change_value(self, label, text, boid_variable, value):
label.text = text.format(value=value)
self.settings["boid_simulator"][boid_variable] = value
for boid in self.boid_sprites:
setattr(boid, boid_variable, value)
def create_boid(self, x, y):
boid = Boid(self.current_boid_num, x, y)
self.boid_sprites.append(boid)
self.current_boid_num += 1
def setup_boids(self):
for i in range(25):
self.create_boid(random.randint(self.window.width / 2 - 150, self.window.width / 2), random.randint(self.window.height / 2 - 150, self.window.height / 2))
def on_show_view(self):
super().on_show_view()
self.setup_boids()
def on_update(self, delta_time):
boid_directions = [(boid.boid_num, boid.direction, arcade.math.Vec2(*boid.position)) for boid in self.boid_sprites]
for boid in self.boid_sprites:
boid.update(self.window.width, self.window.height, boid_directions)
if self.window.mouse[arcade.MOUSE_BUTTON_LEFT]:
self.create_boid(self.window.mouse.data["x"], self.window.mouse.data["y"])
def on_key_press(self, symbol, modifiers):
if symbol == arcade.key.ESCAPE:
self.save_data()
from menus.main import Main
self.window.show_view(Main(self.pypresence_client))
def on_draw(self):
super().on_draw()
self.boid_sprites.draw()