aboutsummaryrefslogtreecommitdiff
path: root/2022/src
diff options
context:
space:
mode:
authorAria <me@aria.rip>2023-01-02 21:58:56 +0000
committerAria <me@aria.rip>2023-01-02 21:58:56 +0000
commit5eb58ad076f2cd435b11b140820da224b60b73d5 (patch)
tree2a67939595fbf993ff04f69b9cd3f0aa20827d96 /2022/src
initial commit
Diffstat (limited to '2022/src')
-rw-r--r--2022/src/day01.rs21
-rw-r--r--2022/src/day02.rs105
-rw-r--r--2022/src/day03.rs62
-rw-r--r--2022/src/day04.rs35
-rw-r--r--2022/src/day05.rs145
-rw-r--r--2022/src/day06.rs47
-rw-r--r--2022/src/day07.rs110
-rw-r--r--2022/src/day08.rs110
-rw-r--r--2022/src/day09.html38
-rw-r--r--2022/src/day09.rs238
-rw-r--r--2022/src/day10.rs82
-rw-r--r--2022/src/day11.rs127
-rw-r--r--2022/src/day12.rs141
-rw-r--r--2022/src/day13.rs132
-rw-r--r--2022/src/day14.html38
-rw-r--r--2022/src/day14.rs230
-rw-r--r--2022/src/utils.rs59
17 files changed, 1720 insertions, 0 deletions
diff --git a/2022/src/day01.rs b/2022/src/day01.rs
new file mode 100644
index 0000000..a328975
--- /dev/null
+++ b/2022/src/day01.rs
@@ -0,0 +1,21 @@
+use crate::utils::max_n;
+
+mod utils;
+
+fn main() {
+ let input = utils::read_input();
+
+ let mut elf_cal_counts: Vec<usize> = input
+ .split("\n\n")
+ .map(|xs| {
+ xs.lines()
+ .map(|x| x.parse::<usize>().unwrap())
+ .sum::<usize>()
+ })
+ .collect();
+
+ let top_3 = max_n(&mut elf_cal_counts, 3);
+
+ println!("Max: {}", top_3.last().unwrap());
+ println!("Sum of top 3: {}", top_3.iter().sum::<usize>());
+}
diff --git a/2022/src/day02.rs b/2022/src/day02.rs
new file mode 100644
index 0000000..d0b0c5f
--- /dev/null
+++ b/2022/src/day02.rs
@@ -0,0 +1,105 @@
+mod utils;
+
+use utils::read_input;
+
+fn main() {
+ let input = read_input();
+
+ let turns_both_moves: Vec<_> = input
+ .lines()
+ .map(|xs| {
+ let mut parts = xs.split(' ').take(2).map(Move::parse);
+ (parts.next().unwrap(), parts.next().unwrap())
+ })
+ .collect();
+
+ let total_score_p1: usize = turns_both_moves
+ .iter()
+ .map(|(them, us)| score(*them, *us))
+ .sum();
+
+ let total_score_p2: usize = turns_both_moves
+ .into_iter()
+ .map(|(them, us)| (them, us.to_outcome()))
+ .map(|(them, outcome)| (them, them.to_get(outcome)))
+ .map(|(them, us)| score(them, us))
+ .sum();
+
+ println!("Part 1: {}", total_score_p1);
+ println!("Part 2: {}", total_score_p2);
+}
+
+fn score(them: Move, us: Move) -> usize {
+ let won_val = match (them.beats(us), us.beats(them)) {
+ (true, false) => 0,
+ (false, true) => 6,
+ (false, false) => 3,
+ (a, b) => unreachable!("they beat us: {}, we beat them: {}", a, b),
+ };
+ us.value() + won_val
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Move {
+ Rock,
+ Paper,
+ Scissors,
+}
+
+impl Move {
+ fn parse(inp: &str) -> Self {
+ match inp {
+ "A" => Move::Rock,
+ "B" => Move::Paper,
+ "C" => Move::Scissors,
+ "X" => Move::Rock,
+ "Y" => Move::Paper,
+ "Z" => Move::Scissors,
+ _ => panic!("unrecognised move: {}", inp),
+ }
+ }
+
+ fn to_outcome(self) -> Outcome {
+ match self {
+ Move::Rock => Outcome::Lose,
+ Move::Paper => Outcome::Draw,
+ Move::Scissors => Outcome::Win,
+ }
+ }
+
+ fn beats(self, other: Move) -> bool {
+ matches!(
+ (self, other),
+ (Move::Paper, Move::Rock)
+ | (Move::Rock, Move::Scissors)
+ | (Move::Scissors, Move::Paper)
+ )
+ }
+
+ fn to_get(self, outcome: Outcome) -> Self {
+ match (self, outcome) {
+ (_, Outcome::Draw) => self,
+ (Move::Rock, Outcome::Win) => Move::Paper,
+ (Move::Paper, Outcome::Win) => Move::Scissors,
+ (Move::Scissors, Outcome::Win) => Move::Rock,
+ (Move::Rock, Outcome::Lose) => Move::Scissors,
+ (Move::Paper, Outcome::Lose) => Move::Rock,
+ (Move::Scissors, Outcome::Lose) => Move::Paper,
+ }
+ }
+
+ fn value(self) -> usize {
+ match self {
+ Move::Rock => 1,
+ Move::Paper => 2,
+ Move::Scissors => 3,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Outcome {
+ Win,
+ Draw,
+ Lose,
+}
diff --git a/2022/src/day03.rs b/2022/src/day03.rs
new file mode 100644
index 0000000..7e245de
--- /dev/null
+++ b/2022/src/day03.rs
@@ -0,0 +1,62 @@
+mod utils;
+
+use std::collections::HashSet;
+
+use utils::read_input;
+
+fn main() {
+ let input = read_input();
+
+ // Convert each line to a tuple of hashsets, one for each backpack pocket
+ let rucksack_items: Vec<_> = input
+ .lines()
+ .map(|line| (&line[..line.len() / 2], &line[line.len() / 2..]))
+ .map(|(a, b)| -> (HashSet<char>, HashSet<char>) {
+ (a.chars().collect(), b.chars().collect())
+ })
+ .collect();
+
+ let sum_error_priorities: u32 = rucksack_items
+ .iter()
+ .map(|(a, b)| {
+ *a.intersection(b)
+ .next()
+ .expect("no common item between two backpack segments!")
+ }) // Get common item
+ .map(priority)
+ .sum();
+
+ let sum_badge_priorities: u32 = rucksack_items // Construct chunked iterator
+ .iter()
+ .step_by(3)
+ .zip(rucksack_items.iter().skip(1).step_by(3))
+ .zip(rucksack_items.iter().skip(2).step_by(3))
+ .map(|(((e1l, e1r), (e2l, e2r)), (e3l, e3r))| {
+ for item in e1l.union(e1r) {
+ if (e2l.contains(item) || e2r.contains(item))
+ && (e3l.contains(item) || e3r.contains(item))
+ {
+ return *item;
+ }
+ }
+ panic!(
+ "group has no badge! {:?}",
+ ((e1l, e1r), (e2l, e2r), (e3l, e3r))
+ )
+ })
+ .map(priority)
+ .sum();
+
+ println!("Part 1: {}", sum_error_priorities);
+ println!("Part 2: {}", sum_badge_priorities);
+}
+
+fn priority(chr: char) -> u32 {
+ if chr.is_ascii_lowercase() {
+ chr as u32 - 96
+ } else if chr.is_ascii_uppercase() {
+ chr as u32 - 38
+ } else {
+ panic!("unrecognised item {}", chr)
+ }
+}
diff --git a/2022/src/day04.rs b/2022/src/day04.rs
new file mode 100644
index 0000000..052dde1
--- /dev/null
+++ b/2022/src/day04.rs
@@ -0,0 +1,35 @@
+mod utils;
+
+use std::ops::RangeInclusive;
+
+use utils::{iter_pair, read_input};
+
+fn main() {
+ let input = read_input();
+
+ let pairs = input.lines().map(|x| {
+ iter_pair(x.split(',').map(|x| -> RangeInclusive<usize> {
+ let (start, end) = iter_pair(x.split('-'));
+ start.parse().unwrap()..=end.parse().unwrap()
+ }))
+ });
+
+ let (fully_contains, partially_contains) = pairs
+ .map(|(a, b)| (fully_overlap(&a, &b), partially_overlap(&a, &b)))
+ .fold((0, 0), |(acc_full, acc_part), (full, part)| {
+ (acc_full + full as usize, acc_part + part as usize)
+ });
+
+ println!("Part 1: {}", fully_contains);
+ println!("Part 2: {}", partially_contains);
+}
+
+#[inline(always)]
+fn fully_overlap<T: PartialOrd>(a: &RangeInclusive<T>, b: &RangeInclusive<T>) -> bool {
+ (a.contains(b.start()) && a.contains(b.end())) || (b.contains(a.start()) && b.contains(a.end()))
+}
+
+#[inline(always)]
+fn partially_overlap<T: PartialOrd>(a: &RangeInclusive<T>, b: &RangeInclusive<T>) -> bool {
+ a.contains(b.start()) || a.contains(b.end()) || b.contains(a.start()) || b.contains(a.end())
+}
diff --git a/2022/src/day05.rs b/2022/src/day05.rs
new file mode 100644
index 0000000..f45cd0a
--- /dev/null
+++ b/2022/src/day05.rs
@@ -0,0 +1,145 @@
+//! Day 5: Supply Stacks
+
+extern crate bumpalo;
+
+mod utils;
+
+use bumpalo::Bump;
+use std::cell::Cell;
+use utils::{parse_num, parse_static, read_input};
+
+/// Base type for a crate
+#[derive(Debug, Clone)]
+struct Crate<'a> {
+ letter: char,
+ below: Cell<Option<&'a Crate<'a>>>,
+}
+
+impl Default for Crate<'_> {
+ fn default() -> Self {
+ Crate {
+ letter: ' ',
+ below: None.into(),
+ }
+ }
+}
+
+/// A move the crane may make
+#[derive(Debug, Clone, Copy)]
+struct Move {
+ reps: usize,
+ from: usize,
+ to: usize,
+}
+type Tops<'arena> = Vec<Option<&'arena Crate<'arena>>>;
+
+fn main() {
+ let input = read_input();
+
+ {
+ let arena = Bump::new();
+ let mut tops = construct_piles(&input, &arena);
+ for mv in parse_moves(&input) {
+ perform_move_9000(&mut tops, mv);
+ }
+
+ println!("Part 1: {}", top_letters(tops));
+ }
+
+ {
+ let arena = Bump::new();
+ let mut tops = construct_piles(&input, &arena);
+ for mv in parse_moves(&input) {
+ perform_move_9001(&mut tops, mv);
+ }
+
+ println!("Part 2: {}", top_letters(tops));
+ }
+}
+
+/// Get the message / the top letters of each stack
+fn top_letters(tops: Tops<'_>) -> String {
+ tops.iter().map(|x| x.as_ref().unwrap().letter).collect()
+}
+
+/// Perform a move for part 1
+fn perform_move_9000(tops: &mut Tops<'_>, mv: Move) {
+ for _ in 0..mv.reps {
+ let from = tops[mv.from].take().unwrap();
+ let to = tops[mv.to].take();
+
+ tops[mv.from] = from.below.replace(to);
+ tops[mv.to] = Some(from);
+ }
+}
+
+/// Perform a move for part 2
+fn perform_move_9001(tops: &mut Tops<'_>, mv: Move) {
+ let pickup_top = tops[mv.from].take().unwrap();
+
+ let mut pickup_bot = pickup_top;
+ for _ in 1..mv.reps {
+ pickup_bot = pickup_bot.below.get().as_ref().unwrap();
+ }
+
+ let to = tops[mv.to].take();
+
+ tops[mv.from] = pickup_bot.below.replace(to);
+ tops[mv.to] = Some(pickup_top);
+}
+
+// Annoying parsing code!
+
+fn construct_piles<'a>(input: &str, arena: &'a Bump) -> Tops<'a> {
+ let mut piles = Vec::new();
+ for layer_iter in input
+ .lines()
+ .take_while(|x| x.chars().find(|x| *x != ' ').unwrap() == '[')
+ .map(|x| x.chars().skip(1).step_by(4))
+ {
+ for (stack, chr) in layer_iter.enumerate() {
+ if piles.len() < stack + 1 {
+ piles.resize(stack + 1, None);
+ }
+
+ if chr != ' ' {
+ let val = &*arena.alloc(Crate {
+ letter: chr,
+ below: None.into(),
+ });
+ if piles[stack].is_none() {
+ piles[stack] = Some(val);
+ } else {
+ let mut top = *piles[stack].as_ref().unwrap();
+ while let Some(below) = top.below.get() {
+ top = below;
+ }
+
+ top.below.set(Some(val));
+ }
+ }
+ }
+ }
+
+ piles
+}
+
+fn parse_moves(input: &str) -> impl Iterator<Item = Move> + '_ {
+ input
+ .lines()
+ .skip_while(|line| !line.starts_with('m'))
+ .map(|line| {
+ let line = parse_static("move ", line);
+ let (reps, line) = parse_num(line);
+ let line = parse_static(" from ", line);
+ let (from, line) = parse_num::<usize>(line);
+ let line = parse_static(" to ", line);
+ let (to, _) = parse_num::<usize>(line);
+
+ Move {
+ reps,
+ from: from - 1,
+ to: to - 1,
+ }
+ })
+}
diff --git a/2022/src/day06.rs b/2022/src/day06.rs
new file mode 100644
index 0000000..8bbfe6d
--- /dev/null
+++ b/2022/src/day06.rs
@@ -0,0 +1,47 @@
+mod utils;
+use utils::read_input;
+
+fn main() {
+ let input = read_input();
+
+ println!("Part 1: {}", find_chunk_nodup::<4>(&input));
+ println!("Part 2: {}", find_chunk_nodup::<14>(&input));
+}
+
+fn find_chunk_nodup<const N: usize>(input: &str) -> usize {
+ let mut window = ['a'; N];
+ let mut chars = input.chars();
+
+ // Fill up initial window
+ for item in window.iter_mut() {
+ *item = chars.next().unwrap();
+ }
+
+ for i in 0..input.len() {
+ // Shuffle everything in window down
+ for j in 0..N - 1 {
+ window[j] = window[j + 1];
+ }
+ window[N - 1] = chars.next().unwrap();
+
+ if window
+ .iter()
+ .fold(Some(0), |acc, chr| add_char(acc?, *chr))
+ .is_some()
+ {
+ return i + N + 1;
+ }
+ }
+
+ panic!("no solution!")
+}
+
+#[inline]
+fn add_char(bits: u32, chr: char) -> Option<u32> {
+ let add = 1 << (chr.to_ascii_lowercase() as u32 - 97);
+ if (bits | add) != bits {
+ Some(bits | add)
+ } else {
+ None
+ }
+}
diff --git a/2022/src/day07.rs b/2022/src/day07.rs
new file mode 100644
index 0000000..88bb647
--- /dev/null
+++ b/2022/src/day07.rs
@@ -0,0 +1,110 @@
+mod utils;
+use utils::read_input;
+
+fn main() {
+ let input = read_input();
+
+ let mut unused = 0;
+ let mut sum_under_threshold = 0;
+
+ for size in DirectorySizes::new(&input) {
+ if size < 100_000 {
+ sum_under_threshold += size;
+ }
+ unused = 70_000_000 - size;
+ }
+
+ println!("Part 1: {}", sum_under_threshold);
+ let deficit = 30_000_000 - unused;
+ println!(
+ "Part 2: {}",
+ DirectorySizes::new(&input)
+ .filter(|x| *x > deficit)
+ .min()
+ .unwrap()
+ );
+}
+
+struct DirectorySizes<'a> {
+ lines: std::str::Lines<'a>,
+ directory_stack: [usize; 64],
+ stack_top: usize,
+}
+
+impl<'a> DirectorySizes<'a> {
+ fn new(input: &'a str) -> Self {
+ let mut lines = input.lines();
+ assert_eq!(lines.next(), Some("$ cd /"));
+
+ DirectorySizes {
+ lines,
+ directory_stack: [0; 64],
+ stack_top: 0,
+ }
+ }
+}
+
+impl Iterator for DirectorySizes<'_> {
+ type Item = usize;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ for line in self.lines.by_ref() {
+ let line: Line = line.into();
+
+ match line {
+ Line::Cd(up) => {
+ if up {
+ self.directory_stack[self.stack_top - 1] +=
+ self.directory_stack[self.stack_top];
+ self.stack_top -= 1;
+ return Some(self.directory_stack[self.stack_top + 1]);
+ } else {
+ self.stack_top += 1;
+ self.directory_stack[self.stack_top] = 0;
+ }
+ }
+ Line::FileSize(size) => {
+ self.directory_stack[self.stack_top] += size;
+ }
+ Line::Ls => (),
+ Line::Dir => (),
+ };
+ }
+
+ if self.stack_top > 0 {
+ self.directory_stack[self.stack_top - 1] += self.directory_stack[self.stack_top];
+ self.stack_top -= 1;
+ Some(self.directory_stack[self.stack_top + 1])
+ } else if self.directory_stack[0] != 0 {
+ Some(std::mem::replace(&mut self.directory_stack[0], 0))
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Line {
+ /// True = .., False = other
+ Cd(bool),
+ Ls,
+ FileSize(usize),
+ Dir,
+}
+
+impl From<&str> for Line {
+ fn from(value: &str) -> Self {
+ if value.starts_with("$ cd ") {
+ let mut args = value.split("cd ");
+ args.next().unwrap();
+ Line::Cd(args.next().unwrap() == "..")
+ } else if value.starts_with("$ ls") {
+ Line::Ls
+ } else if value.starts_with("dir") {
+ Line::Dir
+ } else {
+ let num_index = value.chars().take_while(|chr| chr.is_numeric()).count();
+ Line::FileSize(value[..num_index].parse().unwrap())
+ }
+ }
+}
diff --git a/2022/src/day08.rs b/2022/src/day08.rs
new file mode 100644
index 0000000..514cb21
--- /dev/null
+++ b/2022/src/day08.rs
@@ -0,0 +1,110 @@
+mod utils;
+
+use utils::read_input;
+
+fn main() {
+ let input = read_input();
+
+ let mut grid: Vec<Vec<_>> = input
+ .lines()
+ .map(|x| {
+ x.chars()
+ .map(|x| (x.to_digit(10).unwrap() as u8, false))
+ .collect()
+ })
+ .collect();
+ in_all_directions!(check_vis_along_run, grid);
+
+ println!(
+ "Part 1: {}",
+ grid.iter()
+ .map(|x| x.iter().filter(|x| x.1).count())
+ .sum::<usize>()
+ );
+
+ let mut grid: Vec<Vec<(u8, u32)>> = grid
+ .into_iter()
+ .map(|x| x.into_iter().map(|(x, _)| (x, 1)).collect())
+ .collect();
+ in_all_directions!(vis_score_along_run, grid);
+
+ println!(
+ "Part 2: {}",
+ grid.iter()
+ .map(|row| row.iter().map(|(_, vis)| vis).max().unwrap())
+ .max()
+ .unwrap()
+ );
+}
+
+fn check_vis_along_run(
+ mut run: impl Iterator<Item = (usize, usize)>,
+ grid: &mut [Vec<(u8, bool)>],
+) {
+ let mut curr_height = {
+ let (row_idx, col_idx) = run.next().unwrap();
+
+ grid[row_idx][col_idx].1 = true;
+
+ grid[row_idx][col_idx].0
+ };
+ for (row_idx, col_idx) in run {
+ let height = grid[row_idx][col_idx].0;
+ if height > curr_height {
+ curr_height = height;
+ grid[row_idx][col_idx].1 = true;
+ }
+ }
+}
+
+fn vis_score_along_run(run: impl Iterator<Item = (usize, usize)>, grid: &mut [Vec<(u8, u32)>]) {
+ let mut next_values = [0; 10];
+ for (row_idx, col_idx) in run {
+ let height = grid[row_idx][col_idx].0;
+ grid[row_idx][col_idx].1 *= next_values[height as usize];
+
+ for (val, score) in next_values.iter_mut().enumerate() {
+ *score = if *score == 0 || val > height as usize {
+ *score + 1
+ } else {
+ 1
+ };
+ }
+ }
+}
+
+#[macro_use]
+mod codegen {
+ #[macro_export]
+ macro_rules! in_all_directions {
+ ($f:ident, $grid: ident) => {
+ let grid_rows = $grid.len();
+ let grid_cols = $grid[0].len();
+
+ (0..grid_rows).for_each(|grid_row| {
+ $f(
+ (0..grid_cols).map(move |grid_col| (grid_row, grid_col)),
+ &mut $grid,
+ );
+ $f(
+ (0..grid_cols)
+ .rev()
+ .map(move |grid_col| (grid_row, grid_col)),
+ &mut $grid,
+ )
+ });
+ (0..grid_cols).for_each(|grid_col| {
+ $f(
+ (0..grid_rows).map(move |grid_row| (grid_row, grid_col)),
+ &mut $grid,
+ );
+ $f(
+ (0..grid_rows)
+ .rev()
+ .map(move |grid_row| (grid_row, grid_col)),
+ &mut $grid,
+ )
+ });
+ };
+ }
+}
diff --git a/2022/src/day09.html b/2022/src/day09.html
new file mode 100644
index 0000000..d3ed12b
--- /dev/null
+++ b/2022/src/day09.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <link data-trunk rel="rust" data-wasm-opt="2" href="../Cargo.toml" data-bin="day9" />
+
+ <style>
+ html {
+ /* Remove touch delay: */
+ touch-action: manipulation;
+ }
+
+ html,
+ body {
+ overflow: hidden;
+ margin: 0 !important;
+ padding: 0 !important;
+ height: 100%;
+ width: 100%;
+ }
+
+ canvas {
+ margin-right: auto;
+ margin-left: auto;
+ display: block;
+ position: absolute;
+ top: 0%;
+ left: 50%;
+ transform: translate(-50%, 0%);
+ }
+ </style>
+</head>
+
+<body>
+ <canvas id="canvas"></canvas>
+</body>
+
+</html>
diff --git a/2022/src/day09.rs b/2022/src/day09.rs
new file mode 100644
index 0000000..23b08b3
--- /dev/null
+++ b/2022/src/day09.rs
@@ -0,0 +1,238 @@
+mod utils;
+use std::{
+ cmp::min,
+ collections::{HashSet, VecDeque},
+ time::Duration,
+};
+
+use egui::{Color32, Sense, Stroke};
+use utils::{iter_pair, read_input};
+
+#[cfg(target_arch = "wasm32")]
+fn main() {
+ console_error_panic_hook::set_once();
+
+ let input = include_str!("../fake_inputs/day9");
+ let input = parse_moves(&input).collect();
+
+ 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");
+ });
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {
+ let input = read_input();
+
+ println!("Part 1: {}", visited_from_moves::<2>(parse_moves(&input)));
+ println!("Part 2: {}", visited_from_moves::<10>(parse_moves(&input)));
+}
+
+type Pos = (i32, i32);
+type Move = (Direction, u8);
+
+fn visited_from_moves<const N: usize>(moves: impl Iterator<Item = Move>) -> usize {
+ let mut tail_visited = HashSet::new();
+ tail_visited.insert((0, 0));
+ let mut knots = [(0, 0); N];
+ for mv in moves {
+ make_move(mv, &mut knots, &mut tail_visited);
+ }
+
+ tail_visited.len()
+}
+
+fn make_move(mv: Move, knots: &mut [Pos], tail_visited: &mut HashSet<Pos>) {
+ let (dir, count) = mv;
+
+ for _ in 0..count {
+ // Move head of rope
+ match dir {
+ Direction::Up => knots[0].1 += 1,
+ Direction::Down => knots[0].1 -= 1,
+ Direction::Right => knots[0].0 += 1,
+ Direction::Left => knots[0].0 -= 1,
+ };
+
+ for front_idx in 0..knots.len() - 1 {
+ let (fx, fy) = knots[front_idx];
+ let (bx, by) = &mut knots[front_idx + 1];
+ let (dx, dy) = (fx - *bx, fy - *by);
+ if (dx.abs() == 2 && dy == 0) || (dy.abs() == 2 && dx == 0) || (dy.abs() + dx.abs() > 2)
+ {
+ *bx += (dx.signum()) * min(dx.abs(), 1);
+ *by += (dy.signum()) * min(dy.abs(), 1);
+ }
+ }
+ tail_visited.insert(knots[knots.len() - 1]);
+ }
+}
+
+fn parse_moves(input: &str) -> impl Iterator<Item = Move> + '_ {
+ input.lines().map(|x| {
+ let (dir, count) = iter_pair(x.split(' '));
+ let count = count.parse().unwrap();
+
+ (
+ match dir {
+ "L" => Direction::Left,
+ "R" => Direction::Right,
+ "U" => Direction::Up,
+ "D" => Direction::Down,
+ _ => panic!("invalid direction, {}", dir),
+ },
+ count,
+ )
+ })
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Direction {
+ Up,
+ Down,
+ Left,
+ Right,
+}
+
+struct App {
+ orig_input: VecDeque<Move>,
+ input: VecDeque<Move>,
+ knots: Vec<Pos>,
+ tail_visited: HashSet<Pos>,
+ desired_knot_count: usize,
+ speed: usize,
+}
+
+impl App {
+ fn new(input: VecDeque<Move>) -> Self {
+ App {
+ orig_input: input.clone(),
+ input,
+ knots: vec![],
+ tail_visited: HashSet::new(),
+ desired_knot_count: 2,
+ speed: 0,
+ }
+ }
+
+ fn reset_knots(&mut self) {
+ self.input = self.orig_input.clone();
+ self.knots = vec![(0, 0); self.desired_knot_count];
+ self.tail_visited.clear();
+ self.tail_visited.insert((0, 0));
+ }
+
+ fn step(&mut self) {
+ match self.input.pop_front() {
+ Some(mv) => make_move(mv, &mut self.knots, &mut self.tail_visited),
+ None => (),
+ }
+ }
+}
+
+impl eframe::App for App {
+ fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
+ if self.desired_knot_count != self.knots.len() {
+ self.reset_knots()
+ }
+ egui::SidePanel::right("instructions").show(ctx, |ui| {
+ egui::ScrollArea::vertical().show(ui, |ui| {
+ if ui.button("Step").clicked() {
+ self.step();
+ }
+ if ui.button("Reset").clicked() {
+ self.reset_knots()
+ }
+ ui.label(format!("Tail Visited: {}", self.tail_visited.len()));
+ ui.add(egui::Slider::new(&mut self.desired_knot_count, 2..=20).text("Knot count"));
+ ui.add(egui::Slider::new(&mut self.speed, 0..=20).text("Speed"));
+ ui.heading("Instructions:");
+ for (i, ins) in self.input.iter().take(20).enumerate() {
+ let arrow = match ins.0 {
+ Direction::Up => "⬆",
+ Direction::Down => "⬇",
+ Direction::Right => "➡",
+ Direction::Left => "⬅",
+ };
+ if i == 0 {
+ ui.colored_label(Color32::YELLOW, arrow.repeat(ins.1 as _));
+ } else {
+ ui.label(arrow.repeat(ins.1 as _));
+ }
+ }
+ if self.input.len() > 20 {
+ ui.label(format!("+ {} more", self.input.len() - 20));
+ }
+ })
+ });
+
+ if self.speed > 0 && self.input.len() > 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 SIDE: f32 = 5.0;
+
+ let (res, painter) = ui.allocate_painter(painter_size, Sense::hover());
+ let center = res.rect.center().to_vec2();
+
+ let to_panel_pos = |pos: &Pos| {
+ (egui::vec2(pos.0 as f32 * SIDE, pos.1 as f32 * SIDE) + center).to_pos2()
+ };
+
+ let half_width = (painter_size.x / SIDE).floor() as i32;
+ let half_height = (painter_size.y / SIDE).floor() as i32;
+
+ for x in -half_width..half_width {
+ for y in -half_height..half_height {
+ let dot = (x, y);
+ let color = if dot == (0, 0) {
+ Color32::WHITE
+ } else if self.tail_visited.contains(&dot) {
+ Color32::DARK_RED
+ } else {
+ continue;
+ };
+
+ let dot_pos = to_panel_pos(&dot);
+ painter.circle_stroke(dot_pos, 1.0, Stroke::new(1.0, color));
+ }
+ }
+
+ // paint the head
+ painter.circle_stroke(
+ to_panel_pos(&self.knots[0]),
+ SIDE / 2.0,
+ Stroke::new(SIDE / 2.0, Color32::GREEN),
+ );
+ for knot in self.knots.iter().skip(1) {
+ painter.circle_stroke(
+ to_panel_pos(knot),
+ SIDE / 2.0,
+ Stroke::new(SIDE / 2.0, Color32::YELLOW),
+ );
+ }
+
+ for (a, b) in self.knots.iter().zip(self.knots.iter().skip(1)) {
+ let head_pos = to_panel_pos(a);
+ let tail_pos = to_panel_pos(b);
+ painter.arrow(
+ tail_pos,
+ head_pos - tail_pos,
+ Stroke::new(1.0, Color32::YELLOW),
+ )
+ }
+ });
+ }
+}
diff --git a/2022/src/day10.rs b/2022/src/day10.rs
new file mode 100644
index 0000000..4c74444
--- /dev/null
+++ b/2022/src/day10.rs
@@ -0,0 +1,82 @@
+mod utils;
+use utils::{parse_num, parse_static, read_input};
+
+fn main() {
+ let input = read_input();
+
+ let mut cmds = input.lines().map(parse_cmd);
+ let mut state = State {
+ x: 1,
+ add_next: None,
+ add_next_start: None,
+ crt_pos: 0,
+ };
+
+ let mut acc = 0;
+ run_steps::<20>(&mut state, &mut cmds);
+ acc += state.x * 20;
+ for i in 1..=5 {
+ run_steps::<40>(&mut state, &mut cmds);
+ acc += state.x * (20 + (i * 40));
+ }
+
+ run_steps::<{ TOTAL_STEPS - 220 }>(&mut state, &mut cmds);
+
+ println!("Part 1: {}", acc);
+}
+
+struct State {
+ x: isize,
+ add_next: Option<isize>,
+ add_next_start: Option<isize>,
+ crt_pos: usize,
+}
+
+const CRT_WIDTH: usize = 40;
+const CRT_HEIGHT: usize = 6;
+const TOTAL_STEPS: usize = CRT_WIDTH * CRT_HEIGHT;
+
+fn run_steps<const N: usize>(state: &mut State, cmd: &mut impl Iterator<Item = Command>) {
+ for _ in 0..N {
+ if let Some(add) = state.add_next_start.take() {
+ state.x += add;
+ }
+
+ if (state.x - (state.crt_pos % CRT_WIDTH) as isize).abs() < 2 {
+ print!("█");
+ } else {
+ print!(" ");
+ }
+ state.crt_pos += 1;
+ if (state.crt_pos % CRT_WIDTH) == 0 {
+ println!();
+ }
+
+ if let Some(add) = state.add_next.take() {
+ state.add_next_start = Some(add);
+ } else {
+ match cmd.next() {
+ Some(Command::NoOp) => (),
+ Some(Command::AddX(add)) => state.add_next = Some(add),
+ None => (),
+ }
+ }
+ }
+}
+
+fn parse_cmd(cmd: &str) -> Command {
+ if cmd == "noop" {
+ Command::NoOp
+ } else {
+ let cmd = parse_static("addx ", cmd);
+ let (add, _) = parse_num(cmd);
+
+ Command::AddX(add)
+ }
+}
+
+#[derive(Debug)]
+enum Command {
+ NoOp,
+ AddX(isize),
+}
diff --git a/2022/src/day11.rs b/2022/src/day11.rs
new file mode 100644
index 0000000..aad0b48
--- /dev/null
+++ b/2022/src/day11.rs
@@ -0,0 +1,127 @@
+mod utils;
+extern crate nom;
+use nom::{
+ branch::alt,
+ bytes::complete::{tag, take_until},
+ character::complete::{digit0, u32},
+ multi::separated_list0,
+ IResult,
+};
+use utils::{max_n, read_input};
+
+fn main() {
+ let input = read_input();
+
+ let (_, monkeys_items) = separated_list0(tag("\n\n"), Monkey::parse)(&input).unwrap();
+ let (monkeys, items): (Vec<_>, Vec<_>) = monkeys_items.into_iter().unzip();
+
+ println!(
+ "Part 1: {}",
+ monkey_business::<3, 20>(&monkeys, items.clone(), usize::MAX),
+ );
+ println!(
+ "Part 2: {}",
+ monkey_business::<1, 10000>(&monkeys, items, monkeys.iter().map(|x| x.test).product())
+ );
+}
+
+fn monkey_business<const REDUCER: usize, const ROUNDS: usize>(
+ monkeys: &[Monkey],
+ mut items: Vec<Vec<usize>>,
+ additional_reducer: usize,
+) -> usize {
+ let mut inspections = vec![0; monkeys.len()];
+ for _ in 0..ROUNDS {
+ for monkey_idx in 0..monkeys.len() {
+ let curr_monkey = &monkeys[monkey_idx];
+ while let Some(worry) = items[monkey_idx].pop() {
+ let new_worry = (curr_monkey.do_op(worry) / REDUCER) % additional_reducer;
+ inspections[monkey_idx] += 1;
+ let throw_to = curr_monkey.throw_to(new_worry);
+
+ items[throw_to].push(new_worry);
+ }
+ }
+ }
+
+ max_n(&mut inspections, 2).iter().product::<usize>()
+}
+
+#[derive(Debug)]
+struct Monkey {
+ op: Operation,
+ test: usize,
+ next_monkey: (usize, usize),
+}
+
+impl Monkey {
+ fn parse(i: &str) -> IResult<&str, (Monkey, Vec<usize>)> {
+ let (i, _) = tag("Monkey ")(i)?;
+ let (i, _) = digit0(i)?;
+ let (i, _) = tag(":\n Starting items: ")(i)?;
+ let (i, items) = separated_list0(tag(", "), u32)(i)?;
+ let (i, _) = tag("\n Operation: new = ")(i)?;
+ let (i, op) = Operation::parse(i)?;
+ let (i, _) = take_until("\n")(i)?;
+ let (i, _) = tag("\n Test: divisible by ")(i)?;
+ let (i, test) = u32(i)?;
+ let (i, _) = tag("\n If true: throw to monkey ")(i)?;
+ let (i, monkey_t) = u32(i)?;
+ let (i, _) = tag("\n If false: throw to monkey ")(i)?;
+ let (i, monkey_f) = u32(i)?;
+
+ Ok((
+ i,
+ (
+ Monkey {
+ op,
+ test: test as usize,
+ next_monkey: (monkey_t as usize, monkey_f as usize),
+ },
+ items.into_iter().map(|x| x as usize).collect(),
+ ),
+ ))
+ }
+
+ fn do_op(&self, x: usize) -> usize {
+ match self.op {
+ Operation::Square => x * x,
+ Operation::Add(c) => x + c,
+ Operation::Mult(c) => x * c,
+ }
+ }
+ fn throw_to(&self, x: usize) -> usize {
+ if (x % self.test) == 0 {
+ self.next_monkey.0
+ } else {
+ self.next_monkey.1
+ }
+ }
+}
+
+#[derive(Debug)]
+enum Operation {
+ Square,
+ Add(usize),
+ Mult(usize),
+}
+
+impl Operation {
+ fn parse(i: &str) -> IResult<&str, Operation> {
+ let (i, _) = tag("old ")(i)?;
+ let (i, op) = alt((tag("* "), tag("+ ")))(i)?;
+ if let Ok((i, _)) = tag::<&str, &str, (&str, nom::error::ErrorKind)>("old")(i) {
+ Ok((i, Operation::Square))
+ } else {
+ let (i, x) = u32(i)?;
+ Ok((
+ i,
+ match op {
+ "* " => Operation::Mult(x as usize),
+ "+ " => Operation::Add(x as usize),
+ _ => unreachable!(),
+ },
+ ))
+ }
+ }
+}
diff --git a/2022/src/day12.rs b/2022/src/day12.rs
new file mode 100644
index 0000000..c446c74
--- /dev/null
+++ b/2022/src/day12.rs
@@ -0,0 +1,141 @@
+mod utils;
+use std::collections::BinaryHeap;
+
+use utils::read_input;
+
+fn main() {
+ let input = read_input();
+
+ let width = input.lines().next().unwrap().len();
+ let height = input.lines().count();
+ let mut heights = vec![0; width * height];
+
+ let mut start = (0, 0);
+ let mut end = (0, 0);
+
+ for (y, line) in input.lines().enumerate() {
+ for (x, c) in line.chars().enumerate() {
+ let val = if c == 'S' {
+ start = (x as isize, y as isize);
+ 0
+ } else if c == 'E' {
+ end = (x as isize, y as isize);
+ 25
+ } else {
+ c.to_ascii_lowercase() as isize - 97
+ };
+ heights[(y * width) + x] = val;
+ }
+ }
+
+ let cost = pathfind(&heights, width, end);
+
+ println!("Part 1: {:?}", cost[pos_to_index(&start, width)]);
+ println!(
+ "Part 2: {:?}",
+ (0..height as isize)
+ .map(|y| (0..width as isize)
+ .map(|x| pos_to_index(&(x, y), width))
+ .filter(|idx| heights[*idx] == 0)
+ .map(|idx| cost[idx])
+ .min()
+ .unwrap())
+ .min()
+ .unwrap()
+ )
+}
+
+type HeightMap = Vec<isize>;
+type Pos = (isize, isize);
+
+fn pathfind(heights: &HeightMap, width: usize, end: Pos) -> Vec<usize> {
+ let mut min_cost = vec![usize::MAX; heights.len()];
+ let mut frontier: BinaryHeap<GraphPos> = BinaryHeap::new();
+ frontier.push(GraphPos { pos: end, moves: 0 });
+ while let Some(curr) = frontier.pop() {
+ let idx = pos_to_index(&curr.pos, width);
+ if curr.moves >= min_cost[idx] {
+ continue;
+ } else {
+ min_cost[idx] = curr.moves;
+ }
+
+ for neighbour in CanReach::new(heights, width, curr.pos) {
+ frontier.push(GraphPos {
+ pos: neighbour,
+ moves: curr.moves + 1,
+ })
+ }
+ }
+
+ min_cost
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct GraphPos {
+ pos: Pos,
+ moves: usize,
+}
+
+impl PartialOrd for GraphPos {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ self.moves.partial_cmp(&other.moves).map(|x| x.reverse())
+ }
+}
+
+impl Ord for GraphPos {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.moves.cmp(&other.moves).reverse()
+ }
+}
+
+struct CanReach<'a> {
+ heights: &'a HeightMap,
+ curr: Pos,
+ step: usize,
+ width: usize,
+}
+
+impl<'a> CanReach<'a> {
+ fn new(heights: &'a HeightMap, width: usize, pos: Pos) -> Self {
+ CanReach {
+ heights,
+ curr: pos,
+ step: 0,
+ width,
+ }
+ }
+}
+impl Iterator for CanReach<'_> {
+ type Item = Pos;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let (curr_x, curr_y) = self.curr;
+ loop {
+ let (x, y) = match self.step {
+ 0 => (curr_x + 1, curr_y),
+ 1 => (curr_x - 1, curr_y),
+ 2 => (curr_x, curr_y + 1),
+ 3 => (curr_x, curr_y - 1),
+ _ => break,
+ };
+ self.step += 1;
+
+ if (x >= 0
+ && x < self.width as isize
+ && y >= 0
+ && y < (self.heights.len() / self.width) as isize)
+ && self.heights[pos_to_index(&(x, y), self.width)]
+ >= self.heights[pos_to_index(&(curr_x, curr_y), self.width)] - 1
+ {
+ return Some((x, y));
+ }
+ }
+ None
+ }
+}
+
+#[inline(always)]
+fn pos_to_index(pos: &Pos, width: usize) -> usize {
+ ((pos.1 * width as isize) + pos.0) as usize
+}
diff --git a/2022/src/day13.rs b/2022/src/day13.rs
new file mode 100644
index 0000000..17b55eb
--- /dev/null
+++ b/2022/src/day13.rs
@@ -0,0 +1,132 @@
+mod utils;
+use std::{
+ cmp::Ordering,
+ iter::{once, Peekable},
+};
+
+extern crate nom;
+use nom::{bytes::complete::tag, character::complete::u32};
+use utils::read_input;
+
+fn main() {
+ let input = read_input();
+
+ let mut full: Vec<_> = input.lines().filter(|x| !x.is_empty()).collect();
+
+ println!(
+ "Part 1: {}",
+ full.iter()
+ .step_by(2)
+ .zip(full.iter().skip(1).step_by(2))
+ .map(|(left, right)| { compare(left, right) })
+ .enumerate()
+ .filter_map(|(i, cmp)| match cmp {
+ Ordering::Greater => None,
+ _ => Some(i + 1),
+ })
+ .sum::<usize>()
+ );
+
+ full.sort_by(|l, r| compare(l, r));
+
+ println!(
+ "Part 2: {}",
+ full.iter()
+ .enumerate()
+ .filter_map(|(i, val)| match *val {
+ "[[2]]" | "[[6]]" => Some(i + 1),
+ _ => None,
+ })
+ .product::<usize>()
+ );
+}
+
+fn compare(left: &str, right: &str) -> Ordering {
+ let mut left = Packet(left).peekable();
+ let mut right = Packet(right).peekable();
+ assert_eq!(left.next(), Some(PacketContents::StartList));
+ assert_eq!(right.next(), Some(PacketContents::StartList));
+ compare_lists(&mut left, &mut right)
+}
+
+fn compare_lists(
+ left: &mut Peekable<impl Iterator<Item = PacketContents>>,
+ right: &mut Peekable<impl Iterator<Item = PacketContents>>,
+) -> Ordering {
+ loop {
+ match (left.next().unwrap(), right.next().unwrap()) {
+ (PacketContents::EndList, PacketContents::EndList) => {
+ return Ordering::Equal;
+ }
+ (PacketContents::EndList, _) => {
+ while !matches!(right.next(), Some(PacketContents::EndList)) {}
+ return Ordering::Less;
+ }
+ (_, PacketContents::EndList) => {
+ while !matches!(left.next(), Some(PacketContents::EndList)) {}
+
+ return Ordering::Greater;
+ }
+ (PacketContents::StartList, PacketContents::StartList) => {
+ match compare_lists(left, right) {
+ Ordering::Equal => (),
+ x => return x,
+ }
+ }
+ (l @ PacketContents::Integer(_), PacketContents::StartList) => {
+ match compare_lists(
+ &mut once(l).chain(once(PacketContents::EndList)).peekable(),
+ right,
+ ) {
+ Ordering::Equal => (),
+ x => return x,
+ }
+ }
+ (PacketContents::StartList, r @ PacketContents::Integer(_)) => {
+ match compare_lists(
+ left,
+ &mut once(r).chain(once(PacketContents::EndList)).peekable(),
+ ) {
+ Ordering::Equal => (),
+ x => return x,
+ }
+ }
+ (PacketContents::Integer(l), PacketContents::Integer(r)) => match l.cmp(&r) {
+ Ordering::Equal => (),
+ x => return x,
+ },
+ };
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+enum PacketContents {
+ Integer(usize),
+ StartList,
+ EndList,
+}
+
+struct Packet<'a>(&'a str);
+
+impl Iterator for Packet<'_> {
+ type Item = PacketContents;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if let Ok((i, _)) = tag::<_, _, (&str, nom::error::ErrorKind)>("[")(self.0) {
+ self.0 = i;
+ return Some(PacketContents::StartList);
+ } else if let Ok((i, _)) = tag::<_, _, (&str, nom::error::ErrorKind)>("]")(self.0) {
+ self.0 = i;
+ return Some(PacketContents::EndList);
+ } else if let Ok((i, x)) = u32::<_, (&str, nom::error::ErrorKind)>(self.0) {
+ self.0 = i;
+ return Some(PacketContents::Integer(x as usize));
+ } else if let Ok((i, _)) = tag::<_, _, (&str, nom::error::ErrorKind)>(",")(self.0) {
+ self.0 = i;
+ } else {
+ return None;
+ }
+ }
+ }
+}
diff --git a/2022/src/day14.html b/2022/src/day14.html
new file mode 100644
index 0000000..c441303
--- /dev/null
+++ b/2022/src/day14.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <link data-trunk rel="rust" data-wasm-opt="2" href="../Cargo.toml" data-bin="day14" />
+
+ <style>
+ html {
+ /* Remove touch delay: */
+ touch-action: manipulation;
+ }
+
+ html,
+ body {
+ overflow: hidden;
+ margin: 0 !important;
+ padding: 0 !important;
+ height: 100%;
+ width: 100%;
+ }
+
+ canvas {
+ margin-right: auto;
+ margin-left: auto;
+ display: block;
+ position: absolute;
+ top: 0%;
+ left: 50%;
+ transform: translate(-50%, 0%);
+ }
+ </style>
+</head>
+
+<body>
+ <canvas id="canvas"></canvas>
+</body>
+
+</html>
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,
+ )
+ }
+ });
+ }
+}
diff --git a/2022/src/utils.rs b/2022/src/utils.rs
new file mode 100644
index 0000000..8230c69
--- /dev/null
+++ b/2022/src/utils.rs
@@ -0,0 +1,59 @@
+#![allow(unused)]
+use std::{fs::File, io::Read, str::FromStr};
+
+pub fn read_input() -> String {
+ let mut args = std::env::args();
+
+ let mut file = File::open(
+ args.nth(1)
+ .expect("first argument should be input filename"),
+ )
+ .expect("could not open input");
+ let mut out = String::new();
+ file.read_to_string(&mut out)
+ .expect("could not read input file");
+
+ out
+}
+
+pub fn max_n<T: Ord>(vec: &mut Vec<T>, n: usize) -> &[T] {
+ top_n_by(vec, |a, b| a > b, n)
+}
+
+pub fn top_n_by<T, F: FnMut(&T, &T) -> bool>(vec: &mut Vec<T>, mut f: F, n: usize) -> &[T] {
+ for _ in 0..n {
+ for i in 0..vec.len() - 1 {
+ if f(&vec[i], &vec[i + 1]) {
+ vec.swap(i, i + 1);
+ }
+ }
+ }
+
+ &vec[vec.len() - n..]
+}
+
+pub fn iter_pair<T>(mut i: impl Iterator<Item = T>) -> (T, T) {
+ (i.next().unwrap(), i.next().unwrap())
+}
+
+pub fn iter_triple<T>(mut i: impl Iterator<Item = T>) -> (T, T, T) {
+ (i.next().unwrap(), i.next().unwrap(), i.next().unwrap())
+}
+
+#[inline(always)]
+pub fn parse_num<T: FromStr>(string: &str) -> (T, &str)
+where
+ <T as FromStr>::Err: std::fmt::Debug,
+{
+ let count = string
+ .chars()
+ .take_while(|x| x.is_numeric() || *x == '-')
+ .count();
+
+ (string[..count].parse().unwrap(), &string[count..])
+}
+
+#[inline(always)]
+pub fn parse_static<'a>(exp: &'static str, source: &'a str) -> &'a str {
+ &source[exp.len()..]
+}