life.rs

5.3 kB · rust · 181 lines

1#![allow(clippy::too_many_arguments)]23use crate::{code_of, Fault, Grid};4use mrlycore::{json, Rng, Tensor};5use mrlymath::life::{self, Boundary, Config, Sequence};6use mrlymath::two::Cell2d;7use wasm_bindgen::prelude::*;89fn grid(types: &[u8], width: usize, height: usize) -> Result<Cell2d, Fault> {10    if types.len() != width * height {11        return Err(Fault::new(12            "the grid bytes do not match width times height.",13        ));14    }15    Ok(Cell2d::new(Tensor::of(types.to_vec(), vec![height, width])))16}1718fn counts(list: &[u32]) -> Vec<usize> {19    list.iter().map(|&v| v as usize).collect()20}2122fn boundary(wrap: bool) -> Boundary {23    if wrap {24        Boundary::Wrap25    } else {26        Boundary::Constant27    }28}2930/// Advances a grid one generation under the birth and survive counts on the Moore neighborhood, wrapping the edges on request.31#[wasm_bindgen]32pub fn life_next(33    types: &[u8],34    width: usize,35    height: usize,36    birth: &[u32],37    survive: &[u32],38    wrap: bool,39) -> Result<Vec<u8>, Fault> {40    let mask = life::moore().types().clone();41    let next = life::next_grid(42        &grid(types, width, height)?,43        &counts(birth),44        &counts(survive),45        &mask,46        boundary(wrap),47    )?;48    Ok(next.types().bytes().to_vec())49}5051/// Runs a seed until it fixes, loops or times out: the fate, the generation count and the loop length, as JSON.52#[wasm_bindgen]53pub fn life_run(54    types: &[u8],55    width: usize,56    height: usize,57    birth: &[u32],58    survive: &[u32],59    wrap: bool,60    max_generations: usize,61) -> Result<String, Fault> {62    let config = Config {63        boundary: boundary(wrap),64        max_generations,65        ..Config::new(life::moore(), counts(birth), counts(survive))66    };67    let run = life::animate(&grid(types, width, height)?, &config)?;68    Ok(json!({69        "fate": run.fate.name(),70        "count": run.count,71        "loop": run.loop_length,72    })73    .to_string())74}7576/// Lays down the values a named sequence gives up to the limit.77#[wasm_bindgen]78pub fn life_sequence(name: &str, limit: usize) -> Result<Vec<u32>, Fault> {79    let values = life::sequence::sequence(Sequence::parse(name)?, limit)?;80    Ok(values.iter().map(|&v| v as u32).collect())81}8283/// Draws a seeded grid whose sites fill with the given chance.84#[wasm_bindgen]85pub fn life_noise(width: usize, height: usize, density: f64, seed: u32) -> Vec<u8> {86    let mut rng = Rng::new(seed as u64);87    (0..width * height)88        .map(|_| u8::from(rng.chance(density)))89        .collect()90}9192/// Names every fixed sequence.93#[wasm_bindgen]94pub fn life_sequences() -> Vec<String> {95    Sequence::all().iter().map(|s| s.name()).collect()96}9798fn mask_grid(mask: &[u8], width: usize, height: usize) -> Result<Tensor, Fault> {99    if mask.len() != width * height {100        return Err(Fault::new(101            "the mask bytes do not match width times height.",102        ));103    }104    Ok(Tensor::of(mask.to_vec(), vec![height, width]))105}106107/// 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.108#[wasm_bindgen]109pub fn life_mask(dimension: usize, code: &str, number: usize, level: usize) -> Result<Grid, Fault> {110    let mask = life::design_mask(dimension, code_of(code)?, number, level)?;111    let width = *mask.shape.last().expect("a mask carries a shape");112    Ok(Grid {113        width: width as u32,114        height: (mask.size() / width) as u32,115        types: mask.bytes().to_vec(),116    })117}118119/// 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.120#[wasm_bindgen]121pub fn life_mask_index(mask: &[u8], width: usize, height: usize) -> Result<u32, Fault> {122    let grid = mask_grid(mask, width, height)?;123    let flat = if height == 1 {124        Tensor::of(mask.to_vec(), vec![width])125    } else {126        grid127    };128    Ok(life::lattice_index(&flat) as u32)129}130131/// 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.132#[wasm_bindgen]133pub fn life_next_masked(134    types: &[u8],135    width: usize,136    height: usize,137    birth: &[u32],138    survive: &[u32],139    mask: &[u8],140    mask_width: usize,141    mask_height: usize,142    wrap: bool,143) -> Result<Vec<u8>, Fault> {144    let next = life::next_grid(145        &grid(types, width, height)?,146        &counts(birth),147        &counts(survive),148        &mask_grid(mask, mask_width, mask_height)?,149        boundary(wrap),150    )?;151    Ok(next.types().bytes().to_vec())152}153154/// 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.155#[wasm_bindgen]156pub fn life_run_masked(157    types: &[u8],158    width: usize,159    height: usize,160    birth: &[u32],161    survive: &[u32],162    mask: &[u8],163    mask_width: usize,164    mask_height: usize,165    wrap: bool,166    max_generations: usize,167) -> Result<String, Fault> {168    let shape = Cell2d::new(mask_grid(mask, mask_width, mask_height)?);169    let config = Config {170        boundary: boundary(wrap),171        max_generations,172        ..Config::new(shape, counts(birth), counts(survive))173    };174    let run = life::animate(&grid(types, width, height)?, &config)?;175    Ok(json!({176        "fate": run.fate.name(),177        "count": run.count,178        "loop": run.loop_length,179    })180    .to_string())181}