life.rs

5.4 kB · rust · 185 lines

1#![allow(clippy::too_many_arguments)]23use crate::{code_of, Fault, Grid};4use mrlyrs::core::{json, Rng, Tensor};5use mrlyrs::life::{self, Boundary, Config, Source};6use mrlyrs::math::bang::Code;7use mrlyrs::math::two::Cell2d;8use wasm_bindgen::prelude::*;910fn grid(types: &[u8], width: usize, height: usize) -> Result<Cell2d, Fault> {11    if types.len() != width * height {12        return Err(Fault::new(13            "the grid bytes do not match width times height.",14        ));15    }16    Ok(Cell2d::new(Tensor::of(17        types.to_vec(),18        vec![height, width],19    )?)?)20}2122fn counts(list: &[u32]) -> Vec<usize> {23    list.iter().map(|&v| v as usize).collect()24}2526fn boundary(wrap: bool) -> Boundary {27    if wrap {28        Boundary::Wrap29    } else {30        Boundary::Constant31    }32}3334/// Advances a grid one generation under the birth and survive counts on the Moore neighborhood, wrapping the edges on request.35#[wasm_bindgen]36pub fn life_next(37    types: &[u8],38    width: usize,39    height: usize,40    birth: &[u32],41    survive: &[u32],42    wrap: bool,43) -> Result<Vec<u8>, Fault> {44    let mask = life::moore()?.types().clone();45    let next = life::next_grid(46        &grid(types, width, height)?,47        &counts(birth),48        &counts(survive),49        &mask,50        boundary(wrap),51    )?;52    Ok(next.types().bytes()?.to_vec())53}5455/// Runs a seed until it fixes, loops or times out: the fate, the generation count and the loop length, as JSON.56#[wasm_bindgen]57pub fn life_run(58    types: &[u8],59    width: usize,60    height: usize,61    birth: &[u32],62    survive: &[u32],63    wrap: bool,64    max_generations: usize,65) -> Result<String, Fault> {66    let config = Config {67        boundary: boundary(wrap),68        max_generations,69        ..Config::new(life::moore()?, counts(birth), counts(survive))70    };71    let run = life::animate(&grid(types, width, height)?, &config)?;72    Ok(json!({73        "fate": run.fate.name(),74        "count": run.count,75        "loop": run.loop_length,76    })77    .to_string())78}7980/// Lays down the values a named sequence gives up to the limit.81#[wasm_bindgen]82pub fn life_sequence(name: &str, limit: usize) -> Result<Vec<u32>, Fault> {83    let values = life::source::sequence(Source::parse(name)?, limit)?;84    Ok(values.iter().map(|&v| v as u32).collect())85}8687/// Draws a seeded grid whose sites fill with the given chance.88#[wasm_bindgen]89pub fn life_noise(width: usize, height: usize, density: f64, seed: u32) -> Vec<u8> {90    let mut rng = Rng::new(seed as u64);91    (0..width * height)92        .map(|_| u8::from(rng.chance(density)))93        .collect()94}9596/// Names every fixed sequence.97#[wasm_bindgen]98pub fn life_sequences() -> Vec<String> {99    Source::all().iter().map(|s| s.name()).collect()100}101102fn mask_grid(mask: &[u8], width: usize, height: usize) -> Result<Tensor, Fault> {103    if mask.len() != width * height {104        return Err(Fault::new(105            "the mask bytes do not match width times height.",106        ));107    }108    Ok(Tensor::of(mask.to_vec(), vec![height, width])?)109}110111/// Builds the base-2 design mask a code names at an odd side grown to the given Kronecker level, its centre popped; dimension 1 gives a grid of height one.112#[wasm_bindgen]113pub fn life_mask(dimension: usize, code: &str, number: usize, level: usize) -> Result<Grid, Fault> {114    let mask = life::design_mask(dimension, Code::from(code_of(code)?), number, level)?;115    let width = *mask.shape.last().expect("a mask carries a shape");116    Ok(Grid {117        width: width as u32,118        height: (mask.size() / width) as u32,119        types: mask.bytes()?.to_vec(),120    })121}122123/// Reads the index of the lattice a mask's offsets generate together with its centre, zero when they do not span; height one reads a line.124#[wasm_bindgen]125pub fn life_mask_index(mask: &[u8], width: usize, height: usize) -> Result<u32, Fault> {126    let grid = mask_grid(mask, width, height)?;127    let flat = if height == 1 {128        Tensor::of(mask.to_vec(), vec![width])?129    } else {130        grid131    };132    Ok(life::lattice_index(&flat) as u32)133}134135/// Advances a grid one generation under the birth and survive counts on the given mask, wrapping the edges on request; height one steps a line.136#[wasm_bindgen]137pub fn life_next_masked(138    types: &[u8],139    width: usize,140    height: usize,141    birth: &[u32],142    survive: &[u32],143    mask: &[u8],144    mask_width: usize,145    mask_height: usize,146    wrap: bool,147) -> Result<Vec<u8>, Fault> {148    let next = life::next_grid(149        &grid(types, width, height)?,150        &counts(birth),151        &counts(survive),152        &mask_grid(mask, mask_width, mask_height)?,153        boundary(wrap),154    )?;155    Ok(next.types().bytes()?.to_vec())156}157158/// Runs a seed on the given mask until it fixes, loops or times out: the fate, the generation count and the loop length, as JSON.159#[wasm_bindgen]160pub fn life_run_masked(161    types: &[u8],162    width: usize,163    height: usize,164    birth: &[u32],165    survive: &[u32],166    mask: &[u8],167    mask_width: usize,168    mask_height: usize,169    wrap: bool,170    max_generations: usize,171) -> Result<String, Fault> {172    let shape = Cell2d::new(mask_grid(mask, mask_width, mask_height)?)?;173    let config = Config {174        boundary: boundary(wrap),175        max_generations,176        ..Config::new(shape, counts(birth), counts(survive))177    };178    let run = life::animate(&grid(types, width, height)?, &config)?;179    Ok(json!({180        "fate": run.fate.name(),181        "count": run.count,182        "loop": run.loop_length,183    })184    .to_string())185}