aboutsummaryrefslogtreecommitdiff
path: root/2022/src/day14.rs
diff options
context:
space:
mode:
Diffstat (limited to '2022/src/day14.rs')
-rw-r--r--2022/src/day14.rs230
1 files changed, 230 insertions, 0 deletions
diff --git a/2022/src/day14.rs b/2022/src/day14.rs
new file mode 100644
index 0000000..903f919
--- /dev/null
+++ b/2022/src/day14.rs
@@ -0,0 +1,230 @@
+mod utils;
+use std::{
+ cmp::{max, min},
+ collections::HashSet,
+ ops::Add,
+ time::Duration,
+};
+
+use egui::{Color32, Rect, Sense, Stroke};
+use nom::{bytes::complete::tag, character::complete::u32, multi::separated_list0, IResult};
+
+fn main() {
+ console_error_panic_hook::set_once();
+
+ let input = include_str!("../fake_inputs/day14");
+
+ let options = eframe::WebOptions::default();
+ wasm_bindgen_futures::spawn_local(async {
+ eframe::start_web("canvas", options, Box::new(|_cc| Box::new(App::new(input))))
+ .await
+ .expect("unable to start eframe");
+ });
+}
+
+struct App {
+ structures: Vec<Structure>,
+ sand_grains: HashSet<Pos>,
+ speed: usize,
+ finished: bool,
+}
+
+impl App {
+ fn new(input: &str) -> Self {
+ let mut structures = separated_list0(tag("\n"), Structure::parse)(input)
+ .unwrap()
+ .1;
+ let floor_y = structures
+ .iter()
+ .map(|s| s.points.iter().map(|p| p.1).max().unwrap())
+ .max()
+ .unwrap()
+ + 2;
+ structures.push(Structure::new(vec![
+ Pos(isize::MIN, floor_y),
+ Pos(isize::MAX, floor_y),
+ ]));
+
+ App {
+ structures,
+ sand_grains: HashSet::new(),
+ speed: 0,
+ finished: false,
+ }
+ }
+
+ fn reset(&mut self) {
+ self.sand_grains.clear();
+ }
+
+ fn step(&mut self) {
+ let mut pos = Pos(500, 0);
+ if self.sand_grains.contains(&pos) {
+ self.finished = true;
+ return;
+ }
+ loop {
+ // TODO
+ pos = if !self.inhabited(pos + Pos(0, 1)) {
+ pos + Pos(0, 1)
+ } else if !self.inhabited(pos + Pos(-1, 1)) {
+ pos + Pos(-1, 1)
+ } else if !self.inhabited(pos + Pos(1, 1)) {
+ pos + Pos(1, 1)
+ } else {
+ self.sand_grains.insert(pos);
+ return;
+ };
+ }
+ }
+
+ fn inhabited(&self, pos: Pos) -> bool {
+ self.structures.iter().any(|s| s.contains(pos)) || self.sand_grains.contains(&pos)
+ }
+}
+
+#[derive(Debug)]
+struct Structure {
+ points: Vec<Pos>,
+ bbox_min: Pos,
+ bbox_max: Pos,
+}
+
+impl Structure {
+ fn new(points: Vec<Pos>) -> Self {
+ let bbox_min = points.iter().fold(Pos(isize::MAX, isize::MAX), |acc, pos| {
+ Pos(min(acc.0, pos.0), min(acc.1, pos.1))
+ });
+ let bbox_max = points.iter().fold(Pos(isize::MAX, isize::MAX), |acc, pos| {
+ Pos(max(acc.0, pos.0), max(acc.1, pos.1))
+ });
+
+ Structure {
+ points,
+ bbox_max,
+ bbox_min,
+ }
+ }
+ fn parse(i: &str) -> IResult<&str, Self> {
+ let (i, points) = separated_list0(tag(" -> "), Pos::parse)(i)?;
+
+ Ok((i, Structure::new(points)))
+ }
+
+ fn contains(&self, pos: Pos) -> bool {
+ if pos.0 < self.bbox_min.0
+ || pos.0 > self.bbox_max.0
+ || pos.1 < self.bbox_min.1
+ || pos.1 > self.bbox_max.1
+ {
+ return false;
+ }
+ for (a, b) in self.points.iter().zip(self.points.iter().skip(1)) {
+ if a.0 == b.0 {
+ if pos.0 == a.0 && pos.1 >= min(a.1, b.1) && pos.1 <= max(a.1, b.1) {
+ return true;
+ }
+ } else {
+ // a.1 == b.1
+ if pos.1 == a.1 && pos.0 >= min(a.0, b.0) && pos.0 <= max(a.0, b.0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+struct Pos(isize, isize);
+impl Pos {
+ fn parse(i: &str) -> IResult<&str, Self> {
+ let (i, x) = u32(i)?;
+ let (i, _) = tag(",")(i)?;
+ let (i, y) = u32(i)?;
+
+ Ok((i, Pos(x as _, y as _)))
+ }
+}
+impl From<Pos> for egui::Pos2 {
+ fn from(value: Pos) -> Self {
+ egui::Pos2 {
+ x: value.0 as _,
+ y: value.1 as _,
+ }
+ }
+}
+
+impl Add for Pos {
+ type Output = Pos;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ Pos(self.0 + rhs.0, self.1 + rhs.1)
+ }
+}
+
+impl eframe::App for App {
+ fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
+ egui::SidePanel::right("instructions").show(ctx, |ui| {
+ egui::ScrollArea::vertical().show(ui, |ui| {
+ if ui.button("Step").clicked() && !self.finished {
+ self.step();
+ }
+ if ui.button("Reset").clicked() {
+ self.reset()
+ }
+ ui.label(format!(
+ "{} grains, finished: {}",
+ self.sand_grains.len(),
+ self.finished
+ ));
+ ui.add(egui::Slider::new(&mut self.speed, 0..=100).text("Speed"));
+ })
+ });
+
+ if self.speed > 0 {
+ for _ in 0..self.speed {
+ self.step();
+ }
+ ctx.request_repaint_after(Duration::from_millis(100))
+ }
+
+ egui::CentralPanel::default().show(ctx, |ui| {
+ let mut painter_size = ui.available_size_before_wrap();
+ if !painter_size.is_finite() {
+ painter_size = egui::vec2(500.0, 500.0);
+ }
+
+ const RANGE: f32 = 200.0;
+ const SHIFT: egui::Pos2 = egui::pos2(400.0, 5.0);
+ let (_, painter) = ui.allocate_painter(painter_size, Sense::hover());
+
+ let transform_point = |p: &Pos| -> egui::Pos2 {
+ egui::pos2(
+ ((p.0 as f32 - SHIFT.x) / RANGE) * painter_size.x,
+ ((p.1 as f32 + SHIFT.y) / RANGE) * painter_size.y,
+ )
+ };
+ for structure in self.structures.iter() {
+ for (a, b) in structure.points.iter().zip(structure.points.iter().skip(1)) {
+ painter.line_segment(
+ [transform_point(a), transform_point(b)],
+ Stroke::new(1.0, Color32::RED),
+ );
+ }
+ }
+
+ for grain in self.sand_grains.iter() {
+ painter.rect(
+ Rect {
+ min: transform_point(grain),
+ max: transform_point(&Pos(grain.0 + 1, grain.1 + 1)),
+ },
+ egui::Rounding::none(),
+ Color32::YELLOW,
+ Stroke::NONE,
+ )
+ }
+ });
+ }
+}