use crate::engine::{Automaton, NoAction, Range, Time};
use rill_core::traits::ParamValue;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CellularType {
OneDimensional,
TwoDimensional,
Cyclic,
}
#[derive(Debug, Clone)]
pub struct CellularAutomaton {
name: String,
cell_type: CellularType,
rule: u8,
size: usize,
width: usize,
height: usize,
output_mode: OutputMode,
range: Range,
rng_state: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OutputMode {
Center,
Sum,
Density,
Index(usize),
}
impl CellularAutomaton {
pub fn one_dimensional(name: &str, rule: u8, size: usize) -> Self {
Self {
name: name.to_string(),
cell_type: CellularType::OneDimensional,
rule,
size,
width: size,
height: 1,
output_mode: OutputMode::Center,
range: Range::unipolar(),
rng_state: 123456789,
}
}
pub fn game_of_life(name: &str, width: usize, height: usize) -> Self {
Self {
name: name.to_string(),
cell_type: CellularType::TwoDimensional,
rule: 0,
size: width * height,
width,
height,
output_mode: OutputMode::Density,
range: Range::unipolar(),
rng_state: 123456789,
}
}
pub fn with_output_mode(mut self, mode: OutputMode) -> Self {
self.output_mode = mode;
self
}
pub fn with_range(mut self, range: Range) -> Self {
self.range = range;
self
}
fn random_initial(&self, rng: &mut u64) -> Vec<u8> {
let mut gen = Vec::with_capacity(self.size);
for _ in 0..self.size {
gen.push(if self.random_bit(rng) { 1 } else { 0 });
}
gen
}
fn random_bit(&self, rng: &mut u64) -> bool {
let mut x = *rng;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*rng = x;
(x & 1) == 1
}
fn apply_rule_1d(&self, generation: &[u8]) -> Vec<u8> {
let mut next = vec![0; generation.len()];
for i in 0..generation.len() {
let left = if i > 0 {
generation[i - 1]
} else {
generation[generation.len() - 1]
};
let center = generation[i];
let right = if i < generation.len() - 1 {
generation[i + 1]
} else {
generation[0]
};
let pattern = (left << 2) | (center << 1) | right;
let bit = (self.rule >> pattern) & 1;
next[i] = bit;
}
next
}
fn apply_rule_gol(&self, generation: &[u8]) -> Vec<u8> {
let mut next = vec![0; generation.len()];
for y in 0..self.height {
for x in 0..self.width {
let idx = y * self.width + x;
let cell = generation[idx];
let mut neighbors = 0;
for dy in -1..=1 {
for dx in -1..=1 {
if dx == 0 && dy == 0 {
continue;
}
let nx = (x as i32 + dx + self.width as i32) % self.width as i32;
let ny = (y as i32 + dy + self.height as i32) % self.height as i32;
let nidx = (ny * self.width as i32 + nx) as usize;
if generation[nidx] == 1 {
neighbors += 1;
}
}
}
next[idx] = match (cell, neighbors) {
(1, 2) | (1, 3) => 1, (0, 3) => 1, _ => 0, };
}
}
next
}
fn compute_output(&self, generation: &[u8]) -> f64 {
match self.output_mode {
OutputMode::Center => {
let idx = self.size / 2;
generation[idx] as f64
}
OutputMode::Sum => {
let sum: usize = generation.iter().map(|&c| c as usize).sum();
sum as f64 / self.size as f64
}
OutputMode::Density => {
let sum: usize = generation.iter().map(|&c| c as usize).sum();
sum as f64 / self.size as f64
}
OutputMode::Index(idx) => {
if idx < generation.len() {
generation[idx] as f64
} else {
0.0
}
}
}
}
}
impl Automaton for CellularAutomaton {
type Internal = (Vec<u8>, usize);
type Action = NoAction;
fn step(
&self,
internal: &mut Self::Internal,
_current: &ParamValue,
_time: Time,
_action: &Self::Action,
) -> ParamValue {
let (cells, generation) = internal;
*cells = match self.cell_type {
CellularType::OneDimensional => self.apply_rule_1d(cells),
CellularType::TwoDimensional => self.apply_rule_gol(cells),
CellularType::Cyclic => cells.clone(),
};
*generation += 1;
let raw = self.compute_output(cells);
let value = self.range.clamp(raw);
ParamValue::Float(value as f32)
}
fn initial_internal(&self) -> Self::Internal {
let mut rng = self.rng_state;
let generation = self.random_initial(&mut rng);
(generation, 0)
}
fn name(&self) -> &str {
&self.name
}
}
pub mod rules {
pub const RULE_30: u8 = 30;
pub const RULE_90: u8 = 90;
pub const RULE_110: u8 = 110;
pub const RULE_184: u8 = 184;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_30() {
let ca = CellularAutomaton::one_dimensional("Rule 30", rules::RULE_30, 31);
let mut internal = ca.initial_internal();
let current = ParamValue::Float(0.0);
let value = ca.step(&mut internal, ¤t, 0.0, &NoAction);
assert!(value.as_f32().is_some());
}
}