import pygame from typing import Tuple import numpy as np import pygame.gfxdraw from math import ceil from config import HEIGHT, WIDTH from utils import clamp, mix BLUR_RADIUS = 3 TTL_LOSS_NEAR_BORDER_IN_PIXELS = 200 UP_DRAFT = 300 class Ember: def __init__(self): self.reset() def reset(self): self.position = (np.random.randint(0, WIDTH), HEIGHT) self.up_draft = np.random.uniform(0.3, 1) * UP_DRAFT self.time_to_live = np.random.uniform(1, 6) self.color_offset = np.random.uniform(0, 1) self.size = np.random.uniform(2.5, 6.5) def update(self, velocity: Tuple[float, float], delta_time: float): self.position = ( self.position[0] + velocity[0] * delta_time, self.position[1] + (velocity[1] - self.up_draft) * delta_time, ) self.time_to_live -= delta_time if self.distance_from_screen_border() < TTL_LOSS_NEAR_BORDER_IN_PIXELS: self.time_to_live = min( self.distance_from_screen_border() / TTL_LOSS_NEAR_BORDER_IN_PIXELS, self.time_to_live, ) if self.time_to_live < 0: self.reset() def draw(self, screen: pygame.Surface): color = ( int(mix(255, 189, self.color_offset)), int(mix(165, 40, self.color_offset)), int(mix(0, 40, self.color_offset)), 255 * (1 - clamp((0.5 - self.time_to_live) * 2, 0, 1) ** 2), ) draw_antialiased_circle( screen, self.position[0], self.position[1], int(self.size), color ) def distance_from_screen_border(self): return min( self.position[0], WIDTH - self.position[0], self.position[1], # HEIGHT - self.position[1], ) def __str__(self) -> str: return f"Ember(position={self.position}, velocity={self.velocity}, time_to_live={self.time_to_live})" def draw_antialiased_circle(surface: pygame.Surface, x, y, radius, color): for dx in range(-ceil(radius), ceil(radius) + 1): for dy in range(-ceil(radius), ceil(radius) + 1): distance = ((dx - x % 1) ** 2 + (dy - y % 1) ** 2) ** 0.5 - radius if ( distance < 0 and 0 <= x + dx < surface.get_width() and 0 <= y + dy < surface.get_height() ): alpha = min(color[3] / 255, -distance / BLUR_RADIUS) value = surface.get_at((int(x + dx), int(y + dy))) surface.set_at( (int(x + dx), int(y + dy)), ( mix(value[0], color[0], alpha), mix(value[1], color[1], alpha), mix(value[2], color[2], alpha), mix(value[3], color[3], alpha), ), )