From f358b465693cfbd64582a713e20e0fc34faf7513 Mon Sep 17 00:00:00 2001 From: Finn Bear <finnbearlabs@gmail.com> Date: Tue, 26 Apr 2022 21:01:51 -0700 Subject: [PATCH] Conway WIP. --- src/component/grid.rs | 122 +++++++++++++++++++++++++++-------- src/governor.rs | 15 +++++ src/main.rs | 10 +-- src/slide/s2_introduction.rs | 28 ++++++-- src/slide/s4_conway.rs | 88 ++++++++++++++++++++++++- 5 files changed, 224 insertions(+), 39 deletions(-) create mode 100644 src/governor.rs diff --git a/src/component/grid.rs b/src/component/grid.rs index fec1d98..229157e 100644 --- a/src/component/grid.rs +++ b/src/component/grid.rs @@ -1,13 +1,20 @@ -use eframe::egui::{Align2, Color32, Context, Pos2, Rect, Shape, Stroke, Ui, Vec2, Window}; +use eframe::egui::{ + Align2, Color32, Context, Pos2, Rect, Rounding, Shape, Stroke, Ui, Vec2, Window, +}; use eframe::emath; +use eframe::epaint::RectShape; +#[derive(Clone)] pub struct Grid { - name: &'static str, - offset_x: f32, - offset_y: f32, - size_cells: usize, - size_pixels: f32, - stroke: Color32, + pub name: &'static str, + pub offset_x: f32, + pub offset_y: f32, + pub size_cells: usize, + pub size_pixels: f32, + pub stroke: Color32, + pub stroke_width: f32, + pub background: Color32, + fill: Vec<Color32>, } impl Default for Grid { @@ -19,11 +26,44 @@ impl Default for Grid { size_cells: 16, size_pixels: 400.0, stroke: Color32::GRAY, + stroke_width: 1.0, + background: Color32::from_rgb(230, 230, 230), + fill: Vec::new(), } } } impl Grid { + fn index(&self, x: usize, y: usize) -> usize { + assert!(x < self.size_cells); + assert!(y < self.size_cells); + x + y * self.size_cells + } + + pub fn fill(&self, x: usize, y: usize) -> Color32 { + self.fill + .get(self.index(x, y)) + .cloned() + .unwrap_or(Color32::TRANSPARENT) + } + + pub fn set_fill(&mut self, x: usize, y: usize, fill: Color32) { + let idx = self.index(x, y); + loop { + match self.fill.get_mut(idx) { + Some(f) => { + *f = fill; + return; + } + None => self.fill.push(Color32::TRANSPARENT), + } + } + } + + pub fn clear_fill(&mut self) { + self.fill.clear(); + } + pub fn show(&self, ctx: &Context) { Window::new(self.name) .title_bar(false) @@ -37,27 +77,55 @@ impl Grid { rect, ); - for c in 0..=self.size_cells { - let coord = c as f32 / self.size_cells as f32; - // Horizontal. - ui.painter().add(Shape::LineSegment { - points: [ - to_screen * Pos2::new(0.0, coord), - to_screen * Pos2::new(1.0, coord), - ], - stroke: Stroke::new(1.0, self.stroke), - }); - // Vertical. - ui.painter().add(Shape::LineSegment { - points: [ - to_screen * Pos2::new(coord, 0.0), - to_screen * Pos2::new(coord, 1.0), - ], - stroke: Stroke::new(1.0, self.stroke), - }); + if self.background.a() > 0 { + ui.painter().add(Shape::Rect(RectShape::filled( + to_screen.transform_rect(Rect::from_x_y_ranges(0f32..=1f32, 0f32..=1f32)), + Rounding::none(), + self.background, + ))); } - for x in 0..=self.size_cells { - for y in 0..=self.size_cells {} + + let cell_width = 1.0 / self.size_cells as f32; + for y in 0..self.size_cells { + let y_coord = y as f32 / self.size_cells as f32; + for x in 0..self.size_cells { + let idx = self.index(x, y); + if let Some(fill) = self.fill.get(idx) { + if fill.a() > 0 { + let x_coord = x as f32 / self.size_cells as f32; + ui.painter().add(Shape::Rect(RectShape::filled( + to_screen.transform_rect(Rect::from_x_y_ranges( + x_coord..=x_coord + cell_width, + y_coord..=y_coord + cell_width, + )), + Rounding::none(), + *fill, + ))); + } + } + } + } + + if self.stroke.a() > 0 && self.stroke_width > 0.0 { + for c in 0..=self.size_cells { + let coord = c as f32 / self.size_cells as f32; + // Horizontal. + ui.painter().add(Shape::LineSegment { + points: [ + to_screen * Pos2::new(0.0, coord), + to_screen * Pos2::new(1.0, coord), + ], + stroke: Stroke::new(self.stroke_width, self.stroke), + }); + // Vertical. + ui.painter().add(Shape::LineSegment { + points: [ + to_screen * Pos2::new(coord, 0.0), + to_screen * Pos2::new(coord, 1.0), + ], + stroke: Stroke::new(self.stroke_width, self.stroke), + }); + } } }); } diff --git a/src/governor.rs b/src/governor.rs new file mode 100644 index 0000000..e9b8c62 --- /dev/null +++ b/src/governor.rs @@ -0,0 +1,15 @@ +#[derive(Default)] +pub struct Governor { + last: f64, +} + +impl Governor { + pub fn ready(&mut self, time: f64, limit: f64) -> bool { + if time > self.last + limit { + self.last = time; + true + } else { + false + } + } +} diff --git a/src/main.rs b/src/main.rs index 6acc4a6..e37c890 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![feature(derive_default_enum)] +#![feature(mixed_integer_ops)] pub mod component; +pub mod governor; pub mod image; pub mod slide; @@ -103,14 +105,14 @@ impl epi::App for Cartoon { }); */ - let advance = ctx.input().key_pressed(Key::Space) - || ctx.input().key_pressed(Key::ArrowRight) - || ctx.input().key_pressed(Key::D); + let force_advance = + ctx.input().key_pressed(Key::ArrowRight) || ctx.input().key_pressed(Key::D); + let advance = force_advance || ctx.input().key_pressed(Key::Space); let retreat = ctx.input().key_pressed(Key::ArrowLeft) || ctx.input().key_pressed(Key::A); if advance || retreat { self.slide_index = if advance { - if self.slides[self.slide_index].transition(ctx) { + if force_advance || self.slides[self.slide_index].transition(ctx) { let new = (self.slide_index + 1) % self.slides.len(); if new == 0 { self.slides = create_slides(); diff --git a/src/slide/s2_introduction.rs b/src/slide/s2_introduction.rs index 5fbfed3..ca13b11 100644 --- a/src/slide/s2_introduction.rs +++ b/src/slide/s2_introduction.rs @@ -3,19 +3,33 @@ use eframe::egui::{Context, Frame, Ui}; use eframe::{egui, epi}; #[derive(Default)] -pub struct Introduction {} +pub struct Introduction { + strengths: bool, +} impl Slide for Introduction { + fn transition(&mut self, ctx: &Context) -> bool { + let done = self.strengths; + self.strengths = true; + done + } + fn show(&mut self, ui: &mut Ui, _ctx: &Context) { Frame::none().margin(Margin::same(20.0)).show(ui, |ui| { - ui.heading("Introduction"); + ui.heading("Introduction to Artistic Algorithms"); ui.add_space(8.0); ui.label("Weaknesses"); - ui.small(" ✖ Blah"); - ui.small(" ✖ Blah"); - ui.add_space(10.0); - ui.label("Strengths"); - ui.small(" ✔ Blah"); + ui.small(" ✖ Social context"); + ui.small(" ✖ Human emotion"); + ui.small(" ✖ Political commentary"); + if self.strengths { + ui.add_space(10.0); + ui.label("Strengths"); + ui.small(" ✔ Following rules"); + ui.small(" ✔ Performing computation"); + ui.small(" ✔ Harnessing chaos and randomness"); + ui.small(" ✔ Guided exploration"); + } }); } } diff --git a/src/slide/s4_conway.rs b/src/slide/s4_conway.rs index a3bb41f..2e2babc 100644 --- a/src/slide/s4_conway.rs +++ b/src/slide/s4_conway.rs @@ -1,15 +1,101 @@ use crate::component::grid::Grid; +use crate::governor::Governor; use crate::Slide; +use eframe::egui::style::Margin; use eframe::egui::{Align2, Color32, Context, Frame, Pos2, Rect, Shape, Stroke, Ui, Vec2, Window}; use eframe::emath; +use rand::Rng; -#[derive(Default)] pub struct Conway { life: Grid, + governor: Governor, +} + +impl Default for Conway { + fn default() -> Self { + let mut life = Grid::default(); + + for y in 0..life.size_cells { + for x in 0..life.size_cells { + if rand::thread_rng().gen_bool(0.5) { + life.set_fill(x, y, Color32::BLACK); + } + } + } + + /* + life.clear_fill(); + life.set_fill(5, 5, Color32::BLACK); + life.set_fill(6, 5, Color32::BLACK); + life.set_fill(7, 5, Color32::BLACK); + */ + + Self { + life, + governor: Governor::default(), + } + } } impl Slide for Conway { fn show(&mut self, ui: &mut Ui, ctx: &Context) { + Frame::none().margin(Margin::same(20.0)).show(ui, |ui| { + ui.vertical_centered(|ui| { + ui.heading("Cellular Automata"); + }); + }); + + ctx.request_repaint(); + if self.governor.ready(ctx.input().time, 0.2) { + self.life = conway(&self.life); + } self.life.show(ctx); } } + +fn conway(grid: &Grid) -> Grid { + let mut new = grid.clone(); + new.clear_fill(); + + for cy in 0..grid.size_cells { + for cx in 0..grid.size_cells { + let mut neighbors = 0; + for dy in -1..=1 { + for dx in -1..=1 { + let (x, y) = match (cx.checked_add_signed(dx), cy.checked_add_signed(dy)) { + (Some(x), Some(y)) if x < grid.size_cells && y < grid.size_cells => (x, y), + _ => continue, + }; + if x == cx && y == cy { + // Don't consider self. + continue; + } + if grid.fill(x, y).a() > 0 { + neighbors += 1; + } + } + } + + let mut alive = grid.fill(cx, cy).a() > 0; + + alive = match (alive, neighbors) { + (false, 3) => true, + (false, _) => false, + (true, 2) | (true, 3) => true, + (true, _) => false, + }; + + new.set_fill( + cx, + cy, + if alive { + Color32::BLACK + } else { + Color32::TRANSPARENT + }, + ); + } + } + + new +} -- GitLab