Paths, pulses, and ripples

PathEffect, PulseEffect, and RippleEffect are transparent animations for highlighting a board. They use the same board coordinates and spacing as GridViewport. Supply the board size=(columns, rows); x=None, y=None centers the board in the current buffer on every draw, including after resize. Explicit coordinates anchor it instead. Layer regions and masks provide clipping.

from bruhanimate import PathEffect, PulseEffect, RippleEffect, Scene, capture

scene = Scene(
    PathEffect([(0, 1), (1, 1), (2, 1), (3, 1)], size=(5, 3),
               mode="trace", duration=2, hold_duration=1),
    PulseEffect((0, 1), size=(5, 3), colors=(238, 250, 255), duration=1),
    RippleEffect((3, 1), size=(5, 3), metric="manhattan", radius=2, duration=2),
)
capture(scene, on_frame=lambda frame: None, width=20, height=6, frames=4)

Options and timing

  • PathMode completes "pulse" (moving marker with trail previous cells) and "trace" (all visited cells). points is copied at construction; entries receive equal time intervals. Points are not interpolated and no path finding is performed. Include every desired cell; repeated points create pauses.

  • PulseEffect.colors is a nonempty sequence of ANSI palette indices 0..255. cycles controls triangular foreground brightness cycles during duration. This is a color pulse, not opacity blending. glyph defaults to "*".

  • RippleMetric completes "euclidean" (circle), "manhattan" (diamond), and "chebyshev" (square). radius and thickness are measured in board cells, independent of display spacing. Only visible cells are visited when drawing.

duration is positive seconds; hold_duration freezes the final state before it disappears. An empty path is finished immediately. Nonrepeating accents report finished=True and draw nothing after duration plus hold. With repeat=True, repeat_delay is the blank gap before the next cycle; finished stays false. Large updates skip whole cycles without replaying them. Drawing never advances time. These helpers acquire no resources; a Scene or Player owns their lifecycle. Use a fresh instance for each owner. For sequential highlights, use Animation sequences and factories rather than sharing animations.

Run the complete Routing demo to see a source pulse, traced route, moving marker, and arrival ripple over an independently animated background.

API

Transparent, simulation-timed accents in board coordinates.

class bruhanimate.motion.PathEffect(points: Sequence[tuple[int, int]], *, size: tuple[int, int], duration: float = 2, mode: Literal['pulse', 'trace'] = 'pulse', trail: int = 3, glyph: str = '*', hold_duration: float = 0, repeat: bool = False, repeat_delay: float = 1, x: int | None = None, y: int | None = None, spacing: tuple[int, int] = (1, 1))[source]

Move a pulse along supplied board cells or progressively trace the route.

points are copied, ordered (column, row) cells inside size=(columns, rows). Each interval between entries takes equal time; no interpolation, pathfinding or solver consumption is performed. Repeated points create timed pauses. mode=’pulse’ paints the current cell plus up to trail preceding cells; mode=’trace’ paints all visited cells. trail counts cells, not seconds.

duration is positive simulation seconds from first to last cell; a one-cell path stays there. hold_duration retains the final state before disappearing. repeat adds a blank repeat_delay and starts again. Empty paths finish at once. x/y=None center the board like GridViewport; spacing separates glyph anchors. Resize recenters without changing time. Unpainted cells are transparent. Layer supplies masks/style/background preservation. No resources or input are owned; start/close are inert, update advances time, and draw is pure.

__init__(points: Sequence[tuple[int, int]], *, size: tuple[int, int], duration: float = 2, mode: Literal['pulse', 'trace'] = 'pulse', trail: int = 3, glyph: str = '*', hold_duration: float = 0, repeat: bool = False, repeat_delay: float = 1, x: int | None = None, y: int | None = None, spacing: tuple[int, int] = (1, 1)) → None[source]
property finished: bool

A one-shot finishes after duration + hold_duration and paints nothing.

property position: tuple[int, int] | None

Current board cell, or None when empty/finished/in a repeat gap.

class bruhanimate.motion.PulseEffect(center: tuple[int, int], *, size: tuple[int, int], duration: float = 1, cycles: int = 2, colors: tuple[int, ...] = (238, 244, 250, 255), glyph: str = '*', hold_duration: float = 0, repeat: bool = False, repeat_delay: float = 1, x: int | None = None, y: int | None = None, spacing: tuple[int, int] = (1, 1))[source]

Pulse a board marker through palette colors, then reveal lower layers.

center is a board cell inside size. colors run dim-to-bright and back once per cycle; cycles is a positive integer within duration simulation seconds. This changes the marker’s foreground, not terminal opacity. hold_duration holds its final dim color. See PathEffect for placement, repeat, resize, transparency and inert lifecycle contracts. Layer styling may override color.

__init__(center: tuple[int, int], *, size: tuple[int, int], duration: float = 1, cycles: int = 2, colors: tuple[int, ...] = (238, 244, 250, 255), glyph: str = '*', hold_duration: float = 0, repeat: bool = False, repeat_delay: float = 1, x: int | None = None, y: int | None = None, spacing: tuple[int, int] = (1, 1)) → None[source]
class bruhanimate.motion.RippleEffect(center: tuple[int, int], *, size: tuple[int, int], radius: float = 5, duration: float = 1, thickness: float = 1, metric: Literal['euclidean', 'manhattan', 'chebyshev'] = 'euclidean', glyph: str = '*', hold_duration: float = 0, repeat: bool = False, repeat_delay: float = 1, x: int | None = None, y: int | None = None, spacing: tuple[int, int] = (1, 1))[source]

Expand a ring from a board cell, then reveal the underlying animation.

radius and thickness are positive finite board-cell distances. metric chooses a circular, diamond, or square ring. The ring includes distances within half thickness of progress*radius; its initial frame includes the center. spacing=(2,1) often makes board circles look rounder in a terminal. Drawing visits only visible cell anchors, even for very large boards/radii. See PathEffect for timing, placement, repeat and inert lifecycle contracts.

__init__(center: tuple[int, int], *, size: tuple[int, int], radius: float = 5, duration: float = 1, thickness: float = 1, metric: Literal['euclidean', 'manhattan', 'chebyshev'] = 'euclidean', glyph: str = '*', hold_duration: float = 0, repeat: bool = False, repeat_delay: float = 1, x: int | None = None, y: int | None = None, spacing: tuple[int, int] = (1, 1)) → None[source]