from typing import Any, List, Tuple import pygame as pg from effects.effect import Effect, Colors import random from typing import Union, Generator def fade_statemachine( fade_in: int, hold: int, fade_out: int ) -> Generator[Tuple[int, bool, bool], None, None]: for t in range(fade_in): yield 255 * t // fade_in, False, False for t in range(hold): yield 255, False, False for t in range(fade_out): yield 255 * (fade_out - t) // fade_out, True, False yield 0, True, True class Spot(pg.sprite.Sprite): def __init__( self, color: pg.Color, position: Tuple[int, int], radius: int, fade_in: int, hold: int, fade_out: int, ) -> None: super().__init__() self.rect = pg.Rect( position[0] - radius, position[1] - radius, radius * 2, radius * 2 ) self.image = pg.Surface(self.rect.size) self.image.set_colorkey(Colors.Black) self.image.fill(Colors.Black) pg.draw.ellipse( self.image, color, ((0, 0), self.rect.size), ) self.start_next = False self.finished = False self.state = fade_statemachine(fade_in, hold, fade_out) def update(self) -> None: if not self.finished: alpha, self.start_next, self.finished = next(self.state) self.image.set_alpha(alpha) def draw(self, dest: pg.Surface) -> None: dest.blit(self.image, self.rect) class DoubleSpot(Effect): def __init__( self, bounds: pg.Rect, color: Union[pg.Color, Generator[pg.Color, None, None]], radius: int = 200, hold: int = 60 * 1, fade_out: bool = False, fade_in: bool = False, beat_adapt: bool = False, *groups: pg.sprite.Group ) -> None: self.color = color self.radius: int = radius self.fade_in_time: int = hold // 4 if fade_in else 0 self.fade_out_time: int = hold // 4 if fade_out else 0 self.hold_time: int = hold - (self.fade_in_time + self.fade_out_time) self.beat_adapt: bool = beat_adapt self.spots: List[Spot] = [] self.randrange = ( (bounds.width - 2 * self.radius) // (self.radius * 2), (bounds.height - 2 * self.radius) // (self.radius * 2), ) image = pg.Surface(size=bounds.size) image.set_colorkey(Colors.Black) super().__init__(image, bounds, *groups) self.update(is_beat=False, frames_per_beat=0.0) def add_spot(self, spot_color): position = ( random.randint(0, self.randrange[0]) * self.radius * 2 + self.radius, random.randint(0, self.randrange[1]) * self.radius * 2 + self.radius, ) while any( ( (s.rect.centerx == position[0] and s.rect.centery == position[1]) for s in self.spots ) ): position = ( random.randint(0, self.randrange[0]) * self.radius * 2 + self.radius, random.randint(0, self.randrange[1]) * self.radius * 2 + self.radius, ) self.spots.append( Spot( color=spot_color, position=position, radius=self.radius, fade_in=self.fade_in_time, hold=self.hold_time, fade_out=self.fade_out_time, ) ) def update(self, *args: Any, **kwargs: Any) -> None: self.image.fill(Colors.Black) if self.beat_adapt and kwargs["frames_per_beat"]: hold = kwargs["frames_per_beat"] - 2 self.fade_in_time = int(hold // 4 if self.fade_in_time else 0) self.fade_out_time = int(hold // 4 if self.fade_out_time else 0) self.hold_time = int(hold - self.fade_in_time) if not self.beat_adapt: if len(self.spots) == 0 or ( self.fade_in_time != 0 and self.spots[0].start_next and len(self.spots) == 2 ): spot_color = ( self.color if isinstance(self.color, pg.Color) else next(self.color) ) self.add_spot(spot_color) self.add_spot(spot_color) elif kwargs["is_beat"]: spot_color = ( self.color if isinstance(self.color, pg.Color) else next(self.color) ) self.add_spot(spot_color) self.add_spot(spot_color) for spot in self.spots: if not spot.finished: spot.update() spot.draw(self.image) if self.spots: first_spot = self.spots[0] while self.spots and first_spot.finished: first_spot = self.spots.pop(0) del first_spot if self.spots: first_spot = self.spots[0]