Solver playback¶
Playback consumes a lazy iterator of snapshots only during update. Drawing reads the current snapshot. Pass its handle_event explicitly to enable pause/step/speed controls. The source should yield immutable snapshots or use copy_state=deepcopy.
from bruhanimate import Playback, draw_grid, play
board = ("S...", ".##.", "...E")
def states():
yield from ((0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (3, 2))
def draw(position, buffer):
grid = draw_grid(buffer, board, spacing=(2, 1))
grid.put(*position, "@")
animation = Playback(states(), draw, steps_per_second=5)
play(animation, event_handler=animation.handle_event, on_complete="exit")
Space/p pauses, n/right-arrow steps, +/- changes speed, and q/Escape quits. Pausing the solver does not pause other scene children. Playback stores one snapshot and closes its owned iterator on exhaustion or early cleanup. It is single-use. Solver work runs synchronously; each next() must perform bounded work to keep input responsive. Sample or aggregate large searches within the producer.
on_complete=”hold” keeps polling input over the final state; “exit” stops after presenting completed content. Exhaustion is detected on the next scheduled source advance, without lookahead. Scene completes when all finite children complete, ignoring endless backgrounds. Empty sources present one background frame.
Run python -m bruhanimate.demos.grid_demo for a pure BFS solver, clipped maze
drawing, background animation, controls, and final path. See the full guide
for snapshots, ownership, timing, sampling, and completion details.
Lazy, bounded playback of solver snapshots through the Animation interface.
- class bruhanimate.playback.Playback(states: Iterable[State], draw: Callable[[State, Buffer], None], *, steps_per_second: float = 10, paused: bool = False, copy_state: Callable[[State], State] | None = None, max_catch_up: int = 5)[source]¶
Keep one current snapshot from a lazy state source.
draw(state, buffer) reads the current snapshot without consuming input. Sources should yield independent/immutable states; pass copy_state=deepcopy when the producer reuses mutable data. No history or lookahead is retained.
This is a single-use animation and owns the iterator acquired at start(). Its optional close() runs on exhaustion or playback cleanup. next() executes synchronously: expensive solvers should yield bounded units of work.
- __init__(states: Iterable[State], draw: Callable[[State, Buffer], None], *, steps_per_second: float = 10, paused: bool = False, copy_state: Callable[[State], State] | None = None, max_catch_up: int = 5)[source]¶
- property steps_per_second: float¶
- property state: State | None¶
Current snapshot; use has_state to distinguish a yielded None.
- property has_state: bool¶
- property finished: bool¶
True only after source exhaustion, not after early close/quit.
- property paused: bool¶