diff --git a/petri/src/lib.rs b/petri/src/lib.rs index 4076dc3..168d7b3 100644 --- a/petri/src/lib.rs +++ b/petri/src/lib.rs @@ -18,61 +18,12 @@ pub struct Chunk { #[derive(Debug)] pub struct Rule { - base: SubRule, - variants: Vec, + pub from: RulePattern, + pub to: RulePattern, pub enabled: bool, // probability: u8 - pub flip_h: bool, - pub flip_v: bool, - pub rotate: bool, -} - -#[derive(Debug, Clone, PartialEq)] -struct SubRule { - width: usize, - height: usize, - contents: Vec<(Option, Option)>, -} - -impl SubRule { - fn new() -> Self { - Self { - width: 1, - height: 1, - contents: vec![(None, None)], - } - } - - fn get(&self, x: usize, y: usize) -> (Option, Option) { - if x >= self.width || y >= self.height { - (None, None) - } else { - self.contents[x + self.width * y].clone() - } - } - - fn get_mut(&mut self, x: usize, y: usize) -> &mut (Option, Option) { - assert!(x < self.width || y < self.height); - &mut self.contents[x + self.width * y] - } - - fn set_both(&mut self, x: usize, y: usize, cells: (Option, Option)) { - if x < self.width && y < self.height { - self.contents[x + self.width * y] = cells; - } - } - - fn set_from(&mut self, x: usize, y: usize, cell: Option) { - if x < self.width && y < self.height { - self.contents[x + self.width * y].0 = cell; - } - } - - fn set_to(&mut self, x: usize, y: usize, cell: Option) { - if x < self.width && y < self.height { - self.contents[x + self.width * y].1 = cell; - } - } + // flip: + // rotate: } type ResizeParam = (isize, isize, isize, isize); @@ -89,136 +40,22 @@ impl Rule { pub fn new() -> Self { Self { enabled: false, - base: SubRule::new(), - variants: Vec::new(), - flip_h: false, - flip_v: false, - rotate: false, + from: RulePattern::new(), + to: RulePattern::new(), } } - pub fn get(&self, x: usize, y: usize) -> (Option, Option) { - self.base.get(x, y) - } - - pub fn get_mut(&mut self, x: usize, y: usize) -> &mut (Option, Option) { - self.base.get_mut(x, y) - } - - pub fn set_from(&mut self, x: usize, y: usize, cell: Option) { - self.base.set_from(x, y, cell); - self.generate_variants(); - } - - pub fn set_to(&mut self, x: usize, y: usize, cell: Option) { - self.base.set_to(x, y, cell); - self.generate_variants(); - } - - pub fn height(&self) -> usize { - self.base.height - } - - pub fn width(&self) -> usize { - self.base.width - } - pub fn resize(&mut self, params: ResizeParam) { let (dw, dh, dx, dy) = params; - - let new_width = self.base.width.saturating_add_signed(dw); - let new_height = self.base.height.saturating_add_signed(dh); - if new_width < 1 || new_height < 1 { - return; - } - let mut new_contents = vec![(None, None); new_width * new_height]; - - for nx in 0..new_width { - let oldx = nx.wrapping_add_signed(dx); - for ny in 0..new_height { - let oldy = ny.wrapping_add_signed(dy); - new_contents[nx + new_width * ny] = self.get(oldx, oldy); - } - } - - self.base.contents = new_contents; - self.base.height = new_height; - self.base.width = new_width; - self.generate_variants(); - } - - pub fn generate_variants(&mut self) { - self.variants.clear(); - self.variants.push(self.base.clone()); - - fn transform_variants(variants: &mut Vec, f: fn(&SubRule) -> SubRule) { - let mut new = Vec::new(); - for v in variants.iter() { - let new_variant = f(v); - if !variants.contains(&new_variant) { - new.push(new_variant); - } - } - variants.extend_from_slice(&new); - } - - if self.flip_h { - transform_variants(&mut self.variants, |b| { - let mut new = b.clone(); - for y in 0..new.height { - for x in 0..new.width { - let old = b.get(new.width - x - 1, y); - new.set_both(x, y, old); - } - } - new - }); - } - if self.flip_v { - transform_variants(&mut self.variants, |b| { - let mut new = b.clone(); - for y in 0..new.height { - for x in 0..new.width { - let old = b.get(x, new.height - y - 1); - new.set_both(x, y, old); - } - } - new - }); - } - if self.rotate { - // 180° rotations (same as flipping x and y) - transform_variants(&mut self.variants, |b| { - let mut new = b.clone(); - for y in 0..new.height { - for x in 0..new.width { - let old = b.get(new.width - x - 1, new.height - y - 1); - new.set_both(x, y, old); - } - } - new - }); - // 90° rotations - transform_variants(&mut self.variants, |b| { - let mut new = b.clone(); - new.height = b.width; - new.width = b.height; - for y in 0..new.height { - for x in 0..new.width { - let old = b.get(y, x); - new.set_both(x, y, old); - } - } - new - }) - } + self.from.resize(dw, dh, dx, dy); + self.to.resize(dw, dh, dx, dy); } } impl Chunk { fn new() -> Self { Self { - contents: vec![[Cell(0); CHUNK_SIZE]; CHUNK_SIZE] + contents: vec![[Cell::EMPTY; CHUNK_SIZE]; CHUNK_SIZE] .into_boxed_slice() .try_into() .unwrap(), @@ -247,43 +84,50 @@ impl Chunk { impl Dish { pub fn new() -> Self { - let mut default_rules = vec![ - Rule { - enabled: true, - base: SubRule { - width: 1, - height: 2, - contents: vec![ - (Some(Cell(1)), Some(Cell(0))), - (Some(Cell(0)), Some(Cell(1))), - ], - }, - ..Rule::new() - }, - Rule { - enabled: true, - base: SubRule { - width: 2, - height: 2, - contents: vec![ - (Some(Cell(1)), Some(Cell(0))), - (None, None), - (Some(Cell(1)), None), - (Some(Cell(0)), Some(Cell(1))), - ], - }, - flip_h: true, - ..Rule::new() - }, - ]; - - for rule in &mut default_rules { - rule.generate_variants() - } - Self { chunk: Chunk::new().fill_random(), - rules: default_rules, + + rules: vec![ + Rule { + enabled: true, + from: RulePattern { + width: 1, + height: 2, + contents: vec![Some(Cell(1)), Some(Cell(0))], + }, + to: RulePattern { + width: 1, + height: 2, + contents: vec![Some(Cell(0)), Some(Cell(1))], + }, + }, + Rule { + enabled: true, + from: RulePattern { + width: 2, + height: 2, + contents: vec![Some(Cell(1)), None, Some(Cell(1)), Some(Cell(0))], + }, + to: RulePattern { + width: 2, + height: 2, + contents: vec![Some(Cell(0)), None, Some(Cell(1)), Some(Cell(1))], + }, + }, + Rule { + enabled: true, + from: RulePattern { + width: 2, + height: 2, + contents: vec![None, Some(Cell(1)), Some(Cell(0)), Some(Cell(1))], + }, + to: RulePattern { + width: 2, + height: 2, + contents: vec![None, Some(Cell(0)), Some(Cell(1)), Some(Cell(1))], + }, + }, + ], } } @@ -309,46 +153,29 @@ impl Dish { fn fire_rule(&mut self, rule_index: usize, x: usize, y: usize) { let rule = &self.rules[rule_index]; - // find matching variants - let mut matching_variants = Vec::new(); - for (i, v) in rule.variants.iter().enumerate() { - if self.subrule_matches(x, y, v) { - matching_variants.push(i); - } - } - if matching_variants.is_empty() { - return; - } - - let variant_index = random::() % matching_variants.len(); - let variant = rule.variants[matching_variants[variant_index]].clone(); - - let width = variant.width; - let height = variant.height; + let width = rule.to.width; + let height = rule.to.height; + // check is match for dx in 0..width { for dy in 0..height { let x = x + dx; let y = y + dy; - if let Some(rule_cell) = variant.get(dx, dy).1 { - self.set_cell(x, y, rule_cell.clone()); - } - } - } - } - - fn subrule_matches(&self, x: usize, y: usize, subrule: &SubRule) -> bool { - for dx in 0..subrule.width { - for dy in 0..subrule.height { - let x = x + dx; - let y = y + dy; - if let Some(rule_cell) = subrule.get(dx, dy).0 { + if let Some(rule_cell) = rule.from.get(dx, dy) { if self.get_cell(x, y) != Some(rule_cell) { - return false; + return; } } } } - true + for dx in 0..width { + for dy in 0..height { + let x = x + dx; + let y = y + dy; + if let Some(rule_cell) = &self.rules[rule_index].to.get(dx, dy) { + self.set_cell(x, y, rule_cell.clone()); + } + } + } } //todo isize @@ -369,7 +196,76 @@ impl Dish { } } +#[derive(Debug)] +pub struct RulePattern { + width: usize, + height: usize, + contents: Vec>, +} + +impl RulePattern { + pub fn new() -> Self { + Self { + width: 1, + height: 1, + contents: vec![None], + } + } + + pub fn get(&self, x: usize, y: usize) -> Option { + if x >= self.width || y >= self.height { + None + } else { + self.contents[x + self.width * y].clone() + } + } + + pub fn get_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> { + if x >= self.width || y >= self.height { + None + } else { + self.contents[x + self.width * y].as_mut() + } + } + + pub fn set(&mut self, x: usize, y: usize, cell: Option) { + if x < self.width && y < self.height { + self.contents[x + self.width * y] = cell + } + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn width(&self) -> usize { + self.width + } + + fn resize(&mut self, dw: isize, dh: isize, dx: isize, dy: isize) { + let new_width = self.width.saturating_add_signed(dw); + let new_height = self.height.saturating_add_signed(dh); + if new_width < 1 || new_height < 1 { + return; + } + let mut new_contents = vec![None; new_width * new_height]; + + for nx in 0..new_width { + let oldx = nx.wrapping_add_signed(dx); + for ny in 0..new_height { + let oldy = ny.wrapping_add_signed(dy); + new_contents[nx + new_width * ny] = self.get(oldx, oldy); + } + } + + self.contents = new_contents; + self.height = new_height; + self.width = new_width; + } +} + impl Cell { + pub const EMPTY: Self = Cell(0); pub fn id(&self) -> usize { self.0 as usize } diff --git a/uscope/src/main.rs b/uscope/src/main.rs index 0e5f614..e2a10b2 100644 --- a/uscope/src/main.rs +++ b/uscope/src/main.rs @@ -3,7 +3,7 @@ use eframe::{ epaint::Hsva, NativeOptions, }; -use petri::{Cell, Chunk, Dish, Rule, CHUNK_SIZE}; +use petri::{Cell, Chunk, Dish, Rule, RulePattern, CHUNK_SIZE}; use rand::prelude::*; fn main() { @@ -122,21 +122,12 @@ fn paint_chunk(painter: Painter, chunk: &Chunk, cells: &[CellData]) { } const CSIZE: f32 = 24.; -const OUTLINE: (f32, Color32) = (2., Color32::GRAY); +const OUTLINE: (f32, Color32) = (3., Color32::GRAY); fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) { ui.checkbox(&mut rule.enabled, "enable rule"); - if ui.checkbox(&mut rule.flip_h, "flip H").changed() { - rule.generate_variants(); - } - if ui.checkbox(&mut rule.flip_v, "flip V").changed() { - rule.generate_variants(); - } - if ui.checkbox(&mut rule.rotate, "rotate").changed() { - rule.generate_variants(); - } - let cells_y = rule.height(); - let cells_x = rule.width(); + let cells_y = rule.from.height(); + let cells_x = rule.from.width(); let margin = 8.; let patt_width = CSIZE * cells_x as f32; let patt_height = CSIZE * cells_y as f32; @@ -157,12 +148,8 @@ fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) { for x in 0..cells_x { for y in 0..cells_y { - let (left, right) = rule.get_mut(x, y); - let changed_left = rule_cell_edit(ui, from_cells_rect.min, left, x, y, cells); - let changed_right = rule_cell_edit(ui, to_cells_rect.min, right, x, y, cells); - if changed_left || changed_right { - rule.generate_variants(); - } + rule_cell_edit(ui, from_cells_rect.min, &mut rule.from, x, y, cells); + rule_cell_edit(ui, to_cells_rect.min, &mut rule.to, x, y, cells); } } @@ -231,33 +218,28 @@ fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) { fn rule_cell_edit( ui: &mut Ui, origin: Pos2, - rule: &mut Option, + rule: &mut RulePattern, x: usize, y: usize, cells: &[CellData], -) -> bool { - let mut changed = false; +) { let rect = Rect::from_min_size( origin + Vec2::from((x as f32, y as f32)) * CSIZE, Vec2::splat(CSIZE), ); let aabb = ui.allocate_rect(rect, Sense::click()); - if let Some(cell) = rule { + if let Some(cell) = rule.get_mut(x, y) { let color = cells[cell.id()].color; - ui.painter() - .rect(rect.shrink(OUTLINE.0 / 2.), 0., color, OUTLINE); + ui.painter().rect(rect, 2., color, OUTLINE); if aabb.clicked() { - changed = true; cell.0 += 1; if cell.0 as usize == cells.len() { - *rule = None; + rule.set(x, y, None); } } } else if aabb.clicked() { - *rule = Some(Cell(0)); - changed = true; + rule.set(x, y, Some(Cell(0))); } - changed } impl CellData {