diff options
Diffstat (limited to '2022/src/day14.rs')
-rw-r--r-- | 2022/src/day14.rs | 230 |
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, + ) + } + }); + } +} |