animate.rs

3.2 kB · rust · 104 lines

1use super::models::{Config, Life};2use super::step::next_grid;3use super::Fate;4use crate::two::Cell2d;5use mrlycore::errors::Result;6use std::collections::HashMap;78fn prepare(seed: &Cell2d, config: &Config) -> Cell2d {9    let mut grid = seed.clone();10    if config.grid_size > 1 {11        grid = grid.tile(config.grid_size, config.grid_size);12    }13    if config.padding > 0 {14        grid = grid.pad(config.padding, 0);15    }16    grid17}1819/// Runs a seed under a config until it fixes, loops or times out, recording every generation.20pub fn animate(seed: &Cell2d, config: &Config) -> Result<Life> {21    let mask = config.mask.types().clone();22    let (birth, survive) = config.counts()?;23    let mut current = prepare(seed, config);24    let mut grids = vec![current.clone()];25    let mut history: HashMap<Vec<u8>, usize> = HashMap::new();26    history.insert(current.types().bytes().to_vec(), 0);27    let mut fate = Fate::Timeout;28    let mut loop_length = 0;29    for i in 1..config.max_generations {30        let next = next_grid(&current, &birth, &survive, &mask, config.boundary)?;31        if next.types() == current.types() {32            fate = if next.types().sum() == 0 {33                Fate::Dead34            } else {35                Fate::Alive36            };37            break;38        }39        let key = next.types().bytes().to_vec();40        if let Some(&seen_at) = history.get(&key) {41            loop_length = i - seen_at;42            fate = Fate::Loop;43            break;44        }45        history.insert(key, i);46        current = next;47        grids.push(current.clone());48    }49    let count = grids.len();50    Ok(Life {51        grids,52        fate,53        count,54        loop_length,55    })56}5758#[cfg(test)]59mod tests {60    use super::*;61    use crate::life::{moore, Boundary};62    use mrlycore::tensor::Tensor;63    fn conway(mask: Cell2d) -> Config {64        Config {65            boundary: Boundary::Constant,66            max_generations: 16,67            ..Config::new(mask, vec![3], vec![2, 3])68        }69    }70    #[test]71    fn blinker_is_a_loop_of_two() {72        let mut t = Tensor::new(vec![5, 5]);73        t.set(&[1, 2], 1);74        t.set(&[2, 2], 1);75        t.set(&[3, 2], 1);76        let life = animate(&Cell2d::new(t), &conway(moore())).unwrap();77        assert_eq!(life.fate, Fate::Loop);78        assert_eq!(life.loop_length, 2);79    }80    #[test]81    fn block_is_alive_still_life() {82        let mut t = Tensor::new(vec![4, 4]);83        for (y, x) in [(1, 1), (1, 2), (2, 1), (2, 2)] {84            t.set(&[y, x], 1);85        }86        let life = animate(&Cell2d::new(t), &conway(moore())).unwrap();87        assert_eq!(life.fate, Fate::Alive);88        assert_eq!(life.count, 1);89    }90    #[test]91    fn binarize_on_life_grids_stays_pointwise() {92        let mut t = Tensor::new(vec![5, 5]);93        t.set(&[1, 2], 1);94        t.set(&[2, 2], 1);95        t.set(&[3, 2], 1);96        let life = animate(&Cell2d::new(t), &conway(moore())).unwrap();97        for grid in &life.grids {98            let binarized = grid.clone().binarize(1);99            assert_eq!(binarized.types(), grid.types());100            let twice = binarized.clone().binarize(1);101            assert_eq!(twice.types(), binarized.types());102        }103    }104}