cell groups in rules

This commit is contained in:
Crispy 2024-05-03 22:06:03 +02:00
parent 738e22bd4a
commit f23c06f5d7
2 changed files with 242 additions and 45 deletions

View file

@ -10,6 +10,7 @@ pub struct Cell(pub u16);
pub struct Dish { pub struct Dish {
pub chunk: Chunk, pub chunk: Chunk,
pub rules: Vec<Rule>, pub rules: Vec<Rule>,
pub cell_groups: Vec<Vec<Cell>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -33,7 +34,31 @@ pub struct Rule {
struct SubRule { struct SubRule {
width: usize, width: usize,
height: usize, height: usize,
contents: Vec<(Option<Cell>, Option<Cell>)>, contents: Vec<(RuleCellFrom, RuleCellTo)>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub enum RuleCellFrom {
/// matches anything
#[default]
Any,
/// matches one cell type
One(Cell),
/// matches anything defined in the group referenced by this index
Group(usize),
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub enum RuleCellTo {
/// don't modify this cell
#[default]
None,
/// set to this cell
One(Cell),
/// randomly choose from the group
GroupRandom(usize),
/// copy the cell from the corresponding input position
Copy(usize),
} }
impl SubRule { impl SubRule {
@ -41,36 +66,36 @@ impl SubRule {
Self { Self {
width: 1, width: 1,
height: 1, height: 1,
contents: vec![(None, None)], contents: vec![Default::default()],
} }
} }
fn get(&self, x: usize, y: usize) -> (Option<Cell>, Option<Cell>) { fn get(&self, x: usize, y: usize) -> (RuleCellFrom, RuleCellTo) {
if x >= self.width || y >= self.height { if x >= self.width || y >= self.height {
(None, None) Default::default()
} else { } else {
self.contents[x + self.width * y].clone() self.contents[x + self.width * y].clone()
} }
} }
fn get_mut(&mut self, x: usize, y: usize) -> &mut (Option<Cell>, Option<Cell>) { fn get_mut(&mut self, x: usize, y: usize) -> &mut (RuleCellFrom, RuleCellTo) {
assert!(x < self.width || y < self.height); assert!(x < self.width || y < self.height);
&mut self.contents[x + self.width * y] &mut self.contents[x + self.width * y]
} }
fn set_both(&mut self, x: usize, y: usize, cells: (Option<Cell>, Option<Cell>)) { fn set_both(&mut self, x: usize, y: usize, cells: (RuleCellFrom, RuleCellTo)) {
if x < self.width && y < self.height { if x < self.width && y < self.height {
self.contents[x + self.width * y] = cells; self.contents[x + self.width * y] = cells;
} }
} }
fn set_from(&mut self, x: usize, y: usize, cell: Option<Cell>) { fn set_from(&mut self, x: usize, y: usize, cell: RuleCellFrom) {
if x < self.width && y < self.height { if x < self.width && y < self.height {
self.contents[x + self.width * y].0 = cell; self.contents[x + self.width * y].0 = cell;
} }
} }
fn set_to(&mut self, x: usize, y: usize, cell: Option<Cell>) { fn set_to(&mut self, x: usize, y: usize, cell: RuleCellTo) {
if x < self.width && y < self.height { if x < self.width && y < self.height {
self.contents[x + self.width * y].1 = cell; self.contents[x + self.width * y].1 = cell;
} }
@ -99,20 +124,20 @@ impl Rule {
} }
} }
pub fn get(&self, x: usize, y: usize) -> (Option<Cell>, Option<Cell>) { pub fn get(&self, x: usize, y: usize) -> (RuleCellFrom, RuleCellTo) {
self.base.get(x, y) self.base.get(x, y)
} }
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut (Option<Cell>, Option<Cell>) { pub fn get_mut(&mut self, x: usize, y: usize) -> &mut (RuleCellFrom, RuleCellTo) {
self.base.get_mut(x, y) self.base.get_mut(x, y)
} }
pub fn set_from(&mut self, x: usize, y: usize, cell: Option<Cell>) { pub fn set_from(&mut self, x: usize, y: usize, cell: RuleCellFrom) {
self.base.set_from(x, y, cell); self.base.set_from(x, y, cell);
self.generate_variants(); self.generate_variants();
} }
pub fn set_to(&mut self, x: usize, y: usize, cell: Option<Cell>) { pub fn set_to(&mut self, x: usize, y: usize, cell: RuleCellTo) {
self.base.set_to(x, y, cell); self.base.set_to(x, y, cell);
self.generate_variants(); self.generate_variants();
} }
@ -133,7 +158,7 @@ impl Rule {
if new_width < 1 || new_height < 1 { if new_width < 1 || new_height < 1 {
return; return;
} }
let mut new_contents = vec![(None, None); new_width * new_height]; let mut new_contents = vec![Default::default(); new_width * new_height];
for nx in 0..new_width { for nx in 0..new_width {
let oldx = nx.wrapping_add_signed(dx); let oldx = nx.wrapping_add_signed(dx);
@ -256,8 +281,8 @@ impl Dish {
width: 1, width: 1,
height: 2, height: 2,
contents: vec![ contents: vec![
(Some(Cell(1)), Some(Cell(0))), (RuleCellFrom::One(Cell(1)), RuleCellTo::One(Cell(0))),
(Some(Cell(0)), Some(Cell(1))), (RuleCellFrom::One(Cell(0)), RuleCellTo::One(Cell(1))),
], ],
}, },
..Rule::new() ..Rule::new()
@ -268,10 +293,10 @@ impl Dish {
width: 2, width: 2,
height: 2, height: 2,
contents: vec![ contents: vec![
(Some(Cell(1)), Some(Cell(0))), (RuleCellFrom::One(Cell(1)), RuleCellTo::One(Cell(0))),
(None, None), (RuleCellFrom::Any, RuleCellTo::None),
(Some(Cell(1)), None), (RuleCellFrom::One(Cell(1)), RuleCellTo::None),
(Some(Cell(0)), Some(Cell(1))), (RuleCellFrom::One(Cell(0)), RuleCellTo::One(Cell(1))),
], ],
}, },
flip_h: true, flip_h: true,
@ -286,6 +311,7 @@ impl Dish {
Self { Self {
chunk: Chunk::new().fill_random(), chunk: Chunk::new().fill_random(),
rules: default_rules, rules: default_rules,
cell_groups: vec![vec![Cell(0), Cell(1)]],
} }
} }
@ -333,12 +359,32 @@ impl Dish {
let width = variant.width; let width = variant.width;
let height = variant.height; let height = variant.height;
let mut old_state = Vec::new();
for dy in 0..height {
for dx in 0..width {
old_state.push(self.get_cell(dx, dy).unwrap());
}
}
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 px = x + dx;
let y = y + dy; let py = y + dy;
if let Some(rule_cell) = variant.get(dx, dy).1 { match variant.get(dx, dy).1 {
self.set_cell(x, y, rule_cell.clone()); RuleCellTo::One(rule_cell) => {
self.set_cell(px, py, rule_cell.clone());
}
RuleCellTo::GroupRandom(group_id) => {
let group = &self.cell_groups[group_id];
let i = random::<usize>() % group.len();
let cell = group[i];
self.set_cell(px, py, cell);
}
RuleCellTo::Copy(index) => {
let cell = old_state[index];
self.set_cell(px, py, cell);
}
RuleCellTo::None => (),
} }
} }
} }
@ -349,11 +395,22 @@ impl Dish {
for dy in 0..subrule.height { for dy in 0..subrule.height {
let x = x + dx; let x = x + dx;
let y = y + dy; let y = y + dy;
if let Some(rule_cell) = subrule.get(dx, dy).0 { let Some(cell) = self.get_cell(x, y) else {
if self.get_cell(x, y) != Some(rule_cell) { return false;
};
match subrule.get(dx, dy).0 {
RuleCellFrom::One(rule_cell) => {
if cell != rule_cell {
return false; return false;
} }
} }
RuleCellFrom::Group(group_id) => {
if !self.cell_groups[group_id].contains(&cell) {
return false;
}
}
RuleCellFrom::Any => (),
}
} }
} }
true true

View file

@ -10,11 +10,15 @@ use eframe::{
epaint::Hsva, epaint::Hsva,
NativeOptions, NativeOptions,
}; };
use egui::{
menu::{SubMenu, SubMenuButton},
popup, Layout, PointerButton,
};
use native_dialog::FileDialog; use native_dialog::FileDialog;
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use petri::{Cell, Chunk, Dish, Rule, CHUNK_SIZE}; use petri::{Cell, Chunk, Dish, Rule, RuleCellFrom, RuleCellTo, CHUNK_SIZE};
use serde_json::{json, Value}; use serde_json::{json, Value};
fn main() { fn main() {
@ -45,7 +49,7 @@ impl UScope {
fn new(_cc: &eframe::CreationContext<'_>) -> Self { fn new(_cc: &eframe::CreationContext<'_>) -> Self {
Self { Self {
dish: Dish::new(), dish: Dish::new(),
speed: 250, speed: 500,
show_grid: false, show_grid: false,
brush: Cell(1), brush: Cell(1),
cell_types: vec![ cell_types: vec![
@ -134,12 +138,35 @@ impl eframe::App for UScope {
} }
ui.separator(); ui.separator();
ui.heading("Groups");
for group in &mut self.dish.cell_groups {
let (rect, _response) = ui.allocate_exact_size(Vec2::splat(CSIZE), Sense::click());
draw_group(ui, rect, group, &self.cell_types);
ui.menu_button("edit", |ui| {
for (i, celldata) in self.cell_types.iter().enumerate() {
let mut included = group.contains(&Cell(i as u16));
if ui.checkbox(&mut included, &celldata.name).changed() {
if included {
group.push(Cell(i as u16));
} else {
group.retain(|c| c.0 != i as u16);
}
}
}
});
// if response.clicked(){
// }
}
if ui.button("add group").clicked() {
self.dish.cell_groups.push(Vec::new());
}
ui.heading("Rules"); ui.heading("Rules");
ScrollArea::vertical().show(ui, |ui| { ScrollArea::vertical().show(ui, |ui| {
let mut to_remove = None; let mut to_remove = None;
for (i, rule) in self.dish.rules.iter_mut().enumerate() { for (i, rule) in self.dish.rules.iter_mut().enumerate() {
ui.separator(); ui.separator();
rule_editor(ui, rule, &self.cell_types); rule_editor(ui, rule, &self.cell_types, &self.dish.cell_groups);
if ui.button("delete").clicked() { if ui.button("delete").clicked() {
to_remove = Some(i); to_remove = Some(i);
} }
@ -189,7 +216,7 @@ fn paint_chunk(painter: Painter, chunk: &Chunk, cells: &[CellData], grid: bool)
const CSIZE: f32 = 24.; const CSIZE: f32 = 24.;
const OUTLINE: (f32, Color32) = (2., 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], groups: &[Vec<Cell>]) {
ui.checkbox(&mut rule.enabled, "enable rule"); ui.checkbox(&mut rule.enabled, "enable rule");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("flip"); ui.label("flip");
@ -227,8 +254,10 @@ 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 {
let (left, right) = rule.get_mut(x, 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_left =
let changed_right = rule_cell_edit(ui, to_cells_rect.min, right, x, y, cells); rule_cell_edit_from(ui, from_cells_rect.min, left, x, y, cells, groups);
let changed_right =
rule_cell_edit_to(ui, to_cells_rect.min, right, x, y, cells, groups);
if changed_left || changed_right { if changed_left || changed_right {
rule.generate_variants(); rule.generate_variants();
} }
@ -297,13 +326,14 @@ fn rule_editor(ui: &mut Ui, rule: &mut Rule, cells: &[CellData]) {
} }
} }
fn rule_cell_edit( fn rule_cell_edit_from(
ui: &mut Ui, ui: &mut Ui,
origin: Pos2, origin: Pos2,
rule: &mut Option<Cell>, rule: &mut RuleCellFrom,
x: usize, x: usize,
y: usize, y: usize,
cells: &[CellData], cells: &[CellData],
groups: &[Vec<Cell>],
) -> bool { ) -> bool {
let mut changed = false; let mut changed = false;
let rect = Rect::from_min_size( let rect = Rect::from_min_size(
@ -311,24 +341,134 @@ fn rule_cell_edit(
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 { let cycle_colors = aabb.clicked_by(PointerButton::Primary);
let switch_type = aabb.clicked_by(PointerButton::Secondary);
// draw
match rule {
RuleCellFrom::Any => (),
RuleCellFrom::One(cell) => {
let color = cells[cell.id()].color; let color = cells[cell.id()].color;
ui.painter() ui.painter()
.rect(rect.shrink(OUTLINE.0 / 2.), 0., color, OUTLINE); .rect(rect.shrink(OUTLINE.0 / 2.), 0., color, OUTLINE);
if aabb.clicked() { }
changed = true; RuleCellFrom::Group(group_id) => {
let group = &groups[*group_id];
draw_group(ui, rect, group, cells);
}
}
// update
if cycle_colors {
match rule {
RuleCellFrom::Any => (),
RuleCellFrom::One(cell) => {
cell.0 += 1; cell.0 += 1;
if cell.0 as usize == cells.len() { cell.0 %= cells.len() as u16;
*rule = None;
}
}
} else if aabb.clicked() {
*rule = Some(Cell(0));
changed = true; changed = true;
} }
RuleCellFrom::Group(group_id) => {
*group_id += 1;
*group_id %= groups.len();
changed = true;
}
}
}
if switch_type {
changed = true;
match rule {
RuleCellFrom::Any => {
*rule = RuleCellFrom::One(Cell(0));
}
RuleCellFrom::One(_) => {
*rule = RuleCellFrom::Group(0);
}
RuleCellFrom::Group(_) => {
*rule = RuleCellFrom::Any;
}
}
}
changed changed
} }
fn rule_cell_edit_to(
ui: &mut Ui,
origin: Pos2,
rule: &mut RuleCellTo,
x: usize,
y: usize,
cells: &[CellData],
groups: &[Vec<Cell>],
) -> 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());
let cycle_colors = aabb.clicked_by(PointerButton::Primary);
let switch_type = aabb.clicked_by(PointerButton::Secondary);
// draw
match rule {
RuleCellTo::None => (),
RuleCellTo::One(cell) => {
let color = cells[cell.id()].color;
ui.painter()
.rect(rect.shrink(OUTLINE.0 / 2.), 0., color, OUTLINE);
}
RuleCellTo::GroupRandom(group_id) => {
let group = &groups[*group_id];
draw_group(ui, rect, group, cells);
}
RuleCellTo::Copy(_) => todo!(),
}
if cycle_colors {
match rule {
RuleCellTo::None => (),
RuleCellTo::One(cell) => {
cell.0 += 1;
cell.0 %= cells.len() as u16;
changed = true;
}
RuleCellTo::GroupRandom(group_id) => {
*group_id += 1;
*group_id %= groups.len();
changed = true;
}
RuleCellTo::Copy(_) => todo!(),
}
}
if switch_type {
changed = true;
match rule {
RuleCellTo::None => {
*rule = RuleCellTo::One(Cell(0));
}
RuleCellTo::One(_) => {
*rule = RuleCellTo::GroupRandom(0);
}
RuleCellTo::GroupRandom(_) => {
*rule = RuleCellTo::None;
}
RuleCellTo::Copy(_) => todo!(),
}
}
changed
}
fn draw_group(ui: &mut Ui, rect: Rect, group: &[Cell], cells: &[CellData]) {
let group_size = group.len();
let radius_per_color = (CSIZE * 0.7) / (group_size as f32);
for (i, cell) in group.iter().enumerate() {
let color = cells[cell.id()].color;
let radius = radius_per_color * ((group_size - i) as f32);
ui.painter_at(rect)
.circle_filled(rect.center(), radius, color);
}
}
impl CellData { impl CellData {
fn new(name: &str, r: u8, g: u8, b: u8) -> Self { fn new(name: &str, r: u8, g: u8, b: u8) -> Self {
Self { Self {