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))