Source code for bruhanimate.bruhrenderer.focus_renderer

"""
Copyright 2023 Ethan Christensen

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import random
from typing import Any

from ..bruhutil.bruhtypes import EffectType
from .base_renderer import BaseRenderer


[docs] class FocusRenderer(BaseRenderer): """ A Renderer that takes an image and randomly spreads the characters around the screen. The characters are then pulled to the middle of the screen """
[docs] def __init__( self, screen, img: list[str], frames: int = 100, frame_time: float = 0.1, effect_type: EffectType = "static", background: str = " ", transparent: bool = False, collision: bool = False, start_frame: int = 0, reverse: bool = False, start_reverse: int = None, loop: bool = True, settings: Any = None, preset: str | None = None, ): super().__init__( screen, frames, frame_time, effect_type, background, transparent, collision, settings=settings, preset=preset, ) self.img = img self.start_frame = start_frame self.reverse = reverse self.start_reverse = start_reverse if self.start_reverse: self.frame_gap = start_reverse - start_frame self.loop = True if loop and reverse else False self.loops = 1 if self.reverse and self.start_reverse is None: raise ValueError( "if reverse is enabled, a start_reverse frame must be provided" ) if self.reverse and start_reverse < self.start_frame: raise ValueError( f"start_reverse ({self.start_reverse}) cannot be less than start_frame ({self.start_frame})" ) if self.img: self._set_img_attributes()
[docs] def _set_img_attributes(self): """ Sets attributes for the image, such as its height and width, and initializes boards to track character positions. """ self.img_height = len(self.img) self.img_width = len(self.img[0]) self.img_y_start = (self.height - len(self.img)) // 2 self.img_x_start = (self.width - len(self.img[0])) // 2 self.current_img_x = self.img_x_start self.current_img_y = self.img_y_start # Initialize start board with characters at random positions self.start_board = [ [ [ random.randint(0, self.width - 1), random.randint(0, self.height - 1), self.img[y][x], (x, y), ] for x in range(self.img_width) ] for y in range(self.img_height) ] # Initialize current board with characters at start positions self.current_board = [ [ [ self.start_board[y][x][0], self.start_board[y][x][1], self.img[y][x], (x, y), ] for x in range(self.img_width) ] for y in range(self.img_height) ] # Initialize end board with characters at target positions self.end_board = [ [ [self.img_x_start + x, self.img_y_start + y, self.img[y][x], (x, y)] for x in range(self.img_width) ] for y in range(self.img_height) ] # Initialize direction board to track character movement self.direction_board = [ [ [ ( -1 if (self.end_board[y][x][0] - self.current_board[y][x][0]) < 0 else 1 ), ( -1 if (self.end_board[y][x][1] - self.current_board[y][x][1]) < 0 else 1 ), ] for x in range(self.img_width) ] for y in range(self.img_height) ]
[docs] def update_reverse(self, reverse: bool, start_reverse: int) -> None: """ Updates the state of reverse and start_reverse attributes. Args: reverse (bool): Whether to enable or disable reverse. start_reverse (int): The frame number at which to start the reverse. Raises: Exception: If reverse is enabled but start_reverse is not provided, or if start_reverse is less than the current start_frame. """ self.reverse = reverse self.start_reverse = start_reverse if start_reverse < self.start_frame: raise ValueError( f"start_reverse ({self.start_reverse}) cannot be less than start_frame ({self.start_frame})" )
[docs] def update_start_frame(self, frame_number): """ Updates the state of the start frame attribute. Args: frame_number (int): The new start frame number. Returns: None """ self.start_frame = frame_number
[docs] def solved(self, end_state: str) -> bool: """ Returns True if the current board matches the target state ("end" or "start"). Raises: ValueError: If end_state is not "end" or "start". """ targets = {"end": self.end_board, "start": self.start_board} if end_state not in targets: raise ValueError( f"unknown solved board state for FocusRenderer: {end_state}" ) target = targets[end_state] return all( self.current_board[y][x][0] == target[y][x][0] and self.current_board[y][x][1] == target[y][x][1] for y in range(len(self.current_board)) for x in range(len(self.current_board[y])) )
[docs] def render_img_frame(self, frame_number): """ Renders the image on the screen based on the current frame number. Args: frame_number (int): The current frame number. Returns: None """ if self.reverse and frame_number >= self.start_reverse: if not self.solved(end_state="start"): for y in range(len(self.current_board)): for x in range(len(self.current_board[y])): x_check, y_check = False, False if self.current_board[y][x][0] != self.start_board[y][x][0]: self.current_board[y][x][0] -= self.direction_board[y][x][0] else: x_check = True if self.current_board[y][x][1] != self.start_board[y][x][1]: self.current_board[y][x][1] -= self.direction_board[y][x][1] else: y_check = True if x_check and y_check: self.current_board[y][x][2] = None self.image_buffer.clear_buffer(val=None) for row in self.current_board: for value in row: self.image_buffer.put_char( value[0], value[1], value[2], transparent=self.transparent ) else: if self.loop: self.loops += 1 self.start_frame = self.start_reverse + self.frame_gap self.start_reverse = self.start_frame + self.frame_gap self._set_img_attributes() self.image_buffer.clear_buffer(val=None) elif frame_number >= self.start_frame: if not self.solved(end_state="end"): for y in range(len(self.current_board)): for x in range(len(self.current_board[y])): if self.current_board[y][x][0] != self.end_board[y][x][0]: self.current_board[y][x][0] += self.direction_board[y][x][0] if self.current_board[y][x][1] != self.end_board[y][x][1]: self.current_board[y][x][1] += self.direction_board[y][x][1] self.image_buffer.clear_buffer(val=None) for row in self.current_board: for value in row: self.image_buffer.put_char( value[0], value[1], value[2], transparent=self.transparent )