use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, 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))
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(WolframRule { rule: 30 }),
Box::new(WolframRule { rule: 90 }),
Box::new(WolframRule { rule: 110 }),
Box::new(WolframRule { rule: 184 }),
Box::new(WolframRule { rule: 54 }),
Box::new(WolframRule { rule: 150 }),
Box::new(GameOfLife {
seed: GoLSeed::Glider,
}),
Box::new(GameOfLife {
seed: GoLSeed::RPentomino,
}),
Box::new(BriansBrain),
Box::new(LangtonsAnt),
Box::new(CyclicCA),
Box::new(Wireworld),
Box::new(GrayScott),
Box::new(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 = (ctx.time * 0.3) as usize % w.max(1);
let mut row = vec![0u8; w];
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 in 0..h.min(board.len()) {
for x in 0..reveal_x.min(w) {
if x < board[y].len() && board[y][x] {
draw::dot(grid, x, y);
}
}
}
Ok(())
}
}
struct BriansBrain;
impl BriansBrain {
fn initial(w: usize, h: usize) -> Vec<Vec<u8>> {
let mut board = vec![vec![0u8; w]; h];
for y in 0..h {
for x in 0..w {
let v = hash2(x as u32, y as u32) % 5;
board[y][x] = if v == 0 { 1 } else { 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;
}
}
}
if n == 2 {
1
} else {
0
}
}
};
}
}
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 waves pulse and swirl 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 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 max_sum = (w + h).saturating_sub(2).max(1);
for y in 0..h.min(board.len()) {
for x in 0..w {
let reveal_frac = (x + y) as f32 / max_sum as f32;
if reveal_frac <= ctx.eased {
if x < board[y].len() && board[y][x] == 1 {
draw::dot(grid, 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 = 3;
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 = hash2(x as u32 ^ density_seed, y as u32);
(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 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] == 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 {
"Cyclic CA (rock-paper-scissors): spiral waves of 8 states — progress advances the generation count"
}
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.eased * 40.0) as usize;
let seed = (ctx.time * 0.2) as u32 % 16;
let mut board = Self::initial(w, h, seed);
for _ in 0..gens.min(80) {
board = Self::step(&board);
}
let half = Self::N_STATES / 2;
for y in 0..h.min(board.len()) {
for x in 0..w {
if x < board[y].len() && board[y][x] >= half {
draw::dot(grid, x, y);
}
}
}
Ok(())
}
}
struct Wireworld;
impl Wireworld {
const EMPTY: u8 = 0;
const CONDUCTOR: u8 = 1;
const HEAD: u8 = 2;
const TAIL: u8 = 3;
fn build_grid(w: usize, h: usize, fill_frac: f32) -> Vec<Vec<u8>> {
let mut board = vec![vec![Self::EMPTY; w]; h];
let perimeter: Vec<(usize, usize)> = {
let mut p = Vec::new();
for x in 0..w {
p.push((x, 0));
}
for y in 1..h {
p.push((w.saturating_sub(1), y));
}
if h > 1 {
let y = h - 1;
for x in (0..w.saturating_sub(1)).rev() {
p.push((x, y));
}
}
if w > 1 && h > 1 {
for y in (1..h.saturating_sub(1)).rev() {
p.push((0, y));
}
}
p
};
let total = perimeter.len().max(1);
let lit = (fill_frac * total as f32) as usize;
for i in 0..lit.min(total) {
let (x, y) = perimeter[i];
board[y][x] = Self::CONDUCTOR;
}
board
}
fn inject_electron(board: &mut Vec<Vec<u8>>, electron_pos: usize) {
let h = board.len();
let w = if h == 0 { 0 } else { board[0].len() };
let mut idx = 0usize;
'outer: for pass in 0..2usize {
for x in 0..w {
if board[0][x] == Self::CONDUCTOR {
if idx == electron_pos {
board[0][x] = if pass == 0 { Self::HEAD } else { Self::TAIL };
break 'outer;
}
idx += 1;
}
}
for y in 1..h {
let rx = w.saturating_sub(1);
if board[y][rx] == Self::CONDUCTOR {
if idx == electron_pos {
board[y][rx] = if pass == 0 { Self::HEAD } else { Self::TAIL };
break 'outer;
}
idx += 1;
}
}
if h > 1 {
let by = h - 1;
for x in (0..w.saturating_sub(1)).rev() {
if board[by][x] == Self::CONDUCTOR {
if idx == electron_pos {
board[by][x] = if pass == 0 { Self::HEAD } else { Self::TAIL };
break 'outer;
}
idx += 1;
}
}
}
if w > 1 && h > 1 {
for y in (1..h.saturating_sub(1)).rev() {
if board[y][0] == Self::CONDUCTOR {
if idx == electron_pos {
board[y][0] = if pass == 0 { Self::HEAD } else { Self::TAIL };
break 'outer;
}
idx += 1;
}
}
}
break;
}
}
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 = board.to_vec();
for y in 0..h {
for x in 0..w {
next[y][x] = match board[y][x] {
Self::HEAD => Self::TAIL,
Self::TAIL => Self::CONDUCTOR,
Self::CONDUCTOR => {
let mut heads = 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] == Self::HEAD {
heads += 1;
}
}
}
if heads == 1 || heads == 2 {
Self::HEAD
} else {
Self::CONDUCTOR
}
}
_ => Self::EMPTY,
};
}
}
next
}
}
impl ProgressStyle for Wireworld {
fn name(&self) -> &str {
"ca-wireworld"
}
fn theme(&self) -> &str {
"cellular"
}
fn describe(&self) -> &str {
"Wireworld: an electron chases itself around a conductor perimeter — conductor grows with 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 mut board = Self::build_grid(w, h, ctx.eased);
let perimeter_len = (2 * (w + h)).saturating_sub(4).max(1);
let head_pos = (ctx.time * 8.0) as usize % perimeter_len;
let tail_pos = head_pos.saturating_sub(1) % perimeter_len;
Self::inject_electron(&mut board, head_pos);
Self::inject_electron(&mut board, tail_pos);
for _ in 0..2 {
board = Self::step(&board);
}
for y in 0..h.min(board.len()) {
for x in 0..w {
if x < board[y].len() {
match board[y][x] {
Self::CONDUCTOR => draw::dot(grid, x, y),
Self::HEAD => {
draw::dot(grid, x, y);
if x + 1 < w {
draw::dot(grid, x + 1, y);
}
if x > 0 {
draw::dot(grid, x.saturating_sub(1), y);
}
}
Self::TAIL => {} _ => {}
}
}
}
}
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: coral/mitosis labyrinths grow as progress feeds the reaction"
}
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 n = w * h;
let mut u = vec![1.0f32; n];
let mut v = vec![0.0f32; n];
let cx = w / 2;
let cy = h / 2;
for dy in 0..3 {
for dx in 0..3 {
let x = (cx + dx).saturating_sub(1);
let y = (cy + dy).saturating_sub(1);
if x < w && y < h {
v[y * w + x] = 1.0;
u[y * w + x] = 0.5;
}
}
}
const F: f32 = 0.055;
const K: f32 = 0.062;
const DU: f32 = 0.16;
const DV: f32 = 0.08;
let iters = ((ctx.eased * 60.0) as usize + 1).min(90);
let mut un = u.clone();
let mut vn = v.clone();
for _ in 0..iters {
for y in 0..h {
let ym = if y == 0 { h - 1 } else { y - 1 };
let yp = if y + 1 == h { 0 } else { y + 1 };
for x in 0..w {
let xm = if x == 0 { w - 1 } else { x - 1 };
let xp = if x + 1 == w { 0 } else { x + 1 };
let i = y * w + x;
let lap_u =
u[y * w + xm] + u[y * w + xp] + u[ym * w + x] + u[yp * w + x] - 4.0 * u[i];
let lap_v =
v[y * w + xm] + v[y * w + xp] + v[ym * w + x] + v[yp * w + x] - 4.0 * v[i];
let uvv = u[i] * v[i] * v[i];
un[i] = (u[i] + DU * lap_u - uvv + F * (1.0 - u[i])).clamp(0.0, 1.0);
vn[i] = (v[i] + DV * lap_v + uvv - (F + K) * v[i]).clamp(0.0, 1.0);
}
}
std::mem::swap(&mut u, &mut un);
std::mem::swap(&mut v, &mut vn);
}
for y in 0..h {
for x in 0..w {
if v[y * w + x] > 0.25 {
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(())
}
}