demo-life.rs
4.4 kB · rust · 140 lines
1use mrlycore::errors::Result;2use mrlycore::tensor::Tensor;3use mrlycore::MrlyError;4use mrlyfig::out::root;5use mrlyfig::{ink, save, Board, Ramp};6use mrlymath::life::{design_mask, next_grid, Boundary};7use mrlymath::two::Cell2d;8use std::path::PathBuf;910const NAME: &str = "demo-life";11const SIDE: usize = 96;12const STEPS: usize = 512;13const SEED: [(usize, usize); 5] = [(1, 0), (2, 0), (0, 1), (1, 1), (1, 2)];1415// DATA1617fn path() -> PathBuf {18 root()19 .join("files")20 .join("figures")21 .join("data")22 .join(format!("{NAME}.json"))23}2425fn write_data(first: &[i32], live: &[u8]) -> Result<PathBuf> {26 let file = path();27 let folder = file.parent().unwrap().to_path_buf();28 std::fs::create_dir_all(&folder)29 .map_err(|e| MrlyError::Value(format!("cannot make {folder:?}: {e}")))?;30 let one: Vec<String> = first.iter().map(|step| step.to_string()).collect();31 let two: Vec<String> = live.iter().map(|bit| bit.to_string()).collect();32 let text = format!(33 "{{\"first\":[{}],\"live\":[{}]}}",34 one.join(","),35 two.join(",")36 );37 std::fs::write(&file, text)38 .map_err(|e| MrlyError::Value(format!("cannot write {file:?}: {e}")))?;39 Ok(file)40}4142fn field(text: &str, name: &str) -> Vec<i32> {43 let head = format!("\"{name}\":[");44 let Some(start) = text.find(&head) else {45 return Vec::new();46 };47 let body = &text[start + head.len()..];48 let end = body.find(']').unwrap_or(0);49 body[..end]50 .split(',')51 .filter_map(|token| token.parse().ok())52 .collect()53}5455fn read_data() -> Result<(Vec<i32>, Vec<i32>)> {56 let file = path();57 let raw = std::fs::read_to_string(&file)58 .map_err(|e| MrlyError::Value(format!("cannot read {file:?}: {e}; run -- compute")))?;59 let text: String = raw.chars().filter(|c| !c.is_whitespace()).collect();60 Ok((field(&text, "first"), field(&text, "live")))61}6263// PRESS6465fn compute() -> Result<()> {66 let mask = design_mask(2, 7, 3, 1)?;67 assert_eq!(mask.shape, vec![3, 3]);68 assert_eq!((0..mask.size()).filter(|&i| mask.at(i) == 1).count(), 8);6970 let mut types = Tensor::new(vec![SIDE, SIDE]);71 let corner = SIDE / 2 - 1;72 for (x, y) in SEED {73 types.set(&[corner + y, corner + x], 1);74 }75 assert_eq!(types.bytes().iter().filter(|&&on| on == 1).count(), 5);7677 let mut cell = Cell2d::new(types);78 let mut first = vec![-1i32; SIDE * SIDE];79 for step in 0..=STEPS {80 let live = cell.types();81 for (slot, mark) in first.iter_mut().enumerate() {82 if *mark < 0 && live.at(slot) != 0 {83 *mark = step as i32;84 }85 }86 if step < STEPS {87 cell = next_grid(&cell, &[3], &[2, 3], &mask, Boundary::Constant)?;88 }89 }90 let ever = first.iter().filter(|mark| **mark >= 0).count();91 let standing: Vec<u8> = (0..SIDE * SIDE)92 .map(|slot| u8::from(cell.types().at(slot) != 0))93 .collect();94 let alive = standing.iter().filter(|&&bit| bit != 0).count();95 assert!(alive > 0);96 assert!(ever > alive);9798 let file = write_data(&first, &standing)?;99 println!("{NAME} {ever} ever {alive} live after {STEPS} -> {file:?}");100 Ok(())101}102103fn draw() -> Result<()> {104 let (first, live) = read_data()?;105 assert_eq!(first.len(), SIDE * SIDE);106 assert_eq!(live.len(), SIDE * SIDE);107 assert!(first.iter().any(|&mark| mark >= 0));108 assert!(live.iter().any(|&bit| bit != 0));109110 let mut board = Board::square();111 let area = board.frame(0.08);112 let scale = (area.w / SIDE as f64).floor().max(1.0);113 let block = SIDE as f64 * scale;114 let ox = ((board.width as f64 - block) / 2.0).round();115 let oy = ((board.height as f64 - block) / 2.0).round();116 let ramp = Ramp::tone(ink::green(), ink::line());117 for row in 0..SIDE {118 for col in 0..SIDE {119 let slot = row * SIDE + col;120 let x = ox + col as f64 * scale;121 let y = oy + row as f64 * scale;122 if first[slot] >= 0 {123 let t = first[slot] as f64 / STEPS as f64;124 board.rect(x, y, scale, scale, ramp.at(t));125 }126 if live[slot] != 0 {127 board.rect(x, y, scale, scale, ink::yellow());128 }129 }130 }131 save(NAME, &board)?;132 Ok(())133}134135fn main() -> Result<()> {136 if std::env::args().nth(1).as_deref() == Some("compute") {137 return compute();138 }139 draw()140}