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}