implement rule flipping

This commit is contained in:
Crispy 2024-05-03 14:32:19 +02:00
parent 970eb402a2
commit 84e64fe719
2 changed files with 232 additions and 151 deletions

View file

@ -18,8 +18,8 @@ pub struct Chunk {
#[derive(Debug)] #[derive(Debug)]
pub struct Rule { pub struct Rule {
pub from: RulePattern, base: SubRule,
pub to: RulePattern, variants: Vec<SubRule>,
pub enabled: bool, pub enabled: bool,
// probability: u8 // probability: u8
pub flip_h: bool, pub flip_h: bool,
@ -27,6 +27,54 @@ pub struct Rule {
// rotate: // rotate:
} }
#[derive(Debug, Clone, PartialEq)]
struct SubRule {
width: usize,
height: usize,
contents: Vec<(Option<Cell>, Option<Cell>)>,
}
impl SubRule {
fn new() -> Self {
Self {
width: 1,
height: 1,
contents: vec![(None, None)],
}
}
fn get(&self, x: usize, y: usize) -> (Option<Cell>, Option<Cell>) {
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<Cell>, Option<Cell>) {
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<Cell>, Option<Cell>)) {
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<Cell>) {
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<Cell>) {
if x < self.width && y < self.height {
self.contents[x + self.width * y].1 = cell;
}
}
}
type ResizeParam = (isize, isize, isize, isize); type ResizeParam = (isize, isize, isize, isize);
impl Rule { impl Rule {
pub const EXTEND_LEFT: ResizeParam = (1, 0, -1, 0); pub const EXTEND_LEFT: ResizeParam = (1, 0, -1, 0);
@ -41,24 +89,109 @@ impl Rule {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
enabled: false, enabled: false,
from: RulePattern::new(), base: SubRule::new(),
to: RulePattern::new(), variants: Vec::new(),
flip_h: false, flip_h: false,
flip_v: false, flip_v: false,
} }
} }
pub fn get(&self, x: usize, y: usize) -> (Option<Cell>, Option<Cell>) {
self.base.get(x, y)
}
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut (Option<Cell>, Option<Cell>) {
self.base.get_mut(x, y)
}
pub fn set_from(&mut self, x: usize, y: usize, cell: Option<Cell>) {
self.base.set_from(x, y, cell);
self.generate_variants();
}
pub fn set_to(&mut self, x: usize, y: usize, cell: Option<Cell>) {
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) { pub fn resize(&mut self, params: ResizeParam) {
let (dw, dh, dx, dy) = params; let (dw, dh, dx, dy) = params;
self.from.resize(dw, dh, dx, dy);
self.to.resize(dw, dh, dx, dy); 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<SubRule>, 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
})
}
} }
} }
impl Chunk { impl Chunk {
fn new() -> Self { fn new() -> Self {
Self { Self {
contents: vec![[Cell::EMPTY; CHUNK_SIZE]; CHUNK_SIZE] contents: vec![[Cell(0); CHUNK_SIZE]; CHUNK_SIZE]
.into_boxed_slice() .into_boxed_slice()
.try_into() .try_into()
.unwrap(), .unwrap(),
@ -87,41 +220,43 @@ impl Chunk {
impl Dish { impl Dish {
pub fn new() -> Self { 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 { Self {
chunk: Chunk::new().fill_random(), 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))],
},
flip_h: false,
flip_v: false,
},
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))],
},
flip_h: true,
flip_v: false,
},
],
} }
} }
@ -147,31 +282,48 @@ impl Dish {
fn fire_rule(&mut self, rule_index: usize, x: usize, y: usize) { fn fire_rule(&mut self, rule_index: usize, x: usize, y: usize) {
let rule = &self.rules[rule_index]; let rule = &self.rules[rule_index];
let width = rule.to.width; // find matching variants
let height = rule.to.height; let mut matching_variants = Vec::new();
// check is match for (i, v) in rule.variants.iter().enumerate() {
for dx in 0..width { if self.subrule_matches(x, y, v) {
for dy in 0..height { matching_variants.push(i);
let x = x + dx;
let y = y + dy;
if let Some(rule_cell) = rule.from.get(dx, dy) {
if self.get_cell(x, y) != Some(rule_cell) {
return;
}
}
} }
} }
if matching_variants.is_empty() {
return;
}
let variant_index = random::<usize>() % matching_variants.len();
let variant = rule.variants[matching_variants[variant_index]].clone();
let width = variant.width;
let height = variant.height;
for dx in 0..width { for dx in 0..width {
for dy in 0..height { for dy in 0..height {
let x = x + dx; let x = x + dx;
let y = y + dy; let y = y + dy;
if let Some(rule_cell) = &self.rules[rule_index].to.get(dx, dy) { if let Some(rule_cell) = variant.get(dx, dy).1 {
self.set_cell(x, y, rule_cell.clone()); 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 self.get_cell(x, y) != Some(rule_cell) {
return false;
}
}
}
}
true
}
//todo isize //todo isize
pub fn get_cell(&self, x: usize, y: usize) -> Option<Cell> { pub fn get_cell(&self, x: usize, y: usize) -> Option<Cell> {
if x >= CHUNK_SIZE || y >= CHUNK_SIZE { if x >= CHUNK_SIZE || y >= CHUNK_SIZE {
@ -190,92 +342,8 @@ impl Dish {
} }
} }
#[derive(Debug)]
pub struct RulePattern {
width: usize,
height: usize,
contents: Vec<Option<Cell>>,
}
impl RulePattern {
pub fn new() -> Self {
Self {
width: 1,
height: 1,
contents: vec![None],
}
}
pub fn get(&self, x: usize, y: usize) -> Option<Cell> {
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<Cell>) {
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 { impl Cell {
pub const EMPTY: Self = Cell(0);
pub fn id(&self) -> usize { pub fn id(&self) -> usize {
self.0 as usize self.0 as usize
} }
} }
#[derive(Debug)]
enum Dir {
Pos,
Neg,
}
impl Dir {
fn sign(&self) -> isize {
match self {
Dir::Pos => 1,
Dir::Neg => -1,
}
}
}

View file

@ -3,7 +3,7 @@ use eframe::{
epaint::Hsva, epaint::Hsva,
NativeOptions, NativeOptions,
}; };
use petri::{Cell, Chunk, Dish, Rule, RulePattern, CHUNK_SIZE}; use petri::{Cell, Chunk, Dish, Rule, CHUNK_SIZE};
use rand::prelude::*; use rand::prelude::*;
fn main() { fn main() {
@ -122,14 +122,18 @@ fn paint_chunk(painter: Painter, chunk: &Chunk, cells: &[CellData]) {
} }
const CSIZE: f32 = 24.; const CSIZE: f32 = 24.;
const OUTLINE: (f32, Color32) = (3., Color32::GRAY); const OUTLINE: (f32, Color32) = (2., Color32::GRAY);
fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) { fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) {
ui.checkbox(&mut rule.enabled, "enable rule"); ui.checkbox(&mut rule.enabled, "enable rule");
ui.checkbox(&mut rule.flip_h, "flip H"); if ui.checkbox(&mut rule.flip_h, "flip H").changed() {
ui.checkbox(&mut rule.flip_v, "flip V"); rule.generate_variants();
}
if ui.checkbox(&mut rule.flip_v, "flip V").changed() {
rule.generate_variants();
}
let cells_y = rule.from.height(); let cells_y = rule.height();
let cells_x = rule.from.width(); let cells_x = rule.width();
let margin = 8.; let margin = 8.;
let patt_width = CSIZE * cells_x as f32; let patt_width = CSIZE * cells_x as f32;
let patt_height = CSIZE * cells_y as f32; let patt_height = CSIZE * cells_y as f32;
@ -150,8 +154,12 @@ fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) {
for x in 0..cells_x { for x in 0..cells_x {
for y in 0..cells_y { for y in 0..cells_y {
rule_cell_edit(ui, from_cells_rect.min, &mut rule.from, x, y, cells); let (left, right) = rule.get_mut(x, y);
rule_cell_edit(ui, to_cells_rect.min, &mut rule.to, x, y, cells); 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();
}
} }
} }
@ -220,28 +228,33 @@ fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) {
fn rule_cell_edit( fn rule_cell_edit(
ui: &mut Ui, ui: &mut Ui,
origin: Pos2, origin: Pos2,
rule: &mut RulePattern, rule: &mut Option<Cell>,
x: usize, x: usize,
y: usize, y: usize,
cells: &[CellData], cells: &[CellData],
) { ) -> bool {
let mut changed = false;
let rect = Rect::from_min_size( let rect = Rect::from_min_size(
origin + Vec2::from((x as f32, y as f32)) * CSIZE, origin + Vec2::from((x as f32, y as f32)) * CSIZE,
Vec2::splat(CSIZE), Vec2::splat(CSIZE),
); );
let aabb = ui.allocate_rect(rect, Sense::click()); let aabb = ui.allocate_rect(rect, Sense::click());
if let Some(cell) = rule.get_mut(x, y) { if let Some(cell) = rule {
let color = cells[cell.id()].color; let color = cells[cell.id()].color;
ui.painter().rect(rect, 2., color, OUTLINE); ui.painter()
.rect(rect.shrink(OUTLINE.0 / 2.), 0., color, OUTLINE);
if aabb.clicked() { if aabb.clicked() {
changed = true;
cell.0 += 1; cell.0 += 1;
if cell.0 as usize == cells.len() { if cell.0 as usize == cells.len() {
rule.set(x, y, None); *rule = None;
} }
} }
} else if aabb.clicked() { } else if aabb.clicked() {
rule.set(x, y, Some(Cell(0))); *rule = Some(Cell(0));
changed = true;
} }
changed
} }
impl CellData { impl CellData {