use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, Color, DotmaxError};
#[inline]
fn hash(n: u32) -> u32 {
let mut x = n.wrapping_mul(2_654_435_761);
x ^= x >> 15;
x.wrapping_mul(2_246_822_519)
}
#[inline]
fn hash_f(n: u32) -> f32 {
(hash(n) % 1000) as f32 / 1000.0
}
#[inline]
fn hash2(x: u32, y: u32) -> u32 {
hash(x.wrapping_mul(1_000_003).wrapping_add(y))
}
const TINT_START: Color = Color::rgb(96, 226, 130);
const TINT_END: Color = Color::rgb(24, 128, 88);
fn sample_tint(t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
let lerp = |a: u8, b: u8| (f32::from(a) + (f32::from(b) - f32::from(a)) * t) as u8;
Color::rgb(
lerp(TINT_START.r, TINT_END.r),
lerp(TINT_START.g, TINT_END.g),
lerp(TINT_START.b, TINT_END.b),
)
}
struct Tinted<S>(S);
impl<S: ProgressStyle> ProgressStyle for Tinted<S> {
fn name(&self) -> &str {
self.0.name()
}
fn theme(&self) -> &str {
self.0.theme()
}
fn describe(&self) -> &str {
self.0.describe()
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
self.0.render(grid, ctx)?;
grid.enable_color_support();
let (w, h) = grid.dimensions();
for y in 0..h {
for x in 0..w {
let ch = grid.get_char(x, y);
if ch != '\u{2800}' && ch != ' ' {
let t = (x as f32 / w.max(1) as f32 + ctx.time * 0.05).fract();
let tri = 1.0 - (2.0 * t - 1.0).abs();
let _ = grid.set_cell_color(x, y, sample_tint(tri));
}
}
}
Ok(())
}
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(Tinted(WolframRule { rule: 30 })),
Box::new(Tinted(WolframRule { rule: 90 })),
Box::new(Tinted(WolframRule { rule: 110 })),
Box::new(Tinted(WolframRule { rule: 184 })),
Box::new(Tinted(WolframRule { rule: 54 })),
Box::new(Tinted(WolframRule { rule: 150 })),
Box::new(Tinted(GameOfLife {
seed: GoLSeed::Glider,
})),
Box::new(Tinted(GameOfLife {
seed: GoLSeed::RPentomino,
})),
Box::new(Tinted(BriansBrain)),
Box::new(Tinted(LangtonsAnt)),
Box::new(Tinted(CyclicCA)),
Box::new(Tinted(Wireworld)),
Box::new(Tinted(GrayScott)),
Box::new(Tinted(ForestFire)),
]
}
struct WolframRule {
rule: u8,
}
impl WolframRule {
fn step(row: &[u8], rule: u8) -> Vec<u8> {
let w = row.len();
(0..w)
.map(|i| {
let l = if i == 0 { row[w - 1] } else { row[i - 1] };
let c = row[i];
let r = if i + 1 == w { row[0] } else { row[i + 1] };
let pattern = (l << 2) | (c << 1) | r;
(rule >> pattern) & 1
})
.collect()
}
}
impl ProgressStyle for WolframRule {
fn name(&self) -> &str {
match self.rule {
30 => "ca-rule30",
90 => "ca-rule90",
110 => "ca-rule110",
184 => "ca-rule184",
54 => "ca-rule54",
150 => "ca-rule150",
_ => "ca-wolfram",
}
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
match self.rule {
30 => "Rule 30: chaotic braille texture — Wolfram's fractal entropy engine",
90 => "Rule 90: Sierpinski triangle — XOR diffusion fills the bar row by row",
110 => "Rule 110: Turing-complete edge of chaos — complex local structures emerge",
184 => "Rule 184: traffic flow CA — particles drift rightward with eased density",
54 => "Rule 54: nested gliders — quasi-periodic wave fronts cascade down the bar",
150 => "Rule 150: additive XOR diffusion — Pascal's triangle mod 2 in braille",
_ => "Wolfram elementary CA",
}
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
let w = w.max(1);
let h = h.max(1);
let reveal = (ctx.eased * h as f32).ceil() as usize;
let scroll = if self.rule == 184 {
((ctx.time * 0.25).fract() * w as f32) as usize % w
} else {
(ctx.time * 0.3) as usize % w.max(1)
};
let mut row = vec![0u8; w];
if self.rule == 184 {
let density = 0.30 + 0.45 * ctx.eased;
for (x, cell) in row.iter_mut().enumerate() {
if hash_f(x as u32 * 3 + 11) < density {
*cell = 1;
}
}
} else {
row[w / 2] = 1;
}
for gen in 0..reveal.min(h) {
for x in 0..w {
let sx = (x + scroll) % w;
if row[sx] == 1 {
draw::dot(grid, x, gen);
}
}
row = Self::step(&row, self.rule);
}
Ok(())
}
}
#[derive(Clone, Copy)]
enum GoLSeed {
Glider,
RPentomino,
}
struct GameOfLife {
seed: GoLSeed,
}
impl GameOfLife {
fn make_board(w: usize, h: usize, seed: GoLSeed) -> Vec<Vec<bool>> {
let mut board = vec![vec![false; w]; h];
match seed {
GoLSeed::Glider => {
let patterns: &[(usize, usize)] = &[(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)];
for &(px, py) in patterns {
if px < w && py < h {
board[py][px] = true;
}
}
}
GoLSeed::RPentomino => {
let cx = w / 2;
let cy = h / 2;
let patterns: &[(i32, i32)] = &[(1, -1), (2, -1), (0, 0), (1, 0), (1, 1)];
for &(dx, dy) in patterns {
let px = cx as i32 + dx;
let py = cy as i32 + dy;
if px >= 0 && py >= 0 && (px as usize) < w && (py as usize) < h {
board[py as usize][px as usize] = true;
}
}
}
}
board
}
fn step(board: &[Vec<bool>]) -> Vec<Vec<bool>> {
let h = board.len();
let w = if h == 0 { 0 } else { board[0].len() };
let mut next = vec![vec![false; w]; h];
for y in 0..h {
for x in 0..w {
let mut n = 0u8;
for dy in -1i32..=1 {
for dx in -1i32..=1 {
if dx == 0 && dy == 0 {
continue;
}
let nx = (x as i32 + dx).rem_euclid(w as i32) as usize;
let ny = (y as i32 + dy).rem_euclid(h as i32) as usize;
if board[ny][nx] {
n += 1;
}
}
}
next[y][x] = if board[y][x] {
n == 2 || n == 3
} else {
n == 3
};
}
}
next
}
}
impl ProgressStyle for GameOfLife {
fn name(&self) -> &str {
match self.seed {
GoLSeed::Glider => "ca-gol-glider",
GoLSeed::RPentomino => "ca-gol-rpentomino",
}
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
match self.seed {
GoLSeed::Glider =>
"Game of Life glider — a diagonal spaceship animates across the bar as progress fills",
GoLSeed::RPentomino =>
"Game of Life r-pentomino — explosive chaotic growth, progress gates the reveal column",
}
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
let w = w.max(1);
let h = h.max(1);
let speed = match self.seed {
GoLSeed::Glider => 4.0f32,
GoLSeed::RPentomino => 8.0f32,
};
let gens = (ctx.time * speed) as usize;
let gens = gens.min(500);
let mut board = Self::make_board(w, h, self.seed);
for _ in 0..gens {
board = Self::step(&board);
}
let reveal_x = (ctx.eased * w as f32).round() as usize;
for (y, row) in board.iter().enumerate().take(h) {
for x in 0..reveal_x.min(w) {
if x < row.len() && row[x] {
draw::dot(grid, x, y);
}
}
}
for x in (0..reveal_x.min(w)).step_by(3) {
draw::dot(grid, x, h - 1);
}
let edge = reveal_x.min(w.saturating_sub(1));
draw::vline(grid, edge, h.saturating_sub(3), h - 1);
Ok(())
}
}
struct BriansBrain;
impl BriansBrain {
fn initial(w: usize, h: usize) -> Vec<Vec<u8>> {
let mut board = vec![vec![0u8; w]; h];
for (y, row) in board.iter_mut().enumerate() {
for (x, cell) in row.iter_mut().enumerate() {
let v = hash2(x as u32, y as u32) % 5;
*cell = u8::from(v == 0);
}
}
board
}
fn step(board: &[Vec<u8>]) -> Vec<Vec<u8>> {
let h = board.len();
let w = if h == 0 { 0 } else { board[0].len() };
let mut next = vec![vec![0u8; w]; h];
for y in 0..h {
for x in 0..w {
let state = board[y][x];
next[y][x] = match state {
1 => 2, 2 => 0, _ => {
let mut n = 0u8;
for dy in -1i32..=1 {
for dx in -1i32..=1 {
if dx == 0 && dy == 0 {
continue;
}
let nx = (x as i32 + dx).rem_euclid(w as i32) as usize;
let ny = (y as i32 + dy).rem_euclid(h as i32) as usize;
if board[ny][nx] == 1 {
n += 1;
}
}
}
u8::from(n == 2)
}
};
}
}
next
}
}
impl ProgressStyle for BriansBrain {
fn name(&self) -> &str {
"ca-brians-brain"
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
"Brian's Brain: 3-state excitable CA — firing cells with refractory tails swarm as gliders, revealed left-to-right by progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
let w = w.max(1);
let h = h.max(1);
let gens = ((ctx.time * 6.0) as usize).min(300);
let mut board = Self::initial(w, h);
for _ in 0..gens {
board = Self::step(&board);
}
let reveal_x = ((ctx.eased * w as f32) as usize).max(6).min(w);
for (y, row) in board.iter().enumerate().take(h) {
for (x, &cell) in row.iter().enumerate().take(reveal_x) {
if cell == 1 || (cell == 2 && (x + y) % 2 == 0) {
draw::dot(grid, x, y);
}
}
}
if reveal_x < w {
for y in (0..h).step_by(2) {
draw::dot(grid, reveal_x, y);
}
}
Ok(())
}
}
struct LangtonsAnt;
impl LangtonsAnt {
const MAX_STEPS: usize = 2000;
}
impl ProgressStyle for LangtonsAnt {
fn name(&self) -> &str {
"ca-langtons-ant"
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
"Langton's Ant: deterministic trail grows with progress — the highway emerges near 10,000 steps"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
let w = w.max(1);
let h = h.max(1);
let steps = (ctx.eased * Self::MAX_STEPS as f32) as usize;
let time_offset = (ctx.time * 0.5) as usize % 8;
let steps = steps.saturating_add(time_offset).min(Self::MAX_STEPS + 8);
let mut cells = vec![0u8; w * h];
let mut ax = (w / 2) as i32;
let mut ay = (h / 2) as i32;
let mut dir = 0i32;
for _ in 0..steps {
ax = ax.rem_euclid(w as i32);
ay = ay.rem_euclid(h as i32);
let idx = ay as usize * w + ax as usize;
let c = cells[idx];
if c == 0 {
dir = (dir + 1).rem_euclid(4); cells[idx] = 1;
} else {
dir = (dir - 1).rem_euclid(4); cells[idx] = 0;
}
match dir {
0 => ay -= 1,
1 => ax += 1,
2 => ay += 1,
_ => ax -= 1,
}
}
for y in 0..h {
for x in 0..w {
if cells[y * w + x] == 1 {
draw::dot(grid, x, y);
}
}
}
Ok(())
}
}
struct CyclicCA;
impl CyclicCA {
const N_STATES: u8 = 8;
const THRESHOLD: usize = 1;
fn initial(w: usize, h: usize, density_seed: u32) -> Vec<Vec<u8>> {
let n = Self::N_STATES;
(0..h)
.map(|y| {
(0..w)
.map(|x| {
let v = hash(hash2(x as u32, y as u32) ^ density_seed) >> 16;
(v % n as u32) as u8
})
.collect()
})
.collect()
}
fn step(board: &[Vec<u8>]) -> Vec<Vec<u8>> {
let h = board.len();
let w = if h == 0 { 0 } else { board[0].len() };
let n = CyclicCA::N_STATES;
let thresh = CyclicCA::THRESHOLD;
let mut next = board.to_vec();
for y in 0..h {
for x in 0..w {
let cur = board[y][x];
let target = (cur + 1) % n;
let mut count = 0usize;
for (dx, dy) in [(1i32, 0i32), (-1, 0), (0, 1), (0, -1)] {
let nx = (x as i32 + dx).rem_euclid(w as i32) as usize;
let ny = (y as i32 + dy).rem_euclid(h as i32) as usize;
if board[ny][nx] == target {
count += 1;
}
}
if count >= thresh {
next[y][x] = target;
}
}
}
next
}
}
impl ProgressStyle for CyclicCA {
fn name(&self) -> &str {
"ca-cyclic"
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
"Griffeath cyclic CA: 8 states, each eats its predecessor — spiral demons sweep rotating wave bands across a field revealed by progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
let w = w.max(1);
let h = h.max(1);
let period = Self::N_STATES as usize * 12;
let gens = 48 + (ctx.time.max(0.0) * 6.0) as usize % period;
let seed = 7u32;
let mut board = Self::initial(w, h, seed);
for _ in 0..gens {
board = Self::step(&board);
}
let reveal_x = (ctx.eased * w as f32).round() as usize;
let half = Self::N_STATES / 2;
for (y, row) in board.iter().enumerate().take(h) {
for x in 0..reveal_x.min(w) {
if x < row.len() && row[x] >= half {
draw::dot(grid, x, y);
}
}
}
let edge = reveal_x.min(w.saturating_sub(1));
draw::vline(grid, edge, 0, h - 1);
Ok(())
}
}
struct Wireworld;
impl ProgressStyle for Wireworld {
fn name(&self) -> &str {
"ca-wireworld"
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
"Wireworld clock: an electron circling a conductor loop fires pulses down a wire laid out to the progress point"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w < 4 || h < 3 {
if w > 0 && h > 0 {
let end = (ctx.eased * w as f32) as usize;
draw::hline(grid, 0, end.min(w - 1), h / 2);
}
return Ok(());
}
let (wi, hi) = (w as i32, h as i32);
let mid = hi / 2;
let half_h = (mid - 1).clamp(1, 3);
let (lx0, lx1) = (1i32, 7i32.min(wi - 2));
let (ly0, ly1) = (mid - half_h, mid + half_h);
let mut loop_cells: Vec<(i32, i32)> = Vec::new();
for x in lx0..=lx1 {
loop_cells.push((x, ly0));
}
for y in ly0 + 1..=ly1 {
loop_cells.push((lx1, y));
}
for x in (lx0..lx1).rev() {
loop_cells.push((x, ly1));
}
for y in (ly0 + 1..ly1).rev() {
loop_cells.push((lx0, y));
}
let lap = loop_cells.len().max(1) as i32;
for &(x, y) in &loop_cells {
draw::dot_i(grid, x, y);
}
let tap = loop_cells
.iter()
.position(|&(x, y)| x == lx1 && y == mid)
.unwrap_or(0) as i32;
let wire_x0 = lx1 + 1;
let wire_end = ((ctx.eased * w as f32) as i32).max(wire_x0 + 6).min(wi - 1);
draw::hline(grid, wire_x0 as usize, wire_end as usize, mid as usize);
let g = (ctx.time.max(0.0) * 10.0) as i32;
let head = |grid: &mut BrailleGrid, x: i32, y: i32, vertical_wire: bool| {
draw::dot_i(grid, x, y);
if vertical_wire {
draw::dot_i(grid, x - 1, y);
draw::dot_i(grid, x + 1, y);
} else {
draw::dot_i(grid, x, y - 1);
draw::dot_i(grid, x, y + 1);
}
};
let hp = g % lap;
let (hx, hy) = loop_cells[hp as usize];
head(grid, hx, hy, hx == lx0 || hx == lx1);
let mut d = (g - tap).rem_euclid(lap);
while wire_x0 + d - 1 <= wire_end {
let px = wire_x0 + d - 1;
if px >= wire_x0 {
head(grid, px, mid, false);
head(grid, px - 1, mid, false);
if px > wire_x0 + 1 {
draw::dot_i(grid, px - 2, mid - 1);
draw::dot_i(grid, px - 2, mid + 1);
}
}
d += lap;
}
Ok(())
}
}
struct GrayScott;
impl ProgressStyle for GrayScott {
fn name(&self) -> &str {
"ca-gray-scott"
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
"Gray-Scott reaction-diffusion, spot (mitosis) regime: a colony of round Turing spots spreads from the centre as progress feeds it, new spots budding at the rim"
}
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 k = std::f32::consts::TAU / 7.0;
let dirs = [(1.0f32, 0.0f32), (-0.5, 0.866), (-0.5, -0.866)];
let cx = w as f32 / 2.0;
let cy = h as f32 / 2.0;
let radius = 5.0 + ctx.eased * (cx + 2.0);
const FRONT: f32 = 5.0;
let t = ctx.time;
for y in 0..h {
for x in 0..w {
let (px, py) = (x as f32, y as f32);
let d = ((px - cx).powi(2) + ((py - cy) * 2.5).powi(2)).sqrt();
if d > radius {
continue;
}
let f: f32 = dirs
.iter()
.map(|&(ex, ey)| (k * (ex * px + ey * py)).cos())
.sum();
let grow = ((radius - d) / FRONT).clamp(0.0, 1.0);
let thresh = 1.0 + 1.3 * (1.0 - grow) + 0.15 * (t * 1.7 + px * 0.09).sin();
if f > thresh {
draw::dot(grid, x, y);
}
}
}
Ok(())
}
}
struct ForestFire;
impl ProgressStyle for ForestFire {
fn name(&self) -> &str {
"ca-forest-fire"
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
"Forest-fire CA: a flame front burns through scattered trees as progress advances"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
let w = w.max(1);
let h = h.max(1);
let front = (ctx.eased * w as f32) as i32;
for y in 0..h {
for x in 0..w {
if hash_f((y * w + x) as u32 ^ 0x9e37) >= 0.55 {
continue;
}
let d = x as i32 - front;
if d > 1 {
draw::dot(grid, x, y);
if y > 0 && hash_f((x * 7 + y) as u32) > 0.5 {
draw::dot(grid, x, y - 1);
}
} else if d >= -2 {
let flick = ((ctx.time * 12.0 + (x + y) as f32).sin() * 2.0) as i32;
draw::dot_i(grid, x as i32, y as i32 - flick.abs());
draw::dot(grid, x, y);
}
}
}
Ok(())
}
}