Particle fountain: Difference between revisions

Content added Content deleted
imported>RemoW
No edit summary
Line 755: Line 755:
<span style="color: #000000;">main</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">main</span><span style="color: #0000FF;">()</span>
<!--</syntaxhighlight>-->
<!--</syntaxhighlight>-->

=={{header|Python}}==
{{libheader|SDL}}
{{trans|C++}}

Use arrow keys, PageUp, PageDown and Space to vary fountain parameters.
<syntaxhighlight lang="python">
# Using SDL2 library: # pip install PySDL2

import sys
import random
import time
import math

import sdl2
import sdl2.ext

FPS = 60
NEW_PARTICLES_PER_FRAME = 10
MAX_PARTICLES = 5_000
GRAVITY = 100
WIDTH = 640
HEIGHT = 480


def clamp(value, min_, max_):
"""Return value clamped between min and max"""
return max(min_, min(value, max_))


class Particle:
"""Particle obeying gravity law."""

def __init__(self):
self.x = 0
self.y = 0
self.v_x = 0
self.v_y = 0

def update(self, dtime: float) -> None:
"""Move particle and update speed with gravity"""
self.x = self.x + self.v_x * dtime
self.y = self.y + self.v_y * dtime
self.v_y = self.v_y + GRAVITY * dtime

def set(self, x, y, v_x, v_y):
"""Set particle values"""
self.x = x
self.y = y
self.v_x = v_x
self.v_y = v_y


class Fountain:
"""The fountain"""

def __init__(self, max_particles: int, particles_per_frame: int):
self.particles_per_frame = particles_per_frame
self.max_particles = max_particles
self.spread = 10.0
self.range = math.sqrt(2 * GRAVITY * (HEIGHT - 20 - self.spread))
self.saturation = 155
self.reciprocate = False
self.reciprocating_time = 0.0
self.particles = [
self.init_particle(Particle()) for _ in range(self.particles_per_frame)
]

def update(self, dtime) -> None:
"""Update particles"""
if self.reciprocate:
self.reciprocating_time += dtime

for particle in self.particles:
particle.update(dtime)
if particle.y > HEIGHT - 10:
self.init_particle(particle)

if len(self.particles) < self.max_particles:
for _ in range(self.particles_per_frame):
self.particles.append(self.init_particle(Particle()))
# print(len(particles))

def render(self, renderer: sdl2.ext.renderer.Renderer) -> None:
"""Render particles"""
points = [(particle.x, particle.y) for particle in self.particles]

renderer.clear()
renderer.draw_point(
points, sdl2.ext.Color(self.saturation, self.saturation, 255)
)
renderer.present()

def step_parameter(self, param, step):
"""Change parameters"""
if param == "spread":
self.spread = clamp(self.spread + step, 0, 50)
elif param == "range":
self.range = clamp(self.range + step, 0, 300)
elif param == "color":
self.saturation = clamp(self.saturation + step, 0, 255)
elif param == "reciprocate":
self.reciprocate = not self.reciprocate
self.reciprocating_time = 0.0

def init_particle(self, particle: Particle) -> Particle:
"""Move particle at initial position with a random-y speed"""
radius = random.random() * self.spread
direction = random.random() * math.pi * 2
v_x = radius * math.cos(direction) + math.sin(self.reciprocating_time) * 20.0
v_y = -self.range + radius * math.sin(direction)
particle.set(WIDTH // 2, HEIGHT - 10, v_x, v_y)
return particle


def make_renderer() -> sdl2.ext.renderer.Renderer:
"""Initialise SDL and make renderer"""
sdl2.ext.init()

window = sdl2.ext.Window("Particle Fountain", size=(WIDTH, HEIGHT))
window.show()

renderer = sdl2.ext.renderer.Renderer(window)

return renderer


def limit_frame_rate(fps: float, cur_time: int) -> bool:
"""Limit frame rate"""
dtime = time.monotonic_ns() - cur_time
frame_duration = 1e9 / fps
if dtime < frame_duration:
time.sleep((frame_duration - dtime) / 1e9)
return True
return False


def handle_events(fountain: Fountain):
"""Act on events"""
key_actions = {
sdl2.SDL_SCANCODE_PAGEUP: lambda: fountain.step_parameter("color", 5),
sdl2.SDL_SCANCODE_PAGEDOWN: lambda: fountain.step_parameter("color", -5),
sdl2.SDL_SCANCODE_UP: lambda: fountain.step_parameter("range", 1),
sdl2.SDL_SCANCODE_DOWN: lambda: fountain.step_parameter("range", -1),
sdl2.SDL_SCANCODE_LEFT: lambda: fountain.step_parameter("spread", -1),
sdl2.SDL_SCANCODE_RIGHT: lambda: fountain.step_parameter("spread", 1),
sdl2.SDL_SCANCODE_SPACE: lambda: fountain.step_parameter("reciprocate", 1),
}

events = sdl2.ext.get_events()
for event in events:
if event.type == sdl2.SDL_QUIT:
return False
if event.type == sdl2.SDL_KEYDOWN:
if event.key.keysym.scancode in key_actions:
key_actions[event.key.keysym.scancode]()
elif event.key.keysym.scancode == sdl2.SDL_SCANCODE_Q:
return False
return True


def main_loop(renderer: sdl2.ext.renderer.Renderer, fountain: Fountain) -> None:
"""Main animation loop"""
running = True

cur_time = time.monotonic_ns()
while running:
running = handle_events(fountain)

fountain.render(renderer)

if not limit_frame_rate(FPS, cur_time):
print(f"Didn't make it in time with {len(fountain.particles)} particles.")

dtime = (time.monotonic_ns() - cur_time) / 1e9 # in seconds
fountain.update(dtime)
cur_time = time.monotonic_ns()

sdl2.ext.quit()


def run():
"""Start!"""

renderer = make_renderer()
fountain = Fountain(MAX_PARTICLES, NEW_PARTICLES_PER_FRAME)

main_loop(renderer, fountain)

return 0


if __name__ == "__main__":
sys.exit(run())

</syntaxhighlight>


=={{header|Raku}}==
=={{header|Raku}}==