refactor effect.py into utils

This commit is contained in:
Patrick Moessler 2023-02-22 22:55:11 +01:00
parent 5c14542a6c
commit f7691f2545
17 changed files with 292 additions and 279 deletions

View file

@ -1,13 +1,13 @@
from argparse import ArgumentParser
import time
from typing import Generator, List, Tuple
import pygame as pg
import sys
from effects.effect import Effect, Colors, color_darken, color_fadeout, color_wheel
from effects.effect import Effect
from effects.presets import Presets
from audio import AudioProcess
from util.color import Colors
from util.audio import AudioProcess
def print_displays() -> None:
@ -146,9 +146,6 @@ class Beamshow:
framecounter = 0
blackout = False
single_random = False
# taps = []
# tap_mean = 0
# last_beat = 0
fps_slidewindow = []
frames_per_beat = 0
while True:
@ -158,22 +155,15 @@ class Beamshow:
if is_beat:
if self.beat_skip_counter > 0:
is_beat = False
print(' skip')
print(" skip")
self.beat_skip_counter -= 1
else:
print('beat')
print("beat")
self.beat_skip_counter = self.beat_factor - 1
# is_beat = False
fps_mean = (
sum(fps_slidewindow) / len(fps_slidewindow) if fps_slidewindow else 0
)
# if tap_mean and last_beat:
# this_beat = time.time()
# is_beat = this_beat >= last_beat + tap_mean
# frames_per_beat = fps_mean * tap_mean
# if is_beat:
# last_beat = this_beat
common_events = loop.send((is_beat, frames_per_beat))
reinitialize = False
@ -218,41 +208,14 @@ class Beamshow:
elif event.key == pg.K_HOME:
print("resetting beat timing")
self.beat_skip_counter = 0
# this_tap = time.time()
# last_beat = this_tap
# elif event.key == pg.K_END:
# print("starting new tap measurement")
# taps.clear()
elif event.key == pg.K_PAGEUP:
self.beat_factor = (
self.beat_factor / 2 if self.beat_factor > 1 else 1
)
print(f"Trigger on every {self.beat_factor} beat")
# tap_mean /= 2
# print(
# f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB [x2]"
# )
elif event.key == pg.K_PAGEDOWN:
self.beat_factor = self.beat_factor * 2
print(f"Trigger on every {self.beat_factor} beat")
# tap_mean *= 2
# print(
# f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB [/2]"
# )
# elif event.key == pg.K_KP_ENTER or event.key == pg.K_RETURN:
# this_tap = time.time()
# if taps and this_tap - taps[-1] > 10:
# print("starting new tap measurement")
# taps.clear()
# taps.append(this_tap)
# last_beat = this_tap
# if len(taps) >= 2:
# tap_mean = sum(
# map(lambda t1, t2: t2 - t1, taps, taps[1:])
# ) / (len(taps) - 1)
# print(
# f"Taps: Mean {tap_mean:5.3f} s, {60/tap_mean:5.3f} BPM, {fps_mean * tap_mean:5.3f} FPB"
# )
if reinitialize:
self.window, self.background = self.initialize()
@ -266,7 +229,7 @@ class Beamshow:
self.window.fill(Colors.Black)
if is_beat:
pg.draw.rect(self.window, Colors.White, (0, 0, 30, 30))
pg.draw.rect(self.window, Colors.White, (0, 0, 20, 20))
pg.display.flip()
self.clock.tick(60)

View file

@ -1,9 +1,11 @@
from effects.effect import Effect
from typing import Any
import pygame as pg
from effects.effect import Effect, Colors, transform_bounce
import random
import math
from typing import Union, Generator
from util.color import Colors
from util.transform import transform_bounce
import math
import pygame as pg
import random
class BouncingSpot(Effect):
@ -46,7 +48,7 @@ class BouncingSpot(Effect):
bounds=bounds, velocity=velocity, x_factor=x_factor, y_factor=y_factor
)
next(self.bouncer)
self.update()
self.update(is_beat=False)
def update(self, *args: Any, **kwargs: Any) -> None:
new_size = (math.sin(self.ticks) / 2 + 0.5) * (
@ -56,7 +58,7 @@ class BouncingSpot(Effect):
new_scale = new_size - self.rect.width
self.rect.inflate_ip(new_scale, new_scale)
self.rect.center = self.bouncer.send(self.rect.size)
self.rect.center = self.bouncer.send((self.rect.size, kwargs["is_beat"]))
self.image.fill(Colors.Black)
pg.draw.ellipse(

View file

@ -1,9 +1,11 @@
from effects.effect import Effect
from typing import Any
import pygame as pg
from effects.effect import Effect, Colors, transform_bounce
import random
import math
from typing import Union, Generator
from util.color import Colors
from util.transform import transform_bounce
import math
import pygame as pg
import random
class CrazyPolys(Effect):
@ -69,7 +71,7 @@ class CrazyPolys(Effect):
self.size_f = (self.size * 0.6 - 10) / 2 / (self.R + self.r)
self.cursor = self.calc_pos()
self.update()
self.update(is_beat=False)
def calc_pos(self):
x = self.max_size / 2 + self.size_f * (
@ -98,7 +100,7 @@ class CrazyPolys(Effect):
# new_scale = new_size - self.rect.width
# self.rect.inflate_ip(new_scale, new_scale)
self.rect.center = self.bouncer.send(self.rect.size)
self.rect.center = self.bouncer.send((self.rect.size, kwargs["is_beat"]))
self.image.blit(self.background, (0, 0))

View file

@ -1,6 +1,7 @@
from typing import Any, List, Tuple
import pygame as pg
from effects.effect import Effect, Colors
from effects.effect import Effect
from util.color import Colors
import random
from typing import Union, Generator

View file

@ -1,6 +1,8 @@
from typing import Any, List, Tuple
import pygame as pg
from effects.effect import Effect, Colors, color_fade, transform_falling
from effects.effect import Effect
from util.color import Colors, color_fade
from util.transform import transform_falling
import random
from typing import Union, Generator
@ -38,9 +40,9 @@ class Drop(pg.sprite.Sprite):
)
next(self.fall)
self.finished = False
self.update()
self.update(is_beat=False)
def update(self):
def update(self, *args, **kwargs):
if isinstance(self.color, Generator):
pg.draw.ellipse(
self.image,
@ -48,7 +50,9 @@ class Drop(pg.sprite.Sprite):
((0, 0), self.rect.size),
)
if not self.finished:
self.rect.topleft = self.fall.send((self.bounds.width, self.bounds.width))
self.rect.topleft = self.fall.send(
((self.bounds.width, self.bounds.width), kwargs["is_beat"])
)
if self.rect.bottom >= self.bounds.bottom - self.bounds.width:
self.finished = True
@ -117,7 +121,7 @@ class Drops(Effect):
for drop in self.drops:
if not drop.finished:
drop.update()
drop.update(is_beat=kwargs["is_beat"])
drop.draw(self.image)
if self.drops:

View file

@ -1,202 +1,6 @@
from dataclasses import dataclass
import math
import random
from typing import Generator, Literal, Tuple
import pygame as pg
def copy_color(source: pg.Color) -> pg.Color:
return pg.Color(source.r, source.g, source.b, source.a)
@dataclass(frozen=True, slots=True)
class Colors:
Black = pg.Color(0, 0, 0)
White = pg.Color(255, 255, 255)
Red = pg.Color(255, 0, 0)
Green = pg.Color(0, 255, 0)
Blue = pg.Color(0, 0, 255)
Cyan = pg.Color(0, 255, 255)
Yellow = pg.Color(255, 255, 0)
Magenta = pg.Color(255, 0, 255)
def color_wheel(hue=0, increase=1) -> Generator[pg.Color, None, None]:
color = copy_color(Colors.Red)
h, s, l, a = color.hsla
h = hue
while True:
color.hsla = h, s, l, a
yield color
h = (h + increase) % 360
def color_randomize() -> Generator[pg.Color, None, None]:
color = copy_color(Colors.Red)
h, s, l, a = color.hsla
color.hsla = random.randrange(0, 360 // 5) * 5, s, l, a
while True:
yield color
color.hsla = random.randrange(0, 360 // 5) * 5, s, l, a
def color_fadeout(
initial_color: pg.Color, fade_speed: float
) -> Generator[pg.Color, None, None]:
color = copy_color(initial_color)
h, s, l, a = color.hsla
l_float = float(l)
while True:
yield color
color.hsla = h, s, int(l_float), a
l_float -= fade_speed
if l_float < 0:
l_float = 0
def color_fade(
initial_color: pg.Color, end_color: pg.Color, duration: int
) -> Generator[pg.Color, None, None]:
color = copy_color(initial_color)
h, s, l, a = color.hsla
h2, s2, l2, a2 = end_color.hsla
h_inc = (h2 - h) / duration
s_inc = (s2 - s) / duration
l_inc = (l2 - l) / duration
a_inc = (a2 - a) / duration
h_f = float(h)
s_f = float(s)
l_f = float(l)
a_f = float(a)
while True:
yield color
color.hsla = int(h_f), int(s_f), int(l_f), int(a_f)
if h_f < h2:
h_f += h_inc
if s_f < s2:
s_f += s_inc
if l_f < l2:
l_f += l_inc
if a_f < a2:
a_f += a_inc
def color_darken(color: pg.Color, factor: float) -> pg.Color:
h, s, l, a = color.hsla
new_color = pg.Color(0, 0, 0, 255)
new_color.hsla = h, s, l * factor, a
return new_color
def rainbow_surface(
image: pg.Surface,
orientation: Literal["h", "v"] = "h",
hue: int = 0,
increase: int = 1,
):
wheel = color_wheel(hue, increase)
if orientation == "h":
h = image.get_height()
for x in range(image.get_width()):
pg.draw.line(image, next(wheel), (x, 0), (x, h))
elif orientation == "v":
w = image.get_width()
for y in range(image.get_height()):
pg.draw.line(image, next(wheel), (0, y), (w, y))
def transform_bounce(
bounds: pg.Rect,
velocity: Tuple[float, float],
x_factor: Tuple[float, float],
y_factor: Tuple[float, float],
) -> Generator[Tuple[int, int], Tuple[int, int], None]:
min_velocity = velocity[0]
max_velocity = velocity[1]
current_velocity = random.uniform(min_velocity, max_velocity)
phase = random.uniform(0, 360)
current_x_factor = random.uniform(x_factor[0], x_factor[1])
current_y_factor = random.uniform(y_factor[0], y_factor[1])
size_x, size_y = yield (bounds.centerx, bounds.centery)
while True:
pos_x = int(
math.cos(current_x_factor * phase) * (bounds.width - size_x) // 2
+ bounds.centerx
)
pos_y = int(
math.sin(current_y_factor * phase) * (bounds.height - size_y) // 2
+ bounds.centery
)
phase += current_velocity / 180 * math.pi
# current_velocity = random.uniform(min_velocity, max_velocity)
size_x, size_y = yield (pos_x, pos_y)
def transform_oscillate(
bounds: pg.Rect,
period: int,
initial_pos: Tuple[int, int] = (-1, -1),
) -> Generator[Tuple[int, int], Tuple[int, int], None]:
pos_x = float(initial_pos[0] if initial_pos[0] > 0 else bounds.left)
pos_y = float(initial_pos[1] if initial_pos[1] > 0 else bounds.top)
direction = "+"
size_x, size_y = yield (bounds.left, bounds.top)
while True:
range_x = bounds.width - size_x
range_y = bounds.height - size_y
inc_x = range_x / period
inc_y = range_y / period
if direction == "+":
pos_x = pg.math.clamp(pos_x + inc_x, 0, range_x)
pos_y = pg.math.clamp(pos_y + inc_y, 0, range_y)
else:
pos_x = pg.math.clamp(pos_x - inc_x, 0, range_x)
pos_y = pg.math.clamp(pos_y - inc_y, 0, range_y)
if (inc_x and (pos_x > range_x - inc_x)) or (
inc_y and (pos_y > range_y - inc_y)
):
direction = "-"
elif (inc_x and (pos_x < inc_x)) or (inc_y and (pos_y < inc_y)):
direction = "+"
size_x, size_y = yield (int(pos_x), int(pos_y))
def transform_falling(
bounds: pg.Rect,
acceleration: float,
initial_pos: Tuple[int, int],
initial_velocity: float = 1.0,
) -> Generator[Tuple[int, int], Tuple[int, int], None]:
pos_x = float(initial_pos[0])
pos_y = float(initial_pos[1])
size_x, size_y = yield (bounds.left, bounds.top)
velocity = initial_velocity
while True:
range_y = bounds.height - size_y
pos_y += velocity
velocity += acceleration
if pos_y > range_y:
pos_y = initial_pos[1]
velocity = initial_velocity
size_x, size_y = yield (int(pos_x), int(pos_y))
from util.transform import Mover
class Effect(pg.sprite.Sprite):
@ -209,3 +13,11 @@ class Effect(pg.sprite.Sprite):
def draw(self, surface: pg.Surface):
surface.blit(self.image, self.rect)
class MovingEffect(Effect):
def __init__(
self, image: pg.Surface, rect: pg.Rect, mover: Mover, *groups: pg.sprite.Group
) -> None:
super().__init__(image, rect, *groups)
self.mover = mover

View file

@ -1,6 +1,8 @@
from typing import Any, Tuple
import pygame as pg
from effects.effect import Effect, Colors, transform_bounce
from effects.effect import Effect
from util.color import Colors
from util.transform import transform_bounce
import random
import math
from typing import Union, Generator
@ -60,7 +62,7 @@ class Moonflower(Effect):
)
self.bounds = bounds
self.bouncer = transform_bounce(
bounds=bounds, velocity=velocity, x_factor=x_factor, y_factor=y_factor
bounds=bounds, velocity=velocity, x_factor=x_factor, y_factor=y_factor, on_beat_random_phase=True
)
next(self.bouncer)
self.o_color = (
@ -76,7 +78,7 @@ class Moonflower(Effect):
self.update(is_beat=False)
def update(self, *args: Any, **kwargs: Any) -> None:
self.rect.center = self.bouncer.send(self.rect.size)
self.rect.center = self.bouncer.send((self.rect.size, kwargs["is_beat"]))
self.image.fill(Colors.Black)

View file

@ -1,6 +1,8 @@
from typing import Any, Tuple
import pygame as pg
from effects.effect import Effect, Colors, rainbow_surface, transform_oscillate
from effects.effect import Effect
from util.color import Colors, rainbow_surface
from util.transform import transform_oscillate
import math
@ -69,7 +71,7 @@ class MovingWave(Effect):
width=thickness,
)
self.update()
self.update(is_beat=False)
def update(self, *args: Any, **kwargs: Any) -> None:
@ -78,7 +80,9 @@ class MovingWave(Effect):
# rainbow_surface(self.image)
if self.oscillator:
pos = self.oscillator.send((self.bounds.width, self.wave_height))
pos = self.oscillator.send(
((self.bounds.width, self.wave_height), kwargs["is_beat"])
)
# pg.draw.rect(self.image, Colors.Black, (0, 0, self.bounds.width, pos[1]))
# pg.draw.rect(
# self.image,

View file

@ -3,9 +3,12 @@ import pygame as pg
from typing import List
from random import choice, randint
from effects.effect import Effect
from util.color import color_darken, color_randomize, color_wheel, Colors
from effects.crazypolys import CrazyPolys
from effects.drops import Drops
from effects.effect import Effect, color_darken, color_randomize, color_wheel, Colors
from effects.bouncingspot import BouncingSpot
from effects.doublespot import DoubleSpot
from effects.moonflower import Moonflower

View file

@ -1,6 +1,8 @@
from typing import Any, Tuple
import pygame as pg
from effects.effect import Effect, Colors, transform_bounce
from effects.effect import Effect
from util.color import Colors
from util.transform import transform_bounce
import math
from typing import Union, Generator
@ -64,7 +66,7 @@ class RotatingPoly(Effect):
self.update(is_beat=False)
def update(self, *args: Any, **kwargs: Any) -> None:
self.rect.center = self.bouncer.send(self.rect.size)
self.rect.center = self.bouncer.send((self.rect.size, kwargs["is_beat"]))
self.image.fill(Colors.Black)

View file

@ -1,6 +1,8 @@
from typing import Any, Tuple
import pygame as pg
from effects.effect import Effect, Colors, transform_bounce
from effects.effect import Effect
from util.color import Colors
from util.transform import transform_bounce
import random
from typing import Union, Generator
@ -35,11 +37,13 @@ class ScanReticle(Effect):
bounds=bounds, velocity=velocity, x_factor=x_factor, y_factor=y_factor
)
next(self.bouncer)
self.update()
self.update(is_beat=False)
def update(self, *args: Any, **kwargs: Any) -> None:
target = self.bouncer.send((self.rect_size, self.rect_size))
target = self.bouncer.send(
((self.rect_size, self.rect_size), kwargs["is_beat"])
)
self.image.fill(Colors.Black)

View file

@ -1,6 +1,8 @@
from typing import Any
import pygame as pg
from effects.effect import Effect, Colors, copy_color, transform_bounce
from effects.effect import Effect
from util.color import Colors
from util.transform import transform_bounce
import random
import math
from typing import Union, Generator
@ -23,7 +25,7 @@ class Spiro(Effect):
self.min_velocity = velocity[0]
self.max_velocity = velocity[1]
self.velocity = 0.1 # random.uniform(self.min_velocity, self.max_velocity)
self.velocity = 0.1 # random.uniform(self.min_velocity, self.max_velocity)
self.ticks = 0.0
self.color = color
self.size = self.max_size
@ -32,8 +34,8 @@ class Spiro(Effect):
image.set_colorkey(Colors.Black)
self.R = random.randint(self.max_size / 6, self.max_size / 2)
self.a = 3/random.randint(4, 10) #random.uniform(0, 1)
self.k = 3/random.randint(4, 10) #random.uniform(0, 1)
self.a = 3 / random.randint(4, 10) # random.uniform(0, 1)
self.k = 3 / random.randint(4, 10) # random.uniform(0, 1)
self.scan_speed = random.randint(1, 100)
@ -65,7 +67,7 @@ class Spiro(Effect):
next(self.bouncer)
self.cursor = self.calc_pos()
self.update()
self.update(is_beat=False)
def calc_pos(self):
x = self.max_size / 2 + self.R * (
@ -86,7 +88,7 @@ class Spiro(Effect):
# new_scale = new_size - self.rect.width
# self.rect.inflate_ip(new_scale, new_scale)
self.rect.center = self.bouncer.send(self.rect.size)
self.rect.center = self.bouncer.send((self.rect.size, kwargs["is_beat"]))
self.image.blit(self.background, (0, 0))

View file

@ -1,6 +1,7 @@
from typing import Any, List, Tuple
import pygame as pg
from effects.effect import Effect, Colors
from effects.effect import Effect
from util.color import Colors
import random
from typing import Union, Generator
@ -124,7 +125,7 @@ class Starfield(Effect):
self.image.fill(Colors.Black)
if self.beat_adapt and kwargs["frames_per_beat"]:
hold = 3*kwargs["frames_per_beat"] - 2
hold = 3 * kwargs["frames_per_beat"] - 2
self.fade_in_time = int(hold // 10 if self.fade_in_time else 0)
self.fade_out_time = int(hold * 0.8 if self.fade_out_time else 0)
self.hold_time = int(hold - self.fade_in_time)
@ -135,7 +136,7 @@ class Starfield(Effect):
self.time_to_next == 0 and (not self.beat_adapt or kwargs["is_beat"])
) and len(self.stars) < int(self.num_stars * 0.8):
missing_stars = self.num_stars - len(self.stars)
for _ in range(random.randint(missing_stars // 3, missing_stars//2)):
for _ in range(random.randint(missing_stars // 3, missing_stars // 2)):
star_color = (
self.color if isinstance(self.color, pg.Color) else next(self.color)
)

0
util/__init__.py Normal file
View file

View file

@ -66,6 +66,7 @@ class AudioProcess:
is_beat = self.tempo(samples)
if is_beat:
with self.lock:
# print(self.tempo.get)
self.is_beat = True
# samples += click
# print("tick") # avoid print in audio callback

108
util/color.py Normal file
View file

@ -0,0 +1,108 @@
from dataclasses import dataclass
import random
from typing import Generator, Literal
import pygame as pg
def copy_color(source: pg.Color) -> pg.Color:
return pg.Color(source.r, source.g, source.b, source.a)
@dataclass(frozen=True, slots=True)
class Colors:
Black = pg.Color(0, 0, 0)
White = pg.Color(255, 255, 255)
Red = pg.Color(255, 0, 0)
Green = pg.Color(0, 255, 0)
Blue = pg.Color(0, 0, 255)
Cyan = pg.Color(0, 255, 255)
Yellow = pg.Color(255, 255, 0)
Magenta = pg.Color(255, 0, 255)
def color_wheel(hue=0, increase=1) -> Generator[pg.Color, None, None]:
color = copy_color(Colors.Red)
h, s, l, a = color.hsla
h = hue
while True:
color.hsla = h, s, l, a
yield color
h = (h + increase) % 360
def color_randomize() -> Generator[pg.Color, None, None]:
color = copy_color(Colors.Red)
h, s, l, a = color.hsla
color.hsla = random.randrange(0, 360 // 5) * 5, s, l, a
while True:
yield color
color.hsla = random.randrange(0, 360 // 5) * 5, s, l, a
def color_fadeout(
initial_color: pg.Color, fade_speed: float
) -> Generator[pg.Color, None, None]:
color = copy_color(initial_color)
h, s, l, a = color.hsla
l_float = float(l)
while True:
yield color
color.hsla = h, s, int(l_float), a
l_float -= fade_speed
if l_float < 0:
l_float = 0
def color_fade(
initial_color: pg.Color, end_color: pg.Color, duration: int
) -> Generator[pg.Color, None, None]:
color = copy_color(initial_color)
h, s, l, a = color.hsla
h2, s2, l2, a2 = end_color.hsla
h_inc = (h2 - h) / duration
s_inc = (s2 - s) / duration
l_inc = (l2 - l) / duration
a_inc = (a2 - a) / duration
h_f = float(h)
s_f = float(s)
l_f = float(l)
a_f = float(a)
while True:
yield color
color.hsla = int(h_f), int(s_f), int(l_f), int(a_f)
if h_f < h2:
h_f += h_inc
if s_f < s2:
s_f += s_inc
if l_f < l2:
l_f += l_inc
if a_f < a2:
a_f += a_inc
def color_darken(color: pg.Color, factor: float) -> pg.Color:
h, s, l, a = color.hsla
new_color = pg.Color(0, 0, 0, 255)
new_color.hsla = h, s, l * factor, a
return new_color
def rainbow_surface(
image: pg.Surface,
orientation: Literal["h", "v"] = "h",
hue: int = 0,
increase: int = 1,
):
wheel = color_wheel(hue, increase)
if orientation == "h":
h = image.get_height()
for x in range(image.get_width()):
pg.draw.line(image, next(wheel), (x, 0), (x, h))
elif orientation == "v":
w = image.get_width()
for y in range(image.get_height()):
pg.draw.line(image, next(wheel), (0, y), (w, y))

102
util/transform.py Normal file
View file

@ -0,0 +1,102 @@
import math
import random
from typing import Callable, Generator, Optional, Tuple
import pygame as pg
PositionGenerator = Generator[
Tuple[int, int], Tuple[Tuple[int, int], Optional[bool]], None
]
Mover = Callable[(...), PositionGenerator]
def transform_bounce(
bounds: pg.Rect,
velocity: Tuple[float, float],
x_factor: Tuple[float, float],
y_factor: Tuple[float, float],
on_beat_random_phase: bool = False,
) -> PositionGenerator:
min_velocity = velocity[0]
max_velocity = velocity[1]
current_velocity = random.uniform(min_velocity, max_velocity)
phase = random.uniform(0, 360)
current_x_factor = random.uniform(x_factor[0], x_factor[1])
current_y_factor = random.uniform(y_factor[0], y_factor[1])
(size_x, size_y), is_beat = yield (bounds.centerx, bounds.centery)
while True:
pos_x = int(
math.cos(current_x_factor * phase) * (bounds.width - size_x) // 2
+ bounds.centerx
)
pos_y = int(
math.sin(current_y_factor * phase) * (bounds.height - size_y) // 2
+ bounds.centery
)
phase += current_velocity / 180 * math.pi
if on_beat_random_phase and is_beat:
phase += random.randrange(0, 360)
(size_x, size_y), _ = yield (pos_x, pos_y)
def transform_oscillate(
bounds: pg.Rect,
period: int,
initial_pos: Tuple[int, int] = (-1, -1),
) -> PositionGenerator:
pos_x = float(initial_pos[0] if initial_pos[0] > 0 else bounds.left)
pos_y = float(initial_pos[1] if initial_pos[1] > 0 else bounds.top)
direction = "+"
(size_x, size_y), _ = yield (bounds.left, bounds.top)
while True:
range_x = bounds.width - size_x
range_y = bounds.height - size_y
inc_x = range_x / period
inc_y = range_y / period
if direction == "+":
pos_x = pg.math.clamp(pos_x + inc_x, 0, range_x)
pos_y = pg.math.clamp(pos_y + inc_y, 0, range_y)
else:
pos_x = pg.math.clamp(pos_x - inc_x, 0, range_x)
pos_y = pg.math.clamp(pos_y - inc_y, 0, range_y)
if (inc_x and (pos_x > range_x - inc_x)) or (
inc_y and (pos_y > range_y - inc_y)
):
direction = "-"
elif (inc_x and (pos_x < inc_x)) or (inc_y and (pos_y < inc_y)):
direction = "+"
(size_x, size_y), _ = yield (int(pos_x), int(pos_y))
def transform_falling(
bounds: pg.Rect,
acceleration: float,
initial_pos: Tuple[int, int],
initial_velocity: float = 1.0,
on_beat_reset: bool = False,
) -> PositionGenerator:
pos_x = float(initial_pos[0])
pos_y = float(initial_pos[1])
(_, size_y), is_beat = yield (bounds.left, bounds.top)
velocity = initial_velocity
while True:
range_y = bounds.height - size_y
pos_y += velocity
velocity += acceleration
if (pos_y > range_y) or (on_beat_reset and is_beat):
pos_y = initial_pos[1]
velocity = initial_velocity
(_, size_y), is_beat = yield (int(pos_x), int(pos_y))