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}