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}