pub mod atom;
pub mod aurora;
pub mod automata;
pub mod blackhole;
pub mod boids;
pub mod campfire;
pub mod cells;
pub mod crystallize;
pub mod dna;
pub mod dragon;
pub mod eclipse;
pub mod fire;
pub mod fireflies;
pub mod flappy_bird;
pub mod flow_field;
pub mod fountain;
pub mod galton;
pub mod garden;
pub mod globe;
pub mod hackerman;
pub mod ink_in_water;
pub mod invaders;
pub mod langton;
pub mod lava;
pub mod life;
pub mod lightning;
pub mod mandelbrot;
pub mod matrix;
pub mod maze;
pub mod metaballs;
pub mod nbody;
pub mod newtons_cradle;
pub mod ocean;
pub mod particles;
pub mod pendulum;
pub mod petals;
pub mod physarum;
pub mod plasma;
pub mod pong;
pub mod pulse;
pub mod radar;
pub mod rain;
pub mod rainforest;
pub mod reaction_diffusion;
pub mod ripple;
pub mod sandstorm;
pub mod sierpinski;
pub mod smoke;
pub mod snake;
pub mod snow;
pub mod solar_system;
pub mod sort;
pub mod spiral;
pub mod starfield;
pub mod strange_attractor;
pub mod tetris;
pub mod visualizer;
pub mod voronoi;
pub mod waterfall;
pub mod wave;
use crate::render::{Canvas, RenderMode};
pub trait Animation {
fn name(&self) -> &str;
fn update(&mut self, canvas: &mut Canvas, dt: f64, time: f64);
fn preferred_render(&self) -> RenderMode {
RenderMode::HalfBlock
}
fn set_params(&mut self, _params: &crate::external::ExternalParams) {}
fn on_resize(&mut self, _width: usize, _height: usize) {}
#[allow(dead_code)]
fn supported_params(&self) -> &'static [(&'static str, f64, f64)] {
&[]
}
}
macro_rules! declare_animations {
($(( $name:literal, $path:path, $desc:literal )),* $(,)?) => {
pub const ANIMATIONS: &[(&str, &str)] = &[
$( ($name, $desc), )*
];
pub const ANIMATION_NAMES: &[&str] = &[
$( $name, )*
];
pub fn create(name: &str, width: usize, height: usize, scale: f64) -> Option<Box<dyn Animation>> {
Some(match name {
$( $name => Box::new(<$path>::new(width, height, scale)), )*
_ => return None,
})
}
}
}
declare_animations! {
("fire", fire::Fire, "Doom-style fire effect with heat propagation"),
("matrix", matrix::Matrix, "Matrix digital rain with trailing drops"),
("plasma", plasma::Plasma, "Classic plasma with overlapping sine waves"),
("starfield", starfield::Starfield, "3D starfield with depth parallax"),
("wave", wave::Wave, "Sine wave interference from moving sources"),
("life", life::GameOfLife, "Conway's Game of Life cellular automaton"),
("particles", particles::Particles, "Fireworks bursting with physics and fade"),
("pendulum", pendulum::Pendulum, "Pendulum wave with mesmerizing phase patterns"),
("rain", rain::Rain, "Raindrops with splash particles and wind"),
("fountain", fountain::Fountain, "Water fountain with jets, splashes, and mist"),
("flow", flow_field::FlowField, "Perlin noise flow field with particle trails"),
("spiral", spiral::Spiral, "Rotating multi-arm spiral pattern"),
("ocean", ocean::Ocean, "Ocean waves with foam and depth shading"),
("aurora", aurora::Aurora, "Aurora borealis with layered curtains"),
("lightning", lightning::Lightning, "Lightning bolts with recursive branching"),
("smoke", smoke::Smoke, "Smoke rising with Perlin turbulence"),
("ripple", ripple::Ripple, "Ripple interference from random drop points"),
("snow", snow::Snow, "Snowfall with accumulation on the ground"),
("garden", garden::Garden, "Growing garden with rain, clouds, and blooming plants"),
("fireflies", fireflies::Fireflies, "Fireflies blinking with warm glow"),
("dna", dna::Dna, "Rotating DNA double helix with base pairs"),
("pulse", pulse::Pulse, "Expanding pulse rings from center"),
("boids", boids::Boids, "Boids flocking simulation with trails"),
("lava", lava::Lava, "Lava lamp blobs rising, merging, and splitting"),
("sandstorm", sandstorm::Sandstorm, "Blowing sand with dune formation"),
("petals", petals::Petals, "Cherry blossom petals drifting in wind"),
("campfire", campfire::Campfire, "Campfire with rising ember sparks"),
("waterfall", waterfall::Waterfall, "Cascading water with mist spray"),
("eclipse", eclipse::Eclipse, "Moon crossing sun with corona rays"),
("blackhole", blackhole::Blackhole, "Black hole with accretion disk and lensing"),
("radar", radar::Radar, "Rotating radar sweep with fading blips"),
("rainforest", rainforest::Rainforest, "Layered rainforest with parallax scrolling, rain, birds, and falling leaves"),
("crystallize", crystallize::Crystallize, "DLA crystal growth from center seed"),
("hackerman", hackerman::Hackerman, "Scrolling hex/binary hacker terminal"),
("visualizer", visualizer::Visualizer, "Audio spectrum analyzer with bouncing bars"),
("cells", cells::Cells, "Cell division and mitosis animation"),
("atom", atom::Atom, "Electrons orbiting a nucleus in 3D"),
("automata", automata::Automata, "Cellular automata cycling through multiple rulesets"),
("globe", globe::Globe, "Rotating wireframe Earth with continents"),
("dragon", dragon::Dragon, "Dragon curve fractal with color cycling"),
("sierpinski", sierpinski::Sierpinski, "Animated Sierpinski triangle with zoom"),
("mandelbrot", mandelbrot::Mandelbrot, "Mandelbrot set with zoom and color cycling"),
("maze", maze::Maze, "Animated maze generation with recursive backtracking and BFS solving"),
("metaballs", metaballs::Metaballs, "Organic metaballs merging and splitting with smooth distance fields"),
("nbody", nbody::NBody, "N-body gravitational simulation with colorful orbiting masses and merging"),
("langton", langton::Langton, "Langton's Ant cellular automaton"),
("sort", sort::Sort, "Sorting algorithm visualizer"),
("tetris", tetris::Tetris, "Self-playing Tetris with AI piece placement"),
("snake", snake::Snake, "Self-playing Snake game AI"),
("invaders", invaders::Invaders, "Space Invaders attract mode demo"),
("pong", pong::Pong, "Self-playing Pong with AI paddles"),
("flappy_bird", flappy_bird::FlappyBird, "Self-playing Flappy Bird with AI"),
("reaction_diffusion", reaction_diffusion::ReactionDiffusion, "Gray-Scott reaction-diffusion coral/brain patterns"),
("voronoi", voronoi::Voronoi, "Animated Voronoi diagram with drifting colored cells and edge detection"),
("galton", galton::Galton, "Galton board with balls cascading into a bell-curve histogram"),
("solar_system", solar_system::SolarSystem, "Solar system with planets, moons, rings, and an asteroid belt"),
("physarum", physarum::Physarum, "Physarum slime mold agents self-organizing into networks"),
("newtons_cradle", newtons_cradle::NewtonsCradle, "Newton's cradle with energy-conserving swings"),
("strange_attractor", strange_attractor::StrangeAttractor, "Lorenz strange attractor with a rotating rainbow trail"),
("ink_in_water", ink_in_water::InkInWater, "Colored ink puffs dispersing through a turbulent flow field"),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_returns_some_for_all_known_names() {
for &name in ANIMATION_NAMES {
let result = create(name, 80, 24, 1.0);
assert!(result.is_some(), "create({name:?}) returned None");
}
}
#[test]
fn test_create_returns_none_for_unknown_name() {
let result = create("does_not_exist", 80, 24, 1.0);
assert!(result.is_none());
}
#[test]
fn test_animation_names_and_animations_have_same_length() {
assert_eq!(ANIMATION_NAMES.len(), ANIMATIONS.len());
}
#[test]
fn test_animation_names_match_animations_list() {
for (name, (anim_name, _desc)) in ANIMATION_NAMES.iter().zip(ANIMATIONS.iter()) {
assert_eq!(
name, anim_name,
"ANIMATION_NAMES and ANIMATIONS are out of sync at {name}"
);
}
}
#[test]
fn test_created_animation_name_matches_requested() {
let anim = create("fire", 80, 24, 1.0).unwrap();
assert_eq!(anim.name(), "fire");
}
#[test]
fn test_fire_supported_params_includes_intensity() {
let anim = create("fire", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty(), "fire should have supported params");
assert!(params.iter().any(|&(name, _, _)| name == "intensity"));
}
#[test]
fn test_plasma_supported_params_includes_color_shift() {
let anim = create("plasma", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty(), "plasma should have supported params");
assert!(params.iter().any(|&(name, _, _)| name == "color_shift"));
}
#[test]
fn test_unknown_animation_has_empty_params() {
let anim = create("matrix", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(params.is_empty(), "matrix should have no declared params");
}
#[test]
fn test_boids_supported_params() {
let anim = create("boids", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty());
assert!(params.iter().any(|&(name, _, _)| name == "intensity"));
assert!(params.iter().any(|&(name, _, _)| name == "color_shift"));
}
#[test]
fn test_particles_supported_params() {
let anim = create("particles", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty());
assert!(params.iter().any(|&(name, _, _)| name == "intensity"));
}
#[test]
fn test_wave_supported_params() {
let anim = create("wave", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty());
assert!(params.iter().any(|&(name, _, _)| name == "intensity"));
assert!(params.iter().any(|&(name, _, _)| name == "color_shift"));
}
#[test]
fn test_sort_supported_params() {
let anim = create("sort", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty());
assert!(params.iter().any(|&(name, _, _)| name == "speed"));
}
#[test]
fn test_snake_supported_params() {
let anim = create("snake", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty());
assert!(params.iter().any(|&(name, _, _)| name == "speed"));
}
#[test]
fn test_pong_supported_params() {
let anim = create("pong", 80, 24, 1.0).unwrap();
let params = anim.supported_params();
assert!(!params.is_empty());
assert!(params.iter().any(|&(name, _, _)| name == "speed"));
}
}