Skip to main content

cellular_automaton/
cellular_automaton.rs

1//! Cellular Automaton Example
2//!
3//! Demonstrates the Comonad pattern: a 1D cellular automaton using
4//! Extend (apply rule at every position) and Comonad (extract current cell)
5//! over NonEmptyVec.
6//!
7//! Run with: cargo run -p karpal-std --example cellular_automaton
8
9use karpal_std::prelude::*;
10
11// --- Rule: look at current cell and neighbors ---
12
13/// A simple 1D rule: a cell becomes alive (1) if exactly one of its
14/// two neighbors is alive, otherwise it dies (0). This is similar to
15/// Rule 90 (XOR of neighbors).
16fn rule_90(grid: &NonEmptyVec<u8>) -> u8 {
17    let tails = grid.tails();
18    let current = <NonEmptyVecF as Comonad>::extract(grid);
19    let len = grid.len();
20
21    // Get left neighbor (wrapping)
22    let left = if len > 1 {
23        // The last tail gives us the rightmost element as neighbor context
24        *tails.tail.last().map(|t| &t.head).unwrap_or(&grid.head)
25    } else {
26        current
27    };
28
29    // Get right neighbor
30    let right = if grid.tail.is_empty() {
31        grid.head // wrap around
32    } else {
33        grid.tail[0]
34    };
35
36    // XOR of neighbors
37    left ^ right
38}
39
40/// Simpler rule: majority vote of (left, current, right).
41/// Cell is alive if 2+ of the three are alive.
42fn rule_majority(grid: &NonEmptyVec<u8>) -> u8 {
43    let current = <NonEmptyVecF as Comonad>::extract(grid);
44    let len = grid.len();
45
46    let left = if len > 1 {
47        let tails = grid.tails();
48        *tails.tail.last().map(|t| &t.head).unwrap_or(&grid.head)
49    } else {
50        current
51    };
52
53    let right = if grid.tail.is_empty() {
54        grid.head
55    } else {
56        grid.tail[0]
57    };
58
59    let sum = left as u16 + current as u16 + right as u16;
60    if sum >= 2 { 1 } else { 0 }
61}
62
63// --- Evolution via Extend ---
64
65/// Evolve the grid one step using Extend: applies the rule at every position.
66fn step(grid: NonEmptyVec<u8>, rule: fn(&NonEmptyVec<u8>) -> u8) -> NonEmptyVec<u8> {
67    NonEmptyVecF::extend(grid, rule)
68}
69
70/// Evolve for n steps.
71fn evolve(
72    initial: NonEmptyVec<u8>,
73    rule: fn(&NonEmptyVec<u8>) -> u8,
74    steps: usize,
75) -> Vec<NonEmptyVec<u8>> {
76    let mut history = vec![initial.clone()];
77    let mut current = initial;
78    for _ in 0..steps {
79        current = step(current, rule);
80        history.push(current.clone());
81    }
82    history
83}
84
85// --- Display ---
86
87fn display_grid(grid: &NonEmptyVec<u8>) -> String {
88    let mut s = String::new();
89    for cell in grid.iter() {
90        s.push(if *cell == 1 { '#' } else { '.' });
91    }
92    s
93}
94
95fn main() {
96    println!("=== Cellular Automaton Example ===\n");
97    println!("Using Extend (Comonad) to evolve a 1D cellular automaton.\n");
98
99    // Initial state: single cell in the middle of a 21-cell grid
100    let width = 21;
101    let mid = width / 2;
102    let mut cells: Vec<u8> = vec![0; width];
103    cells[mid] = 1;
104    let initial = NonEmptyVec::new(cells[0], cells[1..].to_vec());
105
106    // Rule 90 (XOR of neighbors)
107    println!("--- Rule 90 (XOR of neighbors) ---");
108    let history = evolve(initial.clone(), rule_90, 10);
109    for (i, grid) in history.iter().enumerate() {
110        println!("  {:>2}: {}", i, display_grid(grid));
111    }
112
113    // Demonstrate Comonad::extract
114    println!("\n--- Comonad::extract (read focused cell) ---");
115    println!(
116        "  Head of initial grid: {}",
117        <NonEmptyVecF as Comonad>::extract(&initial)
118    );
119
120    // Majority rule
121    println!("\n--- Majority rule ---");
122    // Start with a more interesting pattern
123    let pattern = NonEmptyVec::new(1, vec![0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0]);
124    println!("  Initial: {}", display_grid(&pattern));
125    let history = evolve(pattern, rule_majority, 8);
126    for (i, grid) in history.iter().enumerate() {
127        println!("  {:>2}: {}", i, display_grid(grid));
128    }
129
130    // Demonstrate Extend::duplicate
131    println!("\n--- Extend::duplicate (all focused views) ---");
132    let small = NonEmptyVec::new(1, vec![2, 3]);
133    let duplicated: NonEmptyVec<NonEmptyVec<u8>> = NonEmptyVecF::duplicate(small);
134    println!("  Original: [1, 2, 3]");
135    println!("  Duplicated (each row is a focused view):");
136    for (i, view) in duplicated.iter().enumerate() {
137        let cells: Vec<String> = view.iter().map(|c| c.to_string()).collect();
138        println!("    focus {}: [{}]", i, cells.join(", "));
139    }
140}