diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 1a4fd27..1d5af48 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -6,9 +6,9 @@ permissions: on: push: - branches: [ dev, main ] + branches: [ main ] pull_request: - branches: [ dev, main ] + branches: [ main ] workflow_dispatch: jobs: diff --git a/100 b/100 new file mode 100644 index 0000000..e69de29 diff --git a/bruhanimate/bruheffect/audio_effect.py b/bruhanimate/bruheffect/audio_effect.py index 69c9d97..e945e4d 100644 --- a/bruhanimate/bruheffect/audio_effect.py +++ b/bruhanimate/bruheffect/audio_effect.py @@ -22,12 +22,25 @@ from bruhcolor import bruhcolored from .base_effect import BaseEffect + from ..bruhutil import Buffer class AudioEffect(BaseEffect): + """ + A base class for audio effects in a terminal-based visualizer. + """ def __init__( - self, buffer, background: str, num_bands: int = 24, audio_halt: int = 10 + self, buffer: Buffer, background: str, num_bands: int = 24, audio_halt: int = 10 ): + """ + Initialize the AudioEffect class. + + Args: + buffer (Buffer): The effect buffer to push updates to. + background (str): Character or string to use for the background. + num_bands (int, optional): Number of EQ bands to show. Defaults to 24. + audio_halt (int, optional): How often we should update the bands, (frame_number % halt == 0 then update). Defaults to 10. + """ super(AudioEffect, self).__init__(buffer, background) self.FORMAT = pyaudio.paInt16 self.CHANNELS = 1 @@ -78,6 +91,15 @@ def __init__( def set_audio_properties( self, num_bands=24, audio_halt=10, use_gradient=True, non_gradient_color=27 ): + """ + Set the properties for the AudioEffect class. + + Args: + num_bands (int, optional): Number of EQ bands to show. Defaults to 24. + audio_halt (int, optional): How often we should update the bands, (frame_number % halt == 0 then update). Defaults to 10. + use_gradient (bool, optional): Whether or not to use color gradient. Defaults to True. + non_gradient_color (int, optional): Color to use if not a gradient. Defaults to 27 (blue). + """ self.BANDS = num_bands self.audio_halt = audio_halt self.band_ranges = self.generate_even_ranges(self.BANDS, 0, self.buffer.width()) @@ -85,7 +107,17 @@ def set_audio_properties( self.use_gradient = use_gradient self.non_gradient_color = non_gradient_color - def evenly_distribute_original_values(self, original_list, desired_width): + def evenly_distribute_original_values(self, original_list: list[int], desired_width: int): + """ + Evenly distribute the values in a list to fit within a certain width. + + Args: + original_list (list[int]): The list of values to distribute. + desired_width (int): The width to which the values should be distributed. + + Returns: + list[int]: The evenly distributed list of values. + """ repeat_count = desired_width // len(original_list) extra_elements = desired_width % len(original_list) expanded_list = [] @@ -96,7 +128,13 @@ def evenly_distribute_original_values(self, original_list, desired_width): extra_elements -= 1 return expanded_list - def set_orientation(self, orientation): + def set_orientation(self, orientation: str): + """ + Set the orientation of the visualizer. + + Args: + orientation (str): The orientation to set ("top" or "bottom"). + """ if orientation in ["top", "bottom"]: self.orientation = orientation if self.orientation == "bottom": @@ -109,6 +147,13 @@ def set_audio_gradient( gradient=[232, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255], mode="extend", ): + """ + Set the audio gradient for visualizing audio data. + + Args: + gradient (list, optional): List of colors to use for gradient. Defaults to [232, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255]. + mode (str, optional): Do we want to repeat the gradient or extend the list evenly. Defaults to "extend". + """ self.base_gradient = gradient self.gradient_mode = mode if self.gradient_mode == "repeat": @@ -129,6 +174,18 @@ def set_audio_gradient( ] def process_audio(self, data, frame_count, time_info, status): + """ + Process the audio data and update the visualizer buffer. + + Args: + data (_type_): Audio data from the stream. + frame_count (_type_): N/A + time_info (_type_): N/A + status (_type_): N/A + + Returns: + tuple: (data, pyaudio.paContinue) + """ audio_array = np.frombuffer(data, dtype=np.int16) fft_result = np.fft.rfft(audio_array) magnitudes = np.abs(fft_result) @@ -139,7 +196,16 @@ def process_audio(self, data, frame_count, time_info, status): ] return (data, pyaudio.paContinue) - def map_bands_to_range(self, N): + def map_bands_to_range(self, N: int): + """ + Map the bands to a range of values between 0 and N-1. + + Args: + N (int): The range of values to map the bands to. + + Returns: + list[int]: The mapped bands. + """ min_band = min(self.bands) max_band = max(self.bands) rand_band = max_band - min_band if max_band != min_band else 1 @@ -147,7 +213,18 @@ def map_bands_to_range(self, N): scaled_bands = [int(band * N) for band in normalized_bands] return scaled_bands - def generate_even_ranges(self, groups, start, end): + def generate_even_ranges(self, groups: list[any], start: int, end: int): + """ + Generate even ranges from a list of groups. + + Args: + groups (list[any]): The list of groups to generate ranges from. + start (int): The starting index of the ranges. + end (int): The ending index of the ranges. + + Returns: + _type_: _description_ + """ approximate_group_size = round((end - start) / groups) intervals = [] for i in range(groups): @@ -156,7 +233,13 @@ def generate_even_ranges(self, groups, start, end): intervals.append((group_start, min(group_end, end))) return intervals - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): + """ + Render a single frame of the animation. + + Args: + frame_number (int): The current frame number of the animation. + """ if frame_number == 0: self.stream.start_stream() diff --git a/bruhanimate/bruheffect/base_effect.py b/bruhanimate/bruheffect/base_effect.py index d9006e8..80c4ea6 100644 --- a/bruhanimate/bruheffect/base_effect.py +++ b/bruhanimate/bruheffect/base_effect.py @@ -15,6 +15,7 @@ """ from abc import abstractmethod +from ..bruhutil.bruhffer import Buffer class BaseEffect: @@ -22,7 +23,14 @@ class BaseEffect: Class for keeping track of an effect, and updataing it's buffer """ - def __init__(self, buffer, background): + def __init__(self, buffer: Buffer, background: str): + """ + Base class for all effects. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): character or string to use for the background. + """ self.buffer = buffer self.background = background self.background_length = len(background) @@ -30,5 +38,5 @@ def __init__(self, buffer, background): @abstractmethod def render_frame(self, frame_number): """ - To be defined by each effect + To be defined by each effect. """ \ No newline at end of file diff --git a/bruhanimate/bruheffect/chatbot_effect.py b/bruhanimate/bruheffect/chatbot_effect.py index 1153039..9f39189 100644 --- a/bruhanimate/bruheffect/chatbot_effect.py +++ b/bruhanimate/bruheffect/chatbot_effect.py @@ -23,12 +23,25 @@ from threading import Thread from bruhcolor import bruhcolored -from ..bruhutil import Screen +from ..bruhutil import Screen, Buffer from .base_effect import BaseEffect class Key: - def __init__(self, character, representation, value, x, y): + """ + A class representing a key on the keyboard with its position and value. + """ + def __init__(self, character: str, representation: list[str], value: int, x: int, y: int): + """ + Initializes a Key object with the given parameters. + + Args: + character (str): The character represented by the key. + representation (list[str]): The visual representation of the key. + value (int): The value of the key. + x (int): The x-coordinate of the key on the screen. + y (int): The y-coordinate of the key on the screen. + """ self.x = x self.y = y self.character = character @@ -43,7 +56,21 @@ def __repr__(self): class GradientNoise: - def __init__(self, x, y, length, char_halt=1, color_halt=1, gradient_length=1): + """ + A class representing a noise effect with a color gradient. + """ + def __init__(self, x: int, y: int, length: int, char_halt: int = 1, color_halt: int = 1, gradient_length: int = 1): + """ + Initializes a GradientNoise object with the given parameters. + + Args: + x (int): The x-coordinate of the noise effect on the screen. + y (int): The y-coordinate of the noise effect on the screen. + length (int): The length of the noise effect on the screen. + char_halt (int, optional): The halt of characters changing (frame_number % character_halt == 0). Defaults to 1. + color_halt (int, optional): The halt of colors changing (frame_number % color_halt == 0). Defaults to 1. + gradient_length (int, optional): Length of the gradient. Defaults to 1. + """ self.x = x self.y = y self.__gradient_length = gradient_length @@ -112,11 +139,23 @@ def __init__(self, x, y, length, char_halt=1, color_halt=1, gradient_length=1): ] # change the gradient - def update_gradient(self, gradient): + def update_gradient(self, gradient: list[int]): + """ + Updates the gradient of the noise. + + Args: + gradient (list[int]): The new gradient to use. + """ self.__gradient = [c for c in gradient for _ in range(self.__gradient_length)] return self def generate(self, frame_number: int): + """ + Generates the next frame of the noise. + + Args: + frame_number (int): The current frame number. + """ if self.done_generating: return # is it time to change the noise chars? @@ -152,6 +191,9 @@ def mark_done(self): class Loading: + """ + A class to handle the loading animation. + """ def __init__(self, animate_part: GradientNoise): self.animate_part = animate_part @@ -163,7 +205,20 @@ def mark_done(self): class StringStreamer: + """ + A class to handle the string streamer animation. + """ def __init__(self, x: int, y: int, text: str, start_frame: int, halt: int = 1): + """ + Initialize the StringStreamer class. + + Args: + x (int): The x position of the string streamer. + y (int): The y position of the string streamer. + text (str): The text to be displayed. + start_frame (int): The frame number to start the animation. + halt (int, optional): Halt value to delay the animation (frame_number & halt == 0). Defaults to 1. + """ self.x = x self.y = y self.text = text @@ -174,6 +229,12 @@ def __init__(self, x: int, y: int, text: str, start_frame: int, halt: int = 1): self.complete = False def generate(self, frame: int): + """ + Generate the string streamer animation for a given frame number. + + Args: + frame (int): The current frame number. + """ if self.complete or frame < self.__start_frame: return if frame % self.__halt == 0: @@ -183,12 +244,23 @@ def generate(self, frame: int): class OllamaApiCaller: + """ + A class to interact with the Ollama API. + """ def __init__( self, model: str, use_message_history: bool = False, message_history_cap: int = 5, ): + """ + Initialize the OllamaApiCaller class. + + Args: + model (str): The Ollama model to use. + use_message_history (bool, optional): Whether or not to use message history. Defaults to False. + message_history_cap (int, optional): How many messages should we use, sliding window. Defaults to 5. + """ self.model = model self.url = "http://127.0.0.1:11434/api/chat" self.busy = False @@ -201,6 +273,17 @@ def __init__( def chat( self, message: str, user: str | None, previous_messages: list[str] | None = None ) -> str: + """ + Send a chat message to the Ollama API and get a response. + + Args: + message (str): The message to send. + user (str | None): The user who sent the message. + previous_messages (list[str] | None, optional): Past sent messages. Defaults to None. + + Returns: + str: Response from ollama. + """ self.busy = True self.state = "running" payload = { @@ -230,6 +313,9 @@ def chat( class OpenAiCaller: + """ + Class to interact with the OpenAI API using the `openai` Python package. + """ def __init__( self, client: openai.OpenAI | openai.AzureOpenAI, @@ -237,6 +323,15 @@ def __init__( use_message_history: bool = False, message_history_cap: int = 5, ): + """ + Initialize the OpenAiCaller class. + + Args: + client (openai.OpenAI | openai.AzureOpenAI): The OpenAI client object. + model (str): The OpenAI model to be used. + use_message_history (bool, optional): Whether or not to use message history. Defaults to False. + message_history_cap (int, optional): Amount of messages from history to use, sliding window. Defaults to 5. + """ self.client = client self.model = model self.busy = False @@ -247,6 +342,16 @@ def __init__( self.message_history_cap = message_history_cap def chat(self, message: str, user: str | None) -> str: + """ + Send a chat message to the OpenAI API and get a response. + + Args: + message (str): The message to be sent to the OpenAI API. + user (str | None): The user who sent the message. + + Returns: + str: The response from the OpenAI API. + """ self.busy = True self.state = "running" response = self.client.chat.completions.create( @@ -277,7 +382,19 @@ def chat(self, message: str, user: str | None) -> str: class ChatbotEffect(BaseEffect): - def __init__(self, screen: Screen, buffer, back_buffer, background: str = " "): + """ + A class to create a chatbot effect. + """ + def __init__(self, screen: Screen, buffer: Buffer, back_buffer: Buffer, background: str = " "): + """ + Initialize the ChatbotEffect class. + + Args: + screen (Screen): Our instance of the terminal window + buffer (Buffer): Effect buffer to push updates to. + back_buffer (Buffer): The buffer to push the effect updates to. + background (str, optional): Character or string to use for the background. Defaults to " ". + """ super(ChatbotEffect, self).__init__(buffer, background) self.back_buffer = back_buffer self.screen = screen @@ -350,6 +467,16 @@ def __init__(self, screen: Screen, buffer, back_buffer, background: str = " "): self.avatar_placed = False def __expand_list(self, original_list: list[int|str], n: int, mul: int = 1): + """ + Expands a list by adding `n` elements to the end of it, each multiplied by `mul`. + + Args: + original_list (list[int | str]): The list to be expanded. + n (int): The number of elements to add to the end of the list. + mul (int, optional): The multiplier for each added element. Defaults to 1. + Returns: + _type_: _description_ + """ l = [] for val in original_list: for _ in range(mul): @@ -367,6 +494,20 @@ def set_chatbot_properties( use_message_history: bool = False, message_history_cap: int = 5, ): + """ + Sets the properties for the chatbot. + + Args: + interface (str | None): The interface type, e.g., "OpenAI" or "Azure OpenAI". + model (str): The name of the AI model to use. + user (str | None, optional): The name of the user. Defaults to None. + client (openai.OpenAI | openai.AzureOpenAI | None, optional): The client to use. Defaults to None. + use_message_history (bool, optional): Whether or not to use message history. Defaults to False. + message_history_cap (int, optional): How many messages from history to use, sliding window. Defaults to 5. + + Raises: + Exception: If the interface is not recognized, an exception will be raised. + """ if interface: self.interface = interface if user: @@ -391,14 +532,33 @@ def set_chatbot_properties( ) def set_second_effect(self, effect: str): + """ + Sets the second effect for the chatbot. + + Args: + effect (str): The effect to use + """ self.second_effect = effect def set_chatbot_print_halt(self, halt: int): + """ + Sets the chatbot print halt value to control how often it prints messages. + + Args: + halt (int): (frame_number % halt == 0) + """ self.chatbot_print_halt = halt def set_gradient_noise_halts( self, char_halt: int | None = None, color_halt: int | None = None ): + """ + Sets the gradient noise halts for character and color shifts. + + Args: + char_halt (int | None, optional): Sets the character halt for gradient noise. Defaults to None. + color_halt (int | None, optional): Sets the color halt for gradient noise. Defaults to None. + """ if char_halt: self.gradient_noise_char_halt = char_halt if color_halt: @@ -415,6 +575,19 @@ def set_chatbot_user_colors( user_avatar_color: int | str | None = None, user_avatar_text_color: int | str | None = None, ): + """ + Sets the colors for the chatbot and user messages. + + Args: + chatbot_text_color (int | str | None, optional): Color of chatbot output text. Defaults to None. + chatbot_background_color (int | str | None, optional): Background of chatbot output text. Defaults to None. + chatbot_avatar_color (int | str | None, optional): Text color of avatar logo. Defaults to None. + chatbot_avatar_text_color (int | str | None, optional): Background color of avatar logo. Defaults to None. + user_text_color (int | str | None, optional): Color of user text. Defaults to None. + user_background_color (int | str | None, optional): Background color of user text. Defaults to None. + user_avatar_color (int | str | None, optional): Text color of user avatar. Defaults to None. + user_avatar_text_color (int | str | None, optional): Background color of user avatar. Defaults to None. + """ if chatbot_text_color: self.chatbot_text_color = chatbot_text_color if chatbot_background_color: @@ -433,6 +606,12 @@ def set_chatbot_user_colors( self.user_avatar_text_color = user_avatar_text_color def set_avatar_properties(self, size: int): + """ + Set avatar properties for user and chatbot. + + Args: + size (int): Length of the avatars on left side of screen. + """ self.avatar_size = size self.user_cursor_x_idx = self.avatar_size for ydx in range(self.screen.height): @@ -442,24 +621,66 @@ def set_avatar_properties(self, size: int): ] def set_chatbot_stats(self, show: bool = False): + """ + Set chatbot stats on the right side of screen. + + Args: + show (bool, optional): Whether or not to show chatbot stats. Defaults to False. + """ self.show_stats = show def set_chatbot_blink_halt(self, halt: int): + """ + Set chatbot blink and halt properties. + + Args: + halt (int): (frame_number % halt == 0) + """ self.blink_halt = halt def set_divider_flag(self, divider: bool, divider_character: str = "-"): + """ + Set the divider flag and character for screen. + + Args: + divider (bool): Whether or not to show the divider. + divider_character (str, optional): Character to use for the divider. Defaults to "-". + """ self.divider = divider self.divider_character = divider_character def set_chatbot_cursor_colors(self, color_one: int | str, color_two: int | str): + """ + Set the colors for chatbot cursor. + + Args: + color_one (int | str): First color of cursor. + color_two (int | str): Second color of cursor. + """ self.blink_color_one = color_one self.blink_color_two = color_two def set_chatbot_text_gradient(self, gradient: list[int|str], mul: int): + """ + Set the text color for chatbot to use a gradient. + + Args: + gradient (list[int | str]): Gradient color to use for chatbot loading. + mul (int): Multiplier for the gradient effect. + """ self.gradient_text_color = gradient self.gradient_mul = mul - def __handle_keyboard_result(self, result): + def __handle_keyboard_result(self, result: int): + """ + Handle the keyboard input and return a result. + + Args: + result (int): Result from pressing down keyboard from win32 package. + + Returns: + bool: Something . . . + """ if result: if result == -300: # backspace if self.user_cursor_x_idx == self.avatar_size: @@ -533,6 +754,9 @@ def __handle_keyboard_result(self, result): return False def __scroll_up(self): + """ + Scroll up the display by one line. + """ first_key = min(self.all_keys.keys()) last_key = max(self.all_keys.keys()) if len(self.all_keys) == 0 or first_key == 0: @@ -549,6 +773,9 @@ def __scroll_up(self): self.current_bottom_y = self.current_bottom_y - 1 def __scroll_down(self): + """ + Scroll down the display by one line. + """ last_key = max(self.all_keys.keys()) self.all_keys[last_key + 1] = [ Key(" ", [ord(" ")], ord(" "), x=_, y=last_key + 1) @@ -564,9 +791,18 @@ def __scroll_down(self): self.all_keys = {i: self.all_keys[key] for i, key in enumerate(sorted(self.all_keys.keys()))} def scroll_keys(self, shift: int = 1): + """ + Scroll the keys up or down by one line + + Args: + shift (int, optional): How much to scroll. Defaults to 1. + """ self.__scroll_down() if shift == 1 else self.__scroll_up() def place_all_keys(self): + """ + Place all keys on to the buffer. + """ for idx, vals in self.all_keys.items(): if idx > self.current_bottom_y or idx < self.current_top_y: continue @@ -580,7 +816,13 @@ def place_all_keys(self): else: self.buffer.put_char(jdx + displacement, idx - self.current_top_y, key.character) - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): + """ + Render the current state of the buffer to the terminal. + + Args: + frame_number (int): The frame number to render. + """ self.buffer.clear_buffer() if self.turn == 0: if not self.avatar_placed: diff --git a/bruhanimate/bruheffect/draw_lines_effect.py b/bruhanimate/bruheffect/draw_lines_effect.py index d343cab..d4d23df 100644 --- a/bruhanimate/bruheffect/draw_lines_effect.py +++ b/bruhanimate/bruheffect/draw_lines_effect.py @@ -15,10 +15,21 @@ """ from .base_effect import BaseEffect +from ..bruhutil import Buffer class Line: - def __init__(self, start_point, end_point): + """ + A class representing a line segment. + """ + def __init__(self, start_point: tuple[int], end_point: tuple[int]): + """ + Initializes a Line object with the given start and end points. + + Args: + start_point (tuple[int]): Start point of the line segment as a tuple (x, y). + end_point (tuple[int]): End point of the line segment as a tuple (x, y). + """ if start_point and end_point: self.start_point = (start_point[0] * 2, start_point[1] * 2) self.end_point = (end_point[0] * 2, end_point[1] * 2) @@ -26,25 +37,60 @@ def __init__(self, start_point, end_point): self.start_point = None self.end_point = None - def update_points(self, start_point, end_point): + def update_points(self, start_point: tuple[int], end_point: tuple[int]): + """ + Updates the points of the line segment with new values. + + Args: + start_point (tuple[int]): New start point of the line segment as a tuple (x, y). + end_point (tuple[int]): New end point of the line segment as a tuple (x, y). + """ self.start_point = (start_point[0], start_point[1]) self.end_point = (end_point[0], end_point[1]) def get_points(self): + """ + Returns the start and end points of the line segment. + + Returns: + tuple[tuple[int], tuple[int]]: The start and end points of the line. + """ return self.start_point, self.end_point class DrawLinesEffect(BaseEffect): - def __init__(self, buffer, background, char=None, thin=False): + def __init__(self, buffer: Buffer, background: str, char: str = None, thin: bool = False): + """ + Initializes the DrawLinesEffect class. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): Character or string to use as the background. + char (str, optional): Character to use for line drawing. Defaults to None. + thin (bool, optional): Whether or not the line should be thin. Defaults to False. + """ super(DrawLinesEffect, self).__init__(buffer, background) self.lines = [] self.char = char self.thin = thin - def add_line(self, start_point, end_point): + def add_line(self, start_point: tuple[int], end_point: tuple[int]): + """ + Adds a line to the effect. + + Args: + start_point (tuple[int]): Start point of the line as a tuple (x, y). + end_point (tuple[int]): End point of the line as a tuple (x, y). + """ self.lines.append(Line(start_point, end_point)) - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): + """ + Renders the effect for a given frame number. + + Args: + frame_number (int): The current frame number. + """ if frame_number == 0 and len(self.lines) > 0: for y in range(self.buffer.height()): self.buffer.put_at(0, y, self.background * self.buffer.width()) diff --git a/bruhanimate/bruheffect/firework_effect.py b/bruhanimate/bruheffect/firework_effect.py index 8ef5431..585413f 100644 --- a/bruhanimate/bruheffect/firework_effect.py +++ b/bruhanimate/bruheffect/firework_effect.py @@ -30,7 +30,23 @@ class Particle: - def __init__(self, x, y, dx, dy, width, height, symbol="*", life: int = None): + """ + Class representing a particle in a firework. + """ + def __init__(self, x: int, y: int, dx: float, dy: float, width: int, height: int, symbol: chr = "*", life: int = None): + """ + Initialize a particle with the given parameters. + + Args: + x (int): The x position of the particle. + y (int): The y position of the particle. + dx (float): The horizontal velocity of the particle. + dy (float): The vertical velocity of the particle. + width (int): The width of the canvas or screen. + height (int): The height of the canvas or screen. + symbol (chr, optional): The character that will be deplayed to the screen. Defaults to "*". + life (int, optional): How long this particle can live for (frames). Defaults to None. + """ self.x = x self.y = y self.previous_x = x @@ -43,20 +59,24 @@ def __init__(self, x, y, dx, dy, width, height, symbol="*", life: int = None): self.symbol = symbol def update(self): - # Update position + """ + Function to update a particle and it's parameters each frame. + """ self.previous_x = self.x self.previous_y = self.y self.x += self.dx self.y += self.dy - # Apply gravity to simulate a slight downward motion self.dy += 0.05 - # Decrease life self.life -= 1 - # Flicker effect + + # This is what flickers the particles, like a firework if random.random() > 0.5: self.symbol = "*" if self.symbol == "." else "." def is_alive(self): + """ + Whether or not the particle is still alive. A Particle is alive if it has life left and is within bounds. + """ # Particle is alive if it has life left and is within bounds return ( self.life > 0 @@ -66,7 +86,22 @@ def is_alive(self): class Firework: - def __init__(self, firework_type: FireworkType, height, width, firework_color_type: str = None, color_enabled: bool = False, allowed_firework_types: List[str] = None): + """ + The Firework class. Responsible for creating and updating fireworks. + """ + + def __init__(self, firework_type: FireworkType, height: int, width: int, firework_color_type: str = None, color_enabled: bool = False, allowed_firework_types: List[str] = None): + """ + Initialize a Firework object. + + Args: + firework_type (FireworkType): The type of firework this object should be. (eg. 'random', 'ring', 'snowflake'). + height (int): The height of the display area (generally the terminal window). + width (int): The width of the display area (generally the terminal window). + firework_color_type (str, optional): The type of color to be applied to the Firework. Defaults to None. + color_enabled (bool, optional): Whether of not the Firework should have color. Defaults to False. + allowed_firework_types (List[str], optional): List of allowed firework types in the instance firework_type is 'random'. Defaults to None. + """ self.width = width self.height = height self.x = random.randint(0, self.width - 1) @@ -96,6 +131,12 @@ def __init__(self, firework_type: FireworkType, height, width, firework_color_ty self.zigzag_amplitude = random.uniform(0.2, 0.5) # Width of zigzag def update(self): + """ + Function to update the firework's state. + If the firework hasn't exploded yet, then it's trail needs to be advanced. + If the firework has exploded, then we need to update the particles that make + up the firework. + """ if not self.exploded: # Store previous position self.previous_x = self.x @@ -125,11 +166,17 @@ def update(self): self.particles = [p for p in self.particles if p.is_alive()] def move_straight(self): + """ + Function to move the firework trail straight up and down. + """ # Move upward with slight angle self.y -= self.speed self.x += math.sin(self.angle) * self.speed * 0.5 def move_arc(self): + """ + Function to move the firework trail in an arcing trajectory. + """ # Create arcing trajectory progress = (self.height - self.y) / (self.height - self.peak) arc_offset = math.sin(progress * math.pi) * 2.0 @@ -137,12 +184,19 @@ def move_arc(self): self.x += self.arc_direction * arc_offset * 0.2 def move_zigzag(self): + """ + Function to move the firework trail in a zigzag pattern. + """ # Create zigzag pattern self.zigzag_phase += 0.2 self.y -= self.speed self.x += math.sin(self.zigzag_phase) * self.zigzag_amplitude def create_particles(self): + """ + Function to create the particles for the firework after it has + reached it's peak. It is determined by the firework_type parameter. + """ if self.explosion_type == 'circular': self.circular_explosion() elif self.explosion_type == 'ring': @@ -203,6 +257,9 @@ def create_particles(self): self.portal_explosion() def circular_explosion(self): + """ + Creates a circular explosion effect. + """ for _ in range(30): angle = random.uniform(0, 2 * math.pi) speed = random.uniform(0.5, 1.5) @@ -211,6 +268,9 @@ def circular_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def ring_explosion(self): + """ + Creates a ring explosion effect. + """ for angle in range(0, 360, 12): # Ring pattern with evenly spaced particles rad = math.radians(angle) dx = math.cos(rad) @@ -218,6 +278,9 @@ def ring_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def starburst_explosion(self): + """ + Creates a starburst explosion effect. + """ for angle in range( 0, 360, 45 ): # Starburst with particles in specific directions @@ -228,6 +291,9 @@ def starburst_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def cone_explosion(self): + """ + Creates a cone explosion effect. + """ for _ in range(20): angle = random.uniform( -math.pi / 6, math.pi / 6 @@ -238,6 +304,9 @@ def cone_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def spiral_explosion(self): + """ + Creates a spiral explosion effect. + """ for i in range(20): angle = i * 0.3 # Gradually increasing angle for spiral effect speed = 0.1 * i # Particles spread out as the spiral grows @@ -246,6 +315,9 @@ def spiral_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def wave_explosion(self): + """ + Creates a wave explosion effect. + """ for i in range(30): angle = i * 0.2 # Slightly increase angle for wave effect speed = random.uniform(0.5, 1.0) @@ -254,6 +326,9 @@ def wave_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def burst_explosion(self): + """ + Creates a burst explosion effect. + """ for _ in range(20): angle = random.uniform(0, 2 * math.pi) # Random angles for burst speed = random.uniform(0.5, 1.5) @@ -265,6 +340,9 @@ def burst_explosion(self): particle.dy += 0.5 # Increase downward velocity def cross_explosion(self): + """ + Creates a cross explosion effect. + """ for angle in [0, 90, 180, 270]: # Particles in cross directions rad = math.radians(angle) speed = random.uniform(0.5, 1.5) @@ -273,6 +351,9 @@ def cross_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def flower_explosion(self): + """ + Creates a flower explosion effect. + """ for angle in range(0, 360, 30): rad = math.radians(angle) speed = random.uniform(0.5, 1.0) @@ -286,6 +367,9 @@ def flower_explosion(self): self.particles.append(Particle(self.x, self.y, dx_petal, dy_petal, self.width, self.height)) def double_ring_explosion(self): + """ + Creates a double ring explosion effect. + """ for radius_multiplier in [0.8, 1.2]: # Two rings at slightly different radii for angle in range(0, 360, 15): rad = math.radians(angle) @@ -295,6 +379,9 @@ def double_ring_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def heart_explosion(self): + """ + Creates a heart explosion effect. + """ for t in range(0, 360, 10): # Parametric heart shape rad = math.radians(t) dx = 16 * math.sin(rad) ** 3 * 0.1 @@ -302,6 +389,9 @@ def heart_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def star_explosion(self): + """ + Creates a star explosion effect. + """ for i in range(5): # 5-point star angle = i * 2 * math.pi / 5 dx = math.cos(angle) * 1.5 @@ -311,6 +401,9 @@ def star_explosion(self): self.particles.append(Particle(self.x, self.y, -dx, -dy, self.width, self.height)) def fireball_explosion(self): + """ + Creates a fireball explosion effect. + """ for _ in range(50): # Dense number of particles angle = random.uniform(0, 2 * math.pi) speed = random.uniform(0.2, 1.5) @@ -319,6 +412,9 @@ def fireball_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def diamond_explosion(self): + """ + Creates a diamond explosion effect. + """ for angle in [45, 135, 225, 315]: # Four main directions for diamond points rad = math.radians(angle) dx = math.cos(rad) * 1.5 @@ -331,6 +427,9 @@ def diamond_explosion(self): self.particles.append(Particle(self.x, self.y, dx_offset, dy_offset, self.width, self.height)) def burst_with_shockwave_explosion(self): + """ + Creates a burst with shockwave explosion effect. + """ # Main burst particles for angle in range(0, 360, 20): rad = math.radians(angle) @@ -347,6 +446,9 @@ def burst_with_shockwave_explosion(self): self.particles.append(Particle(self.x, self.y, dx * 0.5, dy * 0.5, self.width, self.height, life=5)) # Short lifespan for shockwave def snowflake_explosion(self): + """ + Creates a snowflake explosion effect. + """ for angle in range(0, 360, 60): # Six main directions rad = math.radians(angle) speed = random.uniform(0.8, 1.0) @@ -362,6 +464,9 @@ def snowflake_explosion(self): self.particles.append(Particle(self.x, self.y, dx_branch, dy_branch, self.width, self.height)) def cluster_explosion(self): + """ + Creates a cluster explosion effect with particles moving in different directions + """ for angle in range(0, 360, 30): rad = math.radians(angle) speed = 1.2 @@ -377,6 +482,9 @@ def cluster_explosion(self): self.particles.append(Particle(self.x + offset_dx, self.y + offset_dy, offset_dx * 0.5, offset_dy * 0.5, self.width, self.height)) def comet_tail_explosion(self): + """ + Creates a comet tail explosion effect with particles following a comet-like path + """ # Main comet direction comet_angle = random.choice([45, 135, 225, 315]) # Random diagonal angle for comet rad = math.radians(comet_angle) @@ -396,6 +504,9 @@ def comet_tail_explosion(self): self.particles.append(Particle(self.x - trail_dx * i, self.y - trail_dy * i, trail_dx * 0.5, trail_dy * 0.5, self.width, self.height)) def willow_explosion(self): + """ + Creates a willow explosion effect with multiple branches extending from the center + """ num_arms = 10 # Number of branches in the willow effect angle_offset = 70 # Constrain angle range to mostly horizontal @@ -422,7 +533,9 @@ def willow_explosion(self): self.particles.append(trail_particle) def dna_explosion(self): - """Creates a double helix pattern resembling DNA structure""" + """ + Creates a double helix pattern resembling DNA structure + """ num_points = 30 radius = 1.0 vertical_stretch = 0.5 @@ -447,7 +560,9 @@ def dna_explosion(self): self.particles.append(Particle(self.x, self.y, dx_bridge, dy_bridge, self.width, self.height, symbol="-")) def infinity_explosion(self): - """Creates an infinity symbol (∞) pattern""" + """ + Creates an infinity symbol (∞) pattern + """ num_points = 40 size = 1.2 @@ -461,7 +576,9 @@ def infinity_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def galaxy_explosion(self): - """Creates a spiral galaxy pattern with arms and central bulge""" + """ + Creates a spiral galaxy pattern with arms and central bulge + """ # Central bulge for _ in range(20): angle = random.uniform(0, 2 * math.pi) @@ -482,7 +599,9 @@ def galaxy_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height)) def phoenix_explosion(self): - """Creates a rising phoenix pattern with wings and tail""" + """ + Creates a rising phoenix pattern with wings and tail + """ # Central rising column for i in range(10): dy = -1.0 - (i * 0.1) # Upward movement @@ -513,7 +632,9 @@ def phoenix_explosion(self): self.particles.append(Particle(self.x, self.y, dx, dy, self.width, self.height, symbol="~")) def fountain_explosion(self): - """Creates an upward-shooting fountain pattern with cascading particles""" + """ + Creates an upward-shooting fountain pattern with cascading particles + """ num_streams = 5 particles_per_stream = 8 @@ -540,7 +661,9 @@ def fountain_explosion(self): self.particles.append(particle) def butterfly_explosion(self): - """Creates a butterfly pattern with wings that flutter""" + """ + Creates a butterfly pattern with wings that flutter + """ # Wing shape parameters wing_points = 20 flutter_speed = 0.2 @@ -571,7 +694,9 @@ def butterfly_explosion(self): self.width, self.height, symbol="█")) def dragon_explosion(self): - """Creates a dragon shape with body, wings, and fire breath""" + """ + Creates a dragon shape with body, wings, and fire breath + """ # Body body_length = 15 for i in range(body_length): @@ -604,7 +729,9 @@ def dragon_explosion(self): symbol=symbol, life=10)) def tornado_explosion(self): - """Creates a spinning tornado effect that grows wider at the top""" + """ + Creates a spinning tornado effect that grows wider at the top + """ height_layers = 15 base_radius = 0.2 @@ -628,7 +755,9 @@ def tornado_explosion(self): self.particles.append(particle) def matrix_explosion(self): - """Creates a Matrix-style digital rain effect""" + """ + Creates a Matrix-style digital rain effect + """ num_streams = 15 chars_per_stream = 8 @@ -655,7 +784,9 @@ def matrix_explosion(self): symbol=symbol, life=life)) def portal_explosion(self): - """Creates two connected portals with particles flowing between them""" + """ + Creates two connected portals with particles flowing between them + """ # Portal parameters portal_radius = 1.2 num_particles = 50 @@ -697,10 +828,22 @@ def portal_explosion(self): symbol=symbol, life=10)) def is_active(self): + """ + Returns True if the portal is active (emitting particles), False otherwise. + + Returns: + bool: Whether or not the firework is still alive + """ # Firework is active if it has not exploded or if particles are still alive return not self.exploded or len(self.particles) > 0 def get_colors(self): + """ + Get the colors for the firework based on its color type. + + Returns: + list[int]: List of colors that should be used to color the firework. + """ if self.firework_color_type == "solid": return [random.randint(0, 255)] elif self.firework_color_type == "twotone": @@ -720,6 +863,12 @@ def get_colors(self): ] def render(self, buffer: Buffer): + """ + Render the firework to a Buffer object. + + Args: + buffer (Buffer): Buffer used to house the image effect. + """ # Draw firework trail or particles in the buffer if not self.exploded: if 0 <= self.x < self.width and 0 <= self.y < self.height: @@ -751,6 +900,13 @@ class FireworkEffect(BaseEffect): """ def __init__(self, buffer: Buffer, background: str): + """ + Initialize the fireworks effect + + Args: + buffer (Buffer): Image buffer used to push updates to + background (str): Character that should be used for the background of the buffer + """ super(FireworkEffect, self).__init__(buffer, background) self.firework_type: FireworkType = "circular" self.firework_color_type: FireworkColorType = "solid" diff --git a/bruhanimate/bruheffect/game_of_life_effect.py b/bruhanimate/bruheffect/game_of_life_effect.py index 0e20d61..98d9fe9 100644 --- a/bruhanimate/bruheffect/game_of_life_effect.py +++ b/bruhanimate/bruheffect/game_of_life_effect.py @@ -17,7 +17,7 @@ import random from bruhcolor import bruhcolored -from ..bruhutil import LIFE_COLORS, LIFE_SCALES +from ..bruhutil import LIFE_COLORS, LIFE_SCALES, Buffer from .base_effect import BaseEffect @@ -28,13 +28,24 @@ class GameOfLifeEffect(BaseEffect): def __init__( self, - buffer, - background, - decay=False, - color=False, - color_type=None, - scale="random", + buffer: Buffer, + background: str, + decay: bool = False, + color: bool = False, + color_type: str = None, + scale: str = "random", ): + """ + Initialize the Game of Life effect with a buffer and background color. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): Character of string to use as the background. + decay (bool, optional): Whether or not cells should decay. Defaults to False. + color (bool, optional): Whether or not the decay should use color. Defaults to False. + color_type (str, optional): Type of color scale to use. Defaults to None. + scale (str, optional): Type of gray scale to use. Defaults to "random". + """ super(GameOfLifeEffect, self).__init__(buffer, background) self.decay = decay self.scale = scale @@ -59,7 +70,7 @@ def __init__( def _set_attributes(self): """ - Function to set the attributes of the effect + Function to set the attributes of the effect. """ self.grey_scale = ( LIFE_SCALES[random.choice(list(LIFE_SCALES.keys()))] @@ -71,11 +82,14 @@ def _set_attributes(self): self.DEAD = 0 self.mappings = {i: self.grey_scale[i] for i in range(len(self.grey_scale))} - def update_decay(self, decay, color_type="GREYSCALE", scale="random"): + def update_decay(self, decay: bool, color_type: str = "GREYSCALE", scale: str = "random"): """ - Function to enable to decay and select the color map - :param decay: True / False - :param color_type: color map for the effect + Function to enable the decay and select the color map. + + Args: + decay (bool): Whether or not the cell should decay + color_type (str, optional): Type of color to use. Defaults to "GREYSCALE". + scale (str, optional): Type of scale to use. Defaults to "random". """ self.decay = decay self.scale = scale @@ -83,13 +97,23 @@ def update_decay(self, decay, color_type="GREYSCALE", scale="random"): self.color_type = color_type self._set_attributes() - def update_rules(self, life_rule, death_rule): + def update_rules(self, life_rule: list[int], death_rule: list[int]): + """ + Function to update the rules for life and death. + + Args: + life_rule (list[int]): Lower and upper bound for number of neighbors that lead to life. + death_rule (list[int]): Lower and upper bound for number of neighbors that lead to death. + """ self.rules["life"] = life_rule self.rules["death"] = death_rule - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Function to render the next frame of the GOL effect + Function to render the next frame of the GOL effect. + + Args: + frame_number (int): The current frame number to render. """ if frame_number == 0: # INITIALIZE THE GAME for y in range(self.buffer.height()): diff --git a/bruhanimate/bruheffect/matrix_effect.py b/bruhanimate/bruheffect/matrix_effect.py index 062e6d8..05bb827 100644 --- a/bruhanimate/bruheffect/matrix_effect.py +++ b/bruhanimate/bruheffect/matrix_effect.py @@ -19,24 +19,38 @@ from bruhcolor import bruhcolored from .base_effect import BaseEffect +from ..bruhutil import Buffer class MatrixEffect(BaseEffect): """ - Effect to mimic the cliche coding backgroud with falling random characters + Effect to mimic the cliche coding backgroud with falling random characters. """ def __init__( self, - buffer, - background, - chracter_halt_range=(1, 2), - color_halt_range=(1, 2), - character_randomness_one=0.70, - character_randomness_two=0.60, - color_randomness=0.50, - gradient_length=1, + buffer: Buffer, + background: str, + chracter_halt_range: tuple[int] = (1, 2), + color_halt_range: tuple[int] = (1, 2), + character_randomness_one: float = 0.70, + character_randomness_two: float = 0.60, + color_randomness: float = 0.50, + gradient_length: int = 1, ): + """ + Initialize the MatrixEffect class. + + Args: + buffer (Buffer): Effect buffer to push changes to. + background (str): Character or string used for the background. + chracter_halt_range (tuple[int], optional): Halt range. Defaults to (1, 2). + color_halt_range (tuple[int], optional): Halt range. Defaults to (1, 2). + character_randomness_one (float, optional): Frequency to update a character. Defaults to 0.70. + character_randomness_two (float, optional): Frequency to update a character. Defaults to 0.60. + color_randomness (float, optional): Frequency to move the color gradient. Defaults to 0.50. + gradient_length (int, optional): Length of the color gradient. Defaults to 1. + """ super(MatrixEffect, self).__init__(buffer, background) self.__character_choices = ( string.ascii_letters + "1234567890!@#$%^&*()_+-=<>,.:\";'{}[]?/" @@ -97,13 +111,24 @@ def __init__( def set_matrix_properties( self, - chacter_halt_range=(1, 2), - color_halt_range=(1, 2), - character_randomness_one=0.70, - character_randomness_two=0.60, - color_randomness=0.50, - gradient_length=1, + chacter_halt_range: tuple[int] = (1, 2), + color_halt_range: tuple[int] = (1, 2), + character_randomness_one: float = 0.70, + character_randomness_two: float = 0.60, + color_randomness: float = 0.50, + gradient_length: int = 1, ): + """ + Set the matrix properties for the MatrixEffect. + + Args: + chracter_halt_range (tuple[int], optional): Halt range. Defaults to (1, 2). + color_halt_range (tuple[int], optional): Halt range. Defaults to (1, 2). + character_randomness_one (float, optional): Frequency to update a character. Defaults to 0.70. + character_randomness_two (float, optional): Frequency to update a character. Defaults to 0.60. + color_randomness (float, optional): Frequency to move the color gradient. Defaults to 0.50. + gradient_length (int, optional): Length of the color gradient. Defaults to 1. + """ self.__character_randomness_one = character_randomness_one self.__character_randomness_two = character_randomness_two self.__color_randomness = color_randomness @@ -126,7 +151,13 @@ def set_matrix_properties( for _ in range(self.buffer.height()) ] - def set_matrix_gradient(self, gradient): + def set_matrix_gradient(self, gradient: list[int]): + """ + Set the base gradient of the matrix. This will reset the current gradient and recreate it based on the new base gradient. + + Args: + gradient (list[int]): List of colors. + """ self.__base_gradient = gradient self.__gradient = [ color @@ -135,9 +166,18 @@ def set_matrix_gradient(self, gradient): ] def get_gradient(self): + """ + Get the current gradient. + + Returns: + list[int]: The current gradient. + """ return self.__base_gradient def __initialize_buffer(self): + """ + Initialize the buffer with characters and colors based on the current settings. + """ for y in range(self.buffer.height()): for x in range(self.buffer.width()): self.__buffer_characters[y][x] = random.choice(self.__character_choices) @@ -151,9 +191,12 @@ def __initialize_buffer(self): ).colored, ) - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Renders the next frame for the Matrix effect into the effect buffer + Render a single frame of the matrix effect. + + Args: + frame_number (int): The current frame number. """ if frame_number == 0: self.__initialize_buffer() diff --git a/bruhanimate/bruheffect/noise_effect.py b/bruhanimate/bruheffect/noise_effect.py index b9e0a96..421e6c4 100644 --- a/bruhanimate/bruheffect/noise_effect.py +++ b/bruhanimate/bruheffect/noise_effect.py @@ -18,17 +18,24 @@ from bruhcolor import bruhcolored from .base_effect import BaseEffect +from ..bruhutil import Buffer class NoiseEffect(BaseEffect): """ - Class for generating noise. - :param intensity: randomness for the noise, higher the value the slower the effect (due to computation). - Will be a value 1 - 999 - :param color: whether or not ro color the noise + A noise effect that adds random pixels to the screen with a specified intensity. """ - def __init__(self, buffer, background, intensity=200, color=False): + def __init__(self, buffer: Buffer, background: str, intensity: int = 200, color: bool = False): + """ + Initializes the NoiseEffect class with a specified buffer, background color, noise intensity, and whether to use colors. + + Args: + buffer (Buffer): Effect buffer to push updates tol. + background (str): Character or string for background. + intensity (int, optional): How offten the nosie should update. Defaults to 200. + color (bool, optional): Whether or not the effect should use color. Defaults to False. + """ super(NoiseEffect, self).__init__(buffer, background) self.intensity = ( @@ -39,7 +46,7 @@ def __init__(self, buffer, background, intensity=200, color=False): self.noise_length = len(self.noise) self.color = color - def update_intensity(self, intensity): + def update_intensity(self, intensity: int): """ Function to update the intensity of the effect :param intensity: new intensity @@ -48,18 +55,23 @@ def update_intensity(self, intensity): intensity / 1000 if intensity and 1 <= intensity <= 999 else 200 / 1000 ) - def update_color(self, color, characters): + def update_color(self, color: bool, characters: str): """ - Function to enable / disable color for the effect - :param color: True / False - :param character: True / False to make characters visable + Function to update the color and character set for the noise. + + Args: + color (bool): Whether or not the noise should use color. + characters (str): The set of characters that can be used for noise generation. """ self.color = color self.characters = characters - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Function to render the next frame of the Noise effect + Function to render the frame with noise effect applied. + + Args: + frame_number (int): The current frame number being rendered. """ if self.color: for y in range(self.buffer.height()): diff --git a/bruhanimate/bruheffect/offset_effect.py b/bruhanimate/bruheffect/offset_effect.py index 567740b..9e814b2 100644 --- a/bruhanimate/bruheffect/offset_effect.py +++ b/bruhanimate/bruheffect/offset_effect.py @@ -16,6 +16,8 @@ from ..bruhutil import VALID_DIRECTIONS from .base_effect import BaseEffect +from ..bruhutil.bruhffer import Buffer + class OffsetEffect(BaseEffect): @@ -24,21 +26,34 @@ class OffsetEffect(BaseEffect): :new-param direction: which way the offset should go. """ - def __init__(self, buffer, background, direction="right"): + def __init__(self, buffer: Buffer, background: str, direction: str = "right"): + """ + Initializes the offset effect. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): Character or string to use as the background. + direction (str): Direction for the effect, defaults to right. + """ super(OffsetEffect, self).__init__(buffer, background) self.direction = direction if direction in VALID_DIRECTIONS else "right" - def update_direction(self, direction): + def update_direction(self, direction: str): """ - Function to update the direction of the offset - :param direction: East / West + Function to update the direction of the offset. + + Args: + direction (str): Direction the background text should go. """ self.direction = direction if direction in VALID_DIRECTIONS else "right" self.buffer.clear_buffer() - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Function to render the next frame of the Offset effect + Function to render the frame based on the current direction and frame number. + + Args: + frame_number (int): Current frame number of the animation. Not used by the effect, but required for backwards compatibility. """ for y in range(self.buffer.height()): row = ( diff --git a/bruhanimate/bruheffect/plasma_effect.py b/bruhanimate/bruheffect/plasma_effect.py index 55f81e1..683c4e8 100644 --- a/bruhanimate/bruheffect/plasma_effect.py +++ b/bruhanimate/bruheffect/plasma_effect.py @@ -18,7 +18,7 @@ import random from bruhcolor import bruhcolored -from ..bruhutil import PLASMA_COLORS, GREY_SCALES +from ..bruhutil import PLASMA_COLORS, GREY_SCALES, Buffer from .base_effect import BaseEffect @@ -27,7 +27,14 @@ class PlasmaEffect(BaseEffect): Function to generate a plasma like effect """ - def __init__(self, buffer, background): + def __init__(self, buffer: Buffer, background: str): + """ + Initializes the plasma effect class. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): Character or string to use as the background. + """ super(PlasmaEffect, self).__init__(buffer, background) self.info = False self.random = False @@ -41,15 +48,24 @@ def __init__(self, buffer, background): random.randint(1, 100), ] - def update_info_visibility(self, visible): + def update_info_visibility(self, visible: bool): """ - Function to enable or disable info about the effect + Function to toggle visibility of info text. + + Args: + visible (bool): Toggle visibility. """ self.info = visible - def update_grey_scale_size(self, size): + def update_grey_scale_size(self, size: int): """ - Function to change the size of the grey scale + Function to change the size of the grey scale. + + Args: + size (int): Size of the grey scale to use. + + Raises: + Exception: If the requested grey scale size is not available. """ if size in [8, 10, 16]: self.scale = random.choice( @@ -64,13 +80,14 @@ def update_grey_scale_size(self, size): f"only 8, 10, and 16 are supported grey scale sizes, you provided {size}" ) - def update_color_properties(self, color, characters=True, random_color=False): + def update_color_properties(self, color: bool, characters: bool = True, random_color: bool = False): """ - Function to update the color properties. random_color overrules other functions - like update greyscale size and update color - :param color: True / False to enable color - :param characters: True / False to show the characters - :param random_color: True / False to generate random colors + Function to update the color properties. random_color overrules other functions. + + Args: + color (bool): Toggle the use of colors. + characters (bool, optional): Toggle the use of characters. Defaults to True. + random_color (bool, optional): Toggle the use of random colors. Defaults to False. """ self.color = color self.random = random_color @@ -80,9 +97,15 @@ def update_color_properties(self, color, characters=True, random_color=False): self.colors = [random.randint(0, 255) for _ in range(len(self.scale))] self.characters = characters - def update_color(self, colors): + def update_color(self, colors: list[int]): """ - Function to update the colors used + Function to update the color palette. + + Args: + colors (list[int]): List of RGB values for the color palette. + + Raises: + Exception: If the number of colors does not match the length of the scale. """ if not self.random: if len(colors) == len(self.scale): @@ -92,29 +115,37 @@ def update_color(self, colors): f"update_color(..) must be provided a list of {len(self.scale)} colors, you provided {len(colors)}" ) - def update_background(self, background): + def update_background(self, background: str): """ - Update the background character(s) - :param background: the new background + Function to update the background color or character. + + Args: + background (str): Character or string to set the background to. """ self.background = background self.background_length = len(self.background) def update_plasma_values( self, - a=random.randint(1, 50), - b=random.randint(1, 50), - c=random.randint(1, 50), - d=random.randint(1, 50), + a: int = random.randint(1, 50), + b: int = random.randint(1, 50), + c: int = random.randint(1, 50), + d: int = random.randint(1, 50), ): """ - Function to set the plasma values + Function to set the plasma values. + + Args: + a (int, optional): Value a. Defaults to random.randint(1, 50). + b (int, optional): Value b. Defaults to random.randint(1, 50). + c (int, optional): Value c. Defaults to random.randint(1, 50). + d (int, optional): Value d. Defaults to random.randint(1, 50). """ self.vals = [a, b, c, d] def shuffle_plasma_values(self): """ - Function to generate a new-random set of plasma values + Function to shuffle the plasma values randomly. """ self.vals = [ random.randint(1, 50), @@ -123,9 +154,12 @@ def shuffle_plasma_values(self): random.randint(1, 50), ] - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Function to render the next frame of the Plasma Effect + Function to render the plasma effect frame by frame. + + Args: + frame_number (int): The current frame number. """ self.ayo += 1 for y in range(self.buffer.height()): @@ -171,9 +205,19 @@ def render_frame(self, frame_number): for i in range(1, 5): self.buffer.put_at(0, i, f"VAL {i}: {str(self.vals[i-1]):>3s} ") - def func(self, x, y, a, b, n): + def func(self, x: int, y: int, a: int, b: int, n: int): """ - Helper function to calculate the plasma value given the four plasma values + Generates a plasma effect using the Perlin noise algorithm. + + Args: + x (int): x + y (int): y + a (int): a + b (int): b + n (int): n + + Returns: + float: value of the Perlin noise at (x, y) """ return math.sin( math.sqrt( diff --git a/bruhanimate/bruheffect/rain_effect.py b/bruhanimate/bruheffect/rain_effect.py index 3f3d82c..4378fb7 100644 --- a/bruhanimate/bruheffect/rain_effect.py +++ b/bruhanimate/bruheffect/rain_effect.py @@ -16,7 +16,7 @@ import random -from ..bruhutil import WIND_DIRECTIONS +from ..bruhutil import WIND_DIRECTIONS, Buffer from .base_effect import BaseEffect @@ -27,17 +27,32 @@ class RainEffect(BaseEffect): def __init__( self, - buffer, - background, - img_start_x=None, - img_start_y=None, - img_width=None, - img_height=None, - collision=False, - intensity=1, - swells=False, - wind_direction="none", + buffer: Buffer, + background: str, + img_start_x: int = None, + img_start_y: int = None, + img_width: int = None, + img_height: int = None, + collision: bool = False, + intensity: int = 1, + swells: bool = False, + wind_direction: str = "none", ): + """ + Initialize the RainEffect class with given parameters. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): Character or string to use for the background. + img_start_x (int, optional): Where the image starts on the x axis. Defaults to None. + img_start_y (int, optional): Where the image starts on the y axis. Defaults to None. + img_width (int, optional): The width of the image. Defaults to None. + img_height (int, optional): The height of the image. Defaults to None. + collision (bool, optional): Whether or not the effect should hit the image. Defaults to False. + intensity (int, optional): How intense should the rain be. Defaults to 1. + swells (bool, optional): Where or not to increase and deacrease the intensity automatically. Defaults to False. + wind_direction (str, optional): Which direction the rain should fall. Defaults to "none". + """ super(RainEffect, self).__init__(buffer, background) self.image_present = ( @@ -58,17 +73,21 @@ def __init__( } self._set_rain() - def update_multiplier(self, val): + def update_multiplier(self, val: int): """ - Update the multiplier value that relates to shift amount - :param val: value to set the multiplier to + Update the multiplier value that relates to shift amount. + + Args: + val (int): multiplier value """ self.multiplier = val - def update_wind_direction(self, direction): + def update_wind_direction(self, direction: str): """ - Update the direction of the rain - :param dircetion: direction for the rain to fall (east, west, none) + Update the direction of the rain. + + Args: + direction (str): Direction the rain should fall. """ if direction in WIND_DIRECTIONS: self.wind_direction = direction @@ -76,7 +95,7 @@ def update_wind_direction(self, direction): def _set_rain(self): """ - Function set the rain based on the intensity and wind direction + Set the rain based on intensity and wind direction. """ self.rain = f"{' ' * (1000 - self.intensity)}" if self.intensity > 50: @@ -87,10 +106,12 @@ def _set_rain(self): self.rain += self.wind_mappings[self.wind_direction][0] self.rain_length = len(self.rain) - def update_intensity(self, intensity): + def update_intensity(self, intensity: int): """ - Function to update the intensity of the rain - :param intensity: intentisy value + Function to update the intensity of the rain. + + Args: + intensity (int): The intensity of the rain. """ if self.swells: if self.intensity == 900: @@ -104,24 +125,25 @@ def update_intensity(self, intensity): def update_collision( self, - img_start_x, - img_start_y, - img_width, - img_height, - collision, - smart_transparent=False, - image_buffer=None, + img_start_x: int, + img_start_y: int, + img_width: int, + img_height: int, + collision: bool, + smart_transparent: bool = False, + image_buffer: Buffer = None, ): """ Function to set whether or not to visually see the rain collide with the ground - or images if they are present - :param img_start_x: where the image starts on the screen - :param img_start_y: where the image starts on the screen - :param img_width: the width of the image - :param img_height: the height of the image - :param collision: update collision variable - :param smart_transparent: update smart_transparent - :param image_buffer: the buffer that contains the image + or images if they are present. + Args: + img_start_x (int): Where the image starts on the screen. + img_start_y (int): Where the image starts on the screen. + img_width (int): The width of the image. + img_height (int): The height of the image. + collision (bool): Update collision variable. + smart_transparent (bool): Update smart_transparent. Defaults to False. + image_buffer (Buffer): The buffer that contains the image. Defaults to None. """ self.image_present = ( True if img_start_x and img_start_y and img_width and img_height else False @@ -137,16 +159,21 @@ def update_collision( else: self.image_buffer = None - def update_swells(self, swells): + def update_swells(self, swells: bool): """ - Function to set whether the intensity should evolve on it's own - :param swells: True / False + Function to update whether or not there are swells in the rain effect. + + Args: + swells (bool): Whether or not there are swells in the rain effect. """ self.swells = swells - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Function to render the next frame of the Rain Effect + Function to render the next frame of the Rain Effect. + + Args: + frame_number (int): The current frame number to render. """ if self.swells: self.update_intensity(None) diff --git a/bruhanimate/bruheffect/snow_effect.py b/bruhanimate/bruheffect/snow_effect.py index 259ba1a..3e7f441 100644 --- a/bruhanimate/bruheffect/snow_effect.py +++ b/bruhanimate/bruheffect/snow_effect.py @@ -22,17 +22,33 @@ class SnowEffect(BaseEffect): + """ + A class to represent a snow effect. + """ def __init__( self, buffer: Buffer, - background, - img_start_x=None, - img_start_y=None, - img_width=None, - img_height=None, - collision=False, - show_info=False, + background: str, + img_start_x: int = None, + img_start_y: int = None, + img_width: int = None, + img_height: int = None, + collision: bool = False, + show_info: bool = False, ): + """ + Initializes the SnowEffect class. + + Args: + buffer (Buffer): Effect buffer to render changes to. + background (str): Character or string used for the background of the effect. + img_start_x (int, optional): Where the image starts on the x axis. Defaults to None. + img_start_y (int, optional): Where the image starts on the y axis. Defaults to None. + img_width (int, optional): The width of the image. Defaults to None. + img_height (int, optional): The height of the image. Defaults to None. + collision (bool, optional): Whether or not the effect should collide with the image. Defaults to False. + show_info (bool, optional): Whether or not to show snowflake information. Defaults to False. + """ super(SnowEffect, self).__init__(buffer, background) self.image_present = (True if img_start_x and img_start_y and img_width and img_height else False) self.collision = collision @@ -45,22 +61,26 @@ def __init__( def update_collision( self, - img_start_x, - img_start_y, - img_width, - img_height, - collision, - smart_transparent, - image_buffer=None, + img_start_x: int, + img_start_y: int, + img_width: int, + img_height: int, + collision: bool, + smart_transparent: bool, + image_buffer: Buffer = None, ): """ Function to set whether or not to visually see the snow collide with the ground or images if they are present - :param img_start_x: where the image starts on the screen - :param img_start_y: where the image starts on the screen - :param img_width: the width of the image - :param img_height: the height of the image - :param collision: update collision variable + + Args: + img_start_x (int): Start of the image on the x axis. + img_start_y (int): Start of the image on the y axis. + img_width (int): Width of the image. + img_height (int): Height of the image. + collision (bool): Whether or not the effect should collide with the image. + smart_transparent (bool): not used . . . + image_buffer (Buffer, optional): The image buffer in order to find collisions. Defaults to None. """ self.image_present = ( True if img_start_x and img_start_y and img_width and img_height else False @@ -79,25 +99,64 @@ def update_collision( self.image_buffer = None def set_show_info(self, show_info: bool): + """ + Function to set whether or not to display information about the snow effect + + Args: + show_info (bool): Whether or not to display information about the snow effect + """ self.show_info = show_info def generate_snowflake(self, x: int): + """ + Generates a new snowflake at the given x position. + + Args: + x (int): The x position to generate the snowflake at. + """ snowflake_type = random.choice(list(SNOWFLAKE_TYPES.keys())) snowflake_char = bruhcolored(snowflake_type, SNOWFLAKE_COLORS[snowflake_type]) self.flakes.append({"x": x, "y": 0, "type": snowflake_type, "colored": snowflake_char, "fall_delay": 0}) - def can_stack(self, x, y): + def can_stack(self, x: int, y: int): + """ + Checks if a snowflake can be stacked at the given position. + + Args: + x (int): The x position to check. + y (int): The y position to check. + + Returns: + bool: True if the snowflake can be stacked, False otherwise. + """ if y >= self.buffer.height(): return False return self.ground_flakes[y][x] >= 18 def is_colliding(self, x: int, y: int): + """ + Checks if a snowflake is colliding with the ground / snowflake at the given position. + + Args: + x (int): The x position to check. + y (int): The y position to check. + + Returns: + bool: True if the snowflake is colliding with the ground / snowflake, False otherwise. + """ if self.image_present: if self.image_x_boundaries[0] < x < self.image_x_boundaries[1] and self.image_y_boundaries[0] < y < self.image_y_boundaries[1]: return True return False def handle_snowflake_landing(self, x: int, y: int): + """ + Handles the snowflake landing at the given position. + + Args: + x (int): The x position where the snowflake is landing. + y (int): The y position where the snowflake is landing. + """ self.ground_flakes[y][x] += 1 weight = self.ground_flakes[y][x] for w, char in sorted(FLAKE_WEIGHT_CHARS.items(), reverse=True): @@ -108,13 +167,18 @@ def handle_snowflake_landing(self, x: int, y: int): self.ground_flakes[y - 1][x] += 1 def add_info(self): + """ + Adds information about the falling and grounded snowflakes. + """ self.buffer.put_at(0, 0, f"Total Snow Flakes: {len(self.flakes)}") self.buffer.put_at(0, 1, f"Total Flakes on Ground: {sum([sum([1 for v in row if v > 0]) for row in self.ground_flakes])}") - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Function to render the next frame of the snow effect into it's buffer. - :param frame_number: The current frame number (used to determine the animation state). + Renders a single frame of the snow effect. + + Args: + frame_number (int): The current frame number to render. """ # generate a new row of snowflakes diff --git a/bruhanimate/bruheffect/star_effect.py b/bruhanimate/bruheffect/star_effect.py index 2f7fc80..e43d257 100644 --- a/bruhanimate/bruheffect/star_effect.py +++ b/bruhanimate/bruheffect/star_effect.py @@ -17,7 +17,7 @@ import random from bruhcolor import bruhcolored -from ..bruhutil import LIFE_COLORS +from ..bruhutil import LIFE_COLORS, Buffer from .noise_effect import NoiseEffect class StarEffect(NoiseEffect): @@ -26,33 +26,48 @@ class StarEffect(NoiseEffect): Ideally the background would be ' ' for the best effect, but the choice is yours. """ - def __init__(self, buffer, background, color_type="GREYSCALE"): + def __init__(self, buffer: Buffer, background: str, color_type: str = "GREYSCALE"): + """ + Initializes the Star Effect. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): Character or string for the background. + color_type (str, optional): What color type should be used. Defaults to "GREYSCALE". + """ super(StarEffect, self).__init__(buffer, background) self.stars = f"{background*(100 // self.background_length)}.*+" self.stars_length = len(self.stars) self.color_type = color_type - def update_color_type(self, color_type): + def update_color_type(self, color_type: str): """ - Function to update the color of the stars - :param color_type: color map + Function to update the color type. + + Args: + color_type (str): The color type to use for this effect. """ self.color_type = color_type - def update_background(self, background): + def update_background(self, background: str): """ - Function to update the background of the efffect - :param background: the new background + Function to update the background character or string. + + Args: + background (str): The new background character or string to use. """ self.background = background self.background_length = len(background) self.stars = f"{background*(100 // self.background_length)}.*+" self.stars_length = len(self.stars) - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Function to update the next frame of the Stars effect + Function to render the next frame of the Stars effect. + + Args: + frame_number (int): The current frame number to render. """ for y in range(self.buffer.height()): for x in range(self.buffer.width()): diff --git a/bruhanimate/bruheffect/static_effect.py b/bruhanimate/bruheffect/static_effect.py index e6bad7a..1d58806 100644 --- a/bruhanimate/bruheffect/static_effect.py +++ b/bruhanimate/bruheffect/static_effect.py @@ -15,6 +15,8 @@ """ from .base_effect import BaseEffect +from ..bruhutil.bruhffer import Buffer + class StaticEffect(BaseEffect): @@ -22,12 +24,22 @@ class StaticEffect(BaseEffect): Class for generating a static background. """ - def __init__(self, buffer, background): + def __init__(self, buffer: Buffer, background: str): + """ + Initializes the static effect with a buffer and a background string. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): character or string to use as the background. + """ super(StaticEffect, self).__init__(buffer, background) - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): """ - Renders the background to the screen + Renders the background to the screen. + + Args: + frame_number (int): The current frame number. Not used in this effect, but required by the base class. """ for y in range(self.buffer.height()): self.buffer.put_at( diff --git a/bruhanimate/bruheffect/twinkle_effect.py b/bruhanimate/bruheffect/twinkle_effect.py index 46530ed..40e4f20 100644 --- a/bruhanimate/bruheffect/twinkle_effect.py +++ b/bruhanimate/bruheffect/twinkle_effect.py @@ -17,12 +17,23 @@ import random from bruhcolor import bruhcolored -from ..bruhutil import TWINKLE_COLORS +from ..bruhutil import TWINKLE_COLORS, Buffer from .base_effect import BaseEffect class TWINKLE_SPEC: - def __init__(self, char, value): + """ + A class to represent a single twinkle character with its color value. + """ + + def __init__(self, char: chr, value: int): + """ + Initializes the twinkle character with its color value. + + Args: + char (chr): Character to display. + value (int): Value assoicated with the color. + """ self.char = char self.value = value self.fade = bruhcolored(self.char, TWINKLE_COLORS[self.value]) @@ -38,6 +49,9 @@ def __len__(self): return 1 def next(self): + """ + Updates the twinkle character's color value and returns it as a string. + """ if self.value >= 23: self.mode = -1 elif self.value <= 0: @@ -49,17 +63,39 @@ def next(self): return self def copy(self): + """ + Returns a copy of the current twinkle character with its color value. + + Returns: + TWINKLE_SPEC: Copy of the current twinkle spec. + """ new_TWINKLE_SPEC = TWINKLE_SPEC(self.char, self.value) new_TWINKLE_SPEC.mode = self.mode return new_TWINKLE_SPEC class TwinkleEffect(BaseEffect): - def __init__(self, buffer, background): + """ + Class for the twinkle effect. + """ + def __init__(self, buffer: Buffer, background: str): + """ + Initializes the twinkle effect class. + + Args: + buffer (Buffer): Effect buffer to push updates to. + background (str): Character or string to use as the background. + """ super(TwinkleEffect, self).__init__(buffer, background) self.specs = [] - def render_frame(self, frame_number): + def render_frame(self, frame_number: int): + """ + Renders the next frame of the twinkle effect to the buffer. + + Args: + frame_number (int): The current frame of the animation. + """ if frame_number == 0: for y in range(self.buffer.height()): for x in range(self.buffer.width()):