use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
fn is_prime(n: u64) -> bool {
if n < 2 {
return false;
}
if n == 2 {
return true;
}
if n % 2 == 0 {
return false;
}
let mut i = 3u64;
while i * i <= n {
if n % i == 0 {
return false;
}
i += 2;
}
true
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(Sieve),
Box::new(UlamSpiral),
Box::new(PrimeCounting),
Box::new(Collatz),
Box::new(FibonacciSpiral),
Box::new(PascalMod),
Box::new(TotientHistogram),
Box::new(SternBrocot),
Box::new(ContinuedFraction),
Box::new(ModularCircle),
Box::new(Recaman),
Box::new(DigitalRoot),
]
}
struct Sieve;
impl ProgressStyle for Sieve {
fn name(&self) -> &str {
"sieve-eratosthenes"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Sieve of Eratosthenes: integers cross composites, primes survive as lit dots"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n = w.max(2).min(2000);
let revealed = ((ctx.eased * n as f32).round() as usize).min(n);
let mut composite = vec![false; n + 1];
composite[0] = true;
if n >= 1 {
composite[1] = true;
}
for p in 2..=n {
if !composite[p] {
let mut m = p * 2;
while m <= n {
composite[m] = true;
m += p;
}
}
}
let highlight_p = {
let p_frac = (ctx.time * 0.4).fract();
let prime_count = (2..=n).filter(|&k| !composite[k]).count().max(1);
let pi = ((p_frac * prime_count as f32) as usize).min(prime_count.saturating_sub(1));
(2..=n).filter(|&k| !composite[k]).nth(pi).unwrap_or(2)
};
for k in 1..=revealed {
let x = ((k - 1) * w / n).min(w.saturating_sub(1));
let is_p = !composite[k];
if is_p {
draw::vline(grid, x, 0, h.saturating_sub(1));
if k > 1 && k % highlight_p == 0 {
draw::dot(grid, x, 0);
}
} else {
let tick = (h / 4).max(1);
draw::vline(grid, x, h.saturating_sub(tick), h.saturating_sub(1));
}
}
Ok(())
}
}
struct UlamSpiral;
impl ProgressStyle for UlamSpiral {
fn name(&self) -> &str {
"ulam-spiral"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Ulam spiral: integers coil outward from centre, primes cluster on diagonals"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let cx = (w / 2) as i32;
let cy = (h / 2) as i32;
let n = (w * h).min(4000).max(1);
let revealed = ((ctx.eased * n as f32).round() as usize).min(n);
let dx = [1i32, 0, -1, 0];
let dy = [0i32, -1, 0, 1];
let mut x = 0i32;
let mut y = 0i32;
let mut dir = 0usize;
let mut steps_in_leg = 1usize;
let mut steps_taken = 0usize;
let mut leg_count = 0usize;
let pulse = (ctx.time * 2.0 * PI * 0.5).sin() * 0.5 + 0.5;
let _ = pulse;
for n_i in 1..=revealed {
let px = cx + x;
let py = cy + y;
if is_prime(n_i as u64) {
draw::dot_i(grid, px, py);
}
x += dx[dir];
y += dy[dir];
steps_taken += 1;
if steps_taken == steps_in_leg {
steps_taken = 0;
dir = (dir + 1) % 4;
leg_count += 1;
if leg_count % 2 == 0 {
steps_in_leg += 1;
}
}
}
Ok(())
}
}
struct PrimeCounting;
impl ProgressStyle for PrimeCounting {
fn name(&self) -> &str {
"prime-counting"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"π(x) prime-counting function: a rising step curve filling as primes accumulate"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n = w.max(2).min(3000);
let revealed_x = ((ctx.eased * n as f32).round() as usize).min(n);
let mut pi = 0usize;
let mut prime_counts = vec![0usize; n + 1];
for k in 1..=n {
if is_prime(k as u64) {
pi += 1;
}
prime_counts[k] = pi;
}
let pi_max = prime_counts[n].max(1);
for k in 1..=revealed_x {
let x = ((k - 1) * w / n).min(w.saturating_sub(1));
let count = prime_counts[k];
let bar_h = (count * h / pi_max).min(h);
let y0 = h.saturating_sub(bar_h);
draw::vline(grid, x, y0, h.saturating_sub(1));
}
let scan_x = ((ctx.time * 0.3).fract() * w as f32) as usize;
if scan_x < w {
draw::vline(grid, scan_x, 0, h.saturating_sub(1));
}
Ok(())
}
}
struct Collatz;
impl ProgressStyle for Collatz {
fn name(&self) -> &str {
"collatz"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Collatz (3n+1): seeds swept left→right, trajectory height is stopping sequence depth"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n_seeds = w.min(500).max(1);
let revealed = ((ctx.eased * n_seeds as f32).round() as usize).min(n_seeds);
fn stopping_time(mut k: u64) -> u64 {
let mut count = 0u64;
while k != 1 && count < 10_000 {
k = if k % 2 == 0 { k / 2 } else { 3 * k + 1 };
count += 1;
}
count
}
let times: Vec<u64> = (1..=n_seeds).map(|k| stopping_time(k as u64)).collect();
let max_t = times.iter().copied().max().unwrap_or(1).max(1);
let hi_x = ((ctx.time * 0.7).fract() * revealed as f32) as usize;
for (i, &t) in times.iter().enumerate().take(revealed) {
let x = (i * w / n_seeds).min(w.saturating_sub(1));
let bar_h = ((t as f32 / max_t as f32) * h as f32).round() as usize;
let y0 = h.saturating_sub(bar_h);
draw::vline(grid, x, y0, h.saturating_sub(1));
if i == hi_x && y0 > 0 {
draw::dot(grid, x, y0.saturating_sub(1));
}
}
Ok(())
}
}
struct FibonacciSpiral;
impl ProgressStyle for FibonacciSpiral {
fn name(&self) -> &str {
"fibonacci-spiral"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Golden spiral: Fibonacci quarter-circle arcs converging on φ"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let max_dim = w.min(h).max(1);
let mut fibs = vec![1usize, 1usize];
while *fibs.last().unwrap() < max_dim {
let n = fibs.len();
let next = fibs[n - 1] + fibs[n - 2];
fibs.push(next);
if fibs.len() > 20 {
break;
}
}
let max_arcs = fibs.len().min(12);
let revealed = ((ctx.eased * max_arcs as f32).round() as usize).min(max_arcs);
let cx = (w / 2) as i32;
let cy = (h / 2) as i32;
let start_angles = [0.0f32, PI / 2.0, PI, 3.0 * PI / 2.0];
let mut rx = cx;
let mut ry = cy;
for (arc_idx, &fib) in fibs.iter().enumerate().take(revealed) {
let r = fib as f32;
let angle_start = start_angles[arc_idx % 4];
let steps = ((r * PI / 2.0) as usize).max(4).min(256);
for s in 0..=steps {
let theta = angle_start + (s as f32 / steps as f32) * (PI / 2.0);
let px = rx + (theta.cos() * r) as i32;
let py = ry + (theta.sin() * r) as i32;
draw::dot_i(grid, px, py);
}
match arc_idx % 4 {
0 => ry -= fib as i32,
1 => rx -= fib as i32,
2 => ry += fib as i32,
_ => rx += fib as i32,
}
}
if revealed > 0 && revealed <= fibs.len() {
let last_idx = revealed.saturating_sub(1);
let r = fibs[last_idx] as f32;
let angle_start = start_angles[last_idx % 4];
let theta = angle_start + (ctx.time * 0.5).fract() * (PI / 2.0);
let mut cxl = cx;
let mut cyl = cy;
for i in 0..last_idx {
match i % 4 {
0 => cyl -= fibs[i] as i32,
1 => cxl -= fibs[i] as i32,
2 => cyl += fibs[i] as i32,
_ => cxl += fibs[i] as i32,
}
}
let px = cxl + (theta.cos() * r) as i32;
let py = cyl + (theta.sin() * r) as i32;
draw::dot_i(grid, px, py);
draw::dot_i(grid, px + 1, py);
draw::dot_i(grid, px, py + 1);
}
Ok(())
}
}
struct PascalMod;
impl ProgressStyle for PascalMod {
fn name(&self) -> &str {
"pascal-mod"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Pascal's triangle mod p: Sierpinski (p=2), mod-3, mod-5 patterns cycling with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let mods = [2u64, 3, 5];
let mod_idx = ((ctx.time / 3.0) as usize) % mods.len();
let modulus = mods[mod_idx];
let revealed_rows = ((ctx.eased * h as f32).round() as usize).min(h);
let row_len = w.min(512);
let mut row = vec![0u64; row_len];
if row_len > 0 {
row[0] = 1;
}
for r in 0..revealed_rows {
let py = r;
let entries = (r + 1).min(row_len);
for c in 0..entries {
if row[c] % modulus != 0 {
let x = if entries <= 1 {
w / 2
} else {
c * (w.saturating_sub(1)) / (entries.saturating_sub(1))
};
draw::dot(grid, x, py);
}
}
for c in (1..row_len).rev() {
row[c] = (row[c] + row[c - 1]) % modulus;
}
}
Ok(())
}
}
struct TotientHistogram;
impl ProgressStyle for TotientHistogram {
fn name(&self) -> &str {
"totient-histogram"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Euler totient φ(n): columns show φ(n)/n — primes spike to the top, composites sag"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n = w.max(2).min(2000);
let revealed = ((ctx.eased * n as f32).round() as usize).min(n).max(1);
fn totient(n: usize) -> usize {
if n == 0 {
return 0;
}
let mut phi = n;
let mut temp = n;
let mut p = 2;
while p * p <= temp {
if temp % p == 0 {
while temp % p == 0 {
temp /= p;
}
phi = phi - phi / p;
}
p += 1;
}
if temp > 1 {
phi = phi - phi / temp;
}
phi
}
for k in 2..=revealed {
let x = ((k - 2) * w / (n.saturating_sub(1).max(1))).min(w.saturating_sub(1));
let phi_k = totient(k);
let ratio = phi_k as f32 / k as f32;
let bar_h = (ratio * h as f32).round() as usize;
let y0 = h.saturating_sub(bar_h);
draw::vline(grid, x, y0, h.saturating_sub(1));
}
Ok(())
}
}
struct SternBrocot;
impl ProgressStyle for SternBrocot {
fn name(&self) -> &str {
"stern-brocot"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Stern-Brocot / Farey F_n: every rational in [0,1] placed by value, height = denominator"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n = ((ctx.eased * 20.0).round() as usize).clamp(1, 20);
fn gcd(mut a: usize, mut b: usize) -> usize {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
let mut fracs: Vec<(usize, usize)> = Vec::new();
for q in 1..=n {
for p in 0..=q {
if gcd(p, q) == 1 || (p == 0 && q == 1) {
fracs.push((p, q));
}
}
}
fracs.sort_by(|a, b| {
let va = a.0 * b.1;
let vb = b.0 * a.1;
va.cmp(&vb)
});
fracs.dedup_by(|a, b| a.0 * b.1 == b.0 * a.1);
let max_q = n.max(1);
let cursor_frac = (ctx.time * 0.2).fract();
for (p, q) in &fracs {
let value = *p as f32 / *q as f32;
let x = (value * (w.saturating_sub(1)) as f32).round() as usize;
let bar_h = ((*q as f32 / max_q as f32) * h as f32).round() as usize;
let y0 = h.saturating_sub(bar_h);
draw::vline(grid, x, y0, h.saturating_sub(1));
let dist = (value - cursor_frac).abs();
if dist < 0.03 {
draw::dot(grid, x, y0.saturating_sub(1));
}
}
Ok(())
}
}
struct ContinuedFraction;
impl ProgressStyle for ContinuedFraction {
fn name(&self) -> &str {
"continued-fraction-phi"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Continued fraction convergents of φ: zig-zagging best rationals approaching the golden ratio"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let max_k = h.min(40);
let revealed = ((ctx.eased * max_k as f32).round() as usize).min(max_k);
let mut p_prev = 0u64;
let mut p_curr = 1u64;
let mut q_prev = 1u64;
let mut q_curr = 1u64;
let phi = (1.0 + 5.0f32.sqrt()) / 2.0; let scale = phi;
let mut prev_x: Option<usize> = None;
let mut prev_y: Option<usize> = None;
for k in 0..revealed {
let value = p_curr as f32 / q_curr as f32;
let x =
((value / scale).clamp(0.0, 1.0) * (w.saturating_sub(1)) as f32).round() as usize;
let y = if h <= 1 {
0
} else {
k * (h.saturating_sub(1)) / (max_k.saturating_sub(1).max(1))
};
let y = y.min(h.saturating_sub(1));
draw::dot(grid, x, y);
if let (Some(px), Some(py)) = (prev_x, prev_y) {
let steps = ((x as i32 - px as i32)
.abs()
.max((y as i32 - py as i32).abs()))
.max(1) as usize;
for s in 1..steps {
let t = s as f32 / steps as f32;
let ix = (px as f32 + t * (x as i32 - px as i32) as f32).round() as usize;
let iy = (py as f32 + t * (y as i32 - py as i32) as f32).round() as usize;
draw::dot(
grid,
ix.min(w.saturating_sub(1)),
iy.min(h.saturating_sub(1)),
);
}
}
prev_x = Some(x);
prev_y = Some(y);
let p_new = p_curr + p_prev;
let q_new = q_curr + q_prev;
p_prev = p_curr;
p_curr = p_new;
q_prev = q_curr;
q_curr = q_new;
if p_curr > 1_000_000 || q_curr > 1_000_000 {
break;
}
}
Ok(())
}
}
struct ModularCircle;
impl ProgressStyle for ModularCircle {
fn name(&self) -> &str {
"modular-circle"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Modular multiplication circle: string art i→k·i mod N, k swept by progress (cardioid at k=2)"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n_points = 100usize; let k = (ctx.eased * 10.0).floor() as usize + 2; let k = k.min(50);
let cx = (w / 2) as f32;
let cy = (h / 2) as f32;
let rx = (w / 2).saturating_sub(1) as f32;
let ry = (h / 2).saturating_sub(1) as f32;
let rot = ctx.time * 0.1;
for i in 0..n_points {
let j = (k * i) % n_points;
let angle_i = (i as f32 / n_points as f32) * 2.0 * PI + rot;
let angle_j = (j as f32 / n_points as f32) * 2.0 * PI + rot;
let x0 = (cx + rx * angle_i.cos()).round() as i32;
let y0 = (cy + ry * angle_i.sin()).round() as i32;
let x1 = (cx + rx * angle_j.cos()).round() as i32;
let y1 = (cy + ry * angle_j.sin()).round() as i32;
let steps = ((x1 - x0).abs().max((y1 - y0).abs())).max(1) as usize;
let steps = steps.min(512);
for s in 0..=steps {
let t = s as f32 / steps as f32;
let px = (x0 as f32 + t * (x1 - x0) as f32).round() as i32;
let py = (y0 as f32 + t * (y1 - y0) as f32).round() as i32;
draw::dot_i(grid, px, py);
}
}
Ok(())
}
}
struct Recaman;
impl ProgressStyle for Recaman {
fn name(&self) -> &str {
"recaman"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Recamán's sequence: arcs above/below a number line, each term a backward or forward jump"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let max_terms = w.min(60).max(2);
let revealed = ((ctx.eased * max_terms as f32).round() as usize).clamp(1, max_terms);
let mut seq = vec![0usize];
let mut seen = std::collections::HashSet::new();
seen.insert(0usize);
for n in 1..max_terms {
let prev = seq[n - 1];
let candidate = prev.saturating_sub(n);
let next = if candidate > 0 && !seen.contains(&candidate) {
candidate
} else {
prev + n
};
seq.push(next);
seen.insert(next);
}
let max_val = seq.iter().copied().max().unwrap_or(1).max(1);
let baseline = h / 2;
draw::hline(grid, 0, w.saturating_sub(1), baseline);
for n in 1..revealed {
let a = seq[n - 1];
let b = seq[n];
let forward = b > a;
let x_a = (a * (w.saturating_sub(1)) / max_val).min(w.saturating_sub(1));
let x_b = (b * (w.saturating_sub(1)) / max_val).min(w.saturating_sub(1));
let arc_cx = (x_a + x_b) / 2;
let arc_r = ((x_b as i32 - x_a as i32).abs() / 2).max(1) as f32;
let above = !forward;
let steps = ((arc_r * PI) as usize).max(4).min(256);
for s in 0..=steps {
let theta = s as f32 / steps as f32 * PI;
let dx = (theta.cos() * arc_r).round() as i32;
let dy = (theta.sin() * arc_r).round() as i32;
let px = arc_cx as i32 + dx;
let py = if above {
baseline as i32 - dy
} else {
baseline as i32 + dy
};
draw::dot_i(grid, px, py);
}
}
let _ = ctx.time;
Ok(())
}
}
struct DigitalRoot;
impl ProgressStyle for DigitalRoot {
fn name(&self) -> &str {
"digital-root-vortex"
}
fn theme(&self) -> &str {
"numbertheory"
}
fn describe(&self) -> &str {
"Vortex math: digital-root cycles 1–9 drawn as chords on a circle, revealing mod-9 symmetry"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let cx = (w / 2) as f32;
let cy = (h / 2) as f32;
let rx = (w / 2).saturating_sub(1) as f32;
let ry = (h / 2).saturating_sub(1) as f32;
fn digital_root(n: usize) -> usize {
if n == 0 {
return 9;
}
let r = n % 9;
if r == 0 {
9
} else {
r
}
}
let base_digit = ((ctx.eased * 8.0).floor() as usize + 1).min(9);
let mut cycle = vec![base_digit];
let mut cur = base_digit;
for _ in 0..8 {
cur = digital_root(cur + base_digit);
if cur == cycle[0] {
break;
}
cycle.push(cur);
}
let rot = ctx.time * 0.15;
for d in 1..=9usize {
let angle = (d as f32 - 1.0) / 9.0 * 2.0 * PI - PI / 2.0 + rot;
let px = (cx + rx * angle.cos()).round() as usize;
let py = (cy + ry * angle.sin()).round() as usize;
if px < w && py < h {
draw::dot(grid, px, py);
}
}
for i in 0..cycle.len() {
let a = cycle[i];
let b = cycle[(i + 1) % cycle.len()];
let angle_a = (a as f32 - 1.0) / 9.0 * 2.0 * PI - PI / 2.0 + rot;
let angle_b = (b as f32 - 1.0) / 9.0 * 2.0 * PI - PI / 2.0 + rot;
let x0 = (cx + rx * angle_a.cos()).round() as i32;
let y0 = (cy + ry * angle_a.sin()).round() as i32;
let x1 = (cx + rx * angle_b.cos()).round() as i32;
let y1 = (cy + ry * angle_b.sin()).round() as i32;
let steps = ((x1 - x0).abs().max((y1 - y0).abs())).max(1) as usize;
let steps = steps.min(512);
for s in 0..=steps {
let t = s as f32 / steps as f32;
let px = (x0 as f32 + t * (x1 - x0) as f32).round() as i32;
let py = (y0 as f32 + t * (y1 - y0) as f32).round() as i32;
draw::dot_i(grid, px, py);
}
}
for d in [3usize, 6, 9] {
let angle = (d as f32 - 1.0) / 9.0 * 2.0 * PI - PI / 2.0 + rot;
let px = (cx + rx * angle.cos()).round() as i32;
let py = (cy + ry * angle.sin()).round() as i32;
draw::dot_i(grid, px, py);
draw::dot_i(grid, px + 1, py);
draw::dot_i(grid, px, py + 1);
draw::dot_i(grid, px - 1, py);
draw::dot_i(grid, px, py - 1);
}
Ok(())
}
}