demo-chladni.rs

3.6 kB · rust · 128 lines

1use mrlycore::errors::Result;2use mrlycore::Rng;3use mrlyfig::{ink, save, Board};4use mrlymath::life::design_mask;5use mrlynum::fft::{convolve_with, embed_kernel, transform};67const SIZE: usize = 256;8const STEPS: usize = 64;9const SEED: u64 = 1729;10const DENSITY: f64 = 0.48;11const CELL: f64 = 3.0;12const STAMP: f64 = 6.0;13const CODE: u128 = 7;14const BASE: usize = 3;15const LEVEL: usize = 3;16const SPAN: usize = 27;17const BUDGET: usize = 512;18const BIRTH: (f64, f64) = (0.283, 0.375);19const SURVIVE: (f64, f64) = (0.283, 0.483);20const BORN: (usize, usize) = (145, 192);21const KEPT: (usize, usize) = (145, 247);2223fn soup(seed: u64, density: f64) -> Vec<u8> {24    let mut rng = Rng::new(seed);25    (0..SIZE * SIZE)26        .map(|_| u8::from(rng.chance(density)))27        .collect()28}2930fn window(lo: f64, hi: f64) -> Vec<bool> {31    (0..=BUDGET)32        .map(|k| {33            let share = k as f64 / BUDGET as f64;34            lo <= share && share <= hi35        })36        .collect()37}3839fn counts(table: &[bool]) -> (usize, usize) {40    let on: Vec<usize> = (0..=BUDGET).filter(|&k| table[k]).collect();41    (on[0], on[on.len() - 1])42}4344struct Rule {45    kernel_re: Vec<f64>,46    kernel_im: Vec<f64>,47    born: Vec<bool>,48    kept: Vec<bool>,49    field: Vec<f64>,50}5152impl Rule {53    fn new(mask: &[u8]) -> Rule {54        let (kernel_re, kernel_im) = transform(&embed_kernel(mask, SPAN, SIZE), SIZE);55        Rule {56            kernel_re,57            kernel_im,58            born: window(BIRTH.0, BIRTH.1),59            kept: window(SURVIVE.0, SURVIVE.1),60            field: vec![0.0; SIZE * SIZE],61        }62    }63    fn step(&mut self, types: &mut [u8]) {64        for (slot, &t) in self.field.iter_mut().zip(types.iter()) {65            *slot = f64::from(t.min(1));66        }67        let sums = convolve_with(&self.field, &self.kernel_re, &self.kernel_im, SIZE);68        for (slot, &sum) in types.iter_mut().zip(&sums) {69            let n = (sum.round().max(0.0) as usize).min(BUDGET);70            let lives = if *slot != 0 {71                self.kept[n]72            } else {73                self.born[n]74            };75            *slot = u8::from(lives);76        }77    }78}7980fn main() -> Result<()> {81    let mut board = Board::square();82    let area = board.frame(0.04);83    let mask = design_mask(2, CODE, BASE, LEVEL)?;84    assert_eq!(mask.shape, vec![SPAN, SPAN]);85    assert_eq!(BASE.pow(LEVEL as u32), SPAN);86    assert_eq!(mask.sum() as usize, BUDGET);87    assert_eq!(mask.get(&[SPAN / 2, SPAN / 2]), 0);88    let mut rule = Rule::new(mask.bytes());89    assert_eq!(counts(&rule.born), BORN);90    assert_eq!(counts(&rule.kept), KEPT);9192    let mut types = soup(SEED, DENSITY);93    for _ in 0..STEPS {94        rule.step(&mut types);95    }96    let live = types.iter().filter(|&&t| t != 0).count();97    assert!(live > 0);9899    let block = SIZE as f64 * CELL;100    let ox = (area.x + area.w - block).round();101    let oy = (area.y + area.h - block).round();102    for row in 0..SIZE {103        for col in 0..SIZE {104            if types[row * SIZE + col] != 0 {105                let x = ox + col as f64 * CELL;106                let y = oy + row as f64 * CELL;107                board.rect(x, y, CELL, CELL, ink::blue());108            }109        }110    }111112    let sx = area.x.round();113    let sy = area.y.round();114    let mut stamped = 0usize;115    for row in 0..SPAN {116        for col in 0..SPAN {117            if mask.get(&[row, col]) == 1 {118                let x = sx + col as f64 * STAMP;119                let y = sy + row as f64 * STAMP;120                board.rect(x, y, STAMP, STAMP, ink::yellow());121                stamped += 1;122            }123        }124    }125    assert_eq!(stamped, BUDGET);126    save("demo-chladni", &board)?;127    Ok(())128}