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}