diff --git a/2024/rust/src/day6.rs b/2024/rust/src/day6.rs new file mode 100644 index 0000000..690b0d7 --- /dev/null +++ b/2024/rust/src/day6.rs @@ -0,0 +1,193 @@ +mod prelude; +pub use crate::prelude::*; + +fn main() { + let input = day_input(6); + let mut lab: Lab = input.parse().unwrap(); + let mut lab2: Lab = input.parse().unwrap(); + show_answers( + lab.num_steps_until_guard_leaves_lab(), + lab2.num_positions_of_new_obstructions_that_cause_guard_loop(), + ); +} + +#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)] +struct Vec2 { + x: i64, + y: i64, +} + +impl Vec2 { + fn new(x: i64, y: i64) -> Self { + Self { x, y } + } + + fn add(&self, v: &Vec2) -> Self { + Self::new(self.x + v.x, self.y + v.y) + } +} + +impl Display for Vec2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Vec2({}, {})", self.x, self.y) + } +} + +#[derive(Debug, Clone)] +struct Obstruction { + pos: Vec2, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct Guard { + pos: Vec2, + vel: Vec2, +} + +impl Guard { + fn turn(&mut self) { + // too lazy for trig today I guess + if self.vel.x > 0 && self.vel.y > 0 { + self.vel.x *= -1; + } else if self.vel.x < 0 && self.vel.y > 0 { + self.vel.y *= -1; + } else if self.vel.x < 0 && self.vel.y < 0 { + self.vel.x *= -1; + } else if self.vel.x > 0 && self.vel.y < 0 { + self.vel.y *= -1; + } else if self.vel.y != 0 { + self.vel.x = self.vel.y * -1; + self.vel.y = 0; + } else if self.vel.x != 0 { + self.vel.y = self.vel.x; + self.vel.x = 0; + } + } +} + +#[derive(Debug, Clone)] +struct Lab { + obstructions: Vec, + guard: Guard, + size: Vec2, +} + +impl FromStr for Lab { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + let mut size = Vec2::default(); + let mut obstructions = vec![]; + let mut guard: Option = None; + for (y, line) in s.lines().enumerate() { + size.y += 1; + size.x = line.len() as i64; // whatever - still linear ;) + for (x, c) in line.bytes().enumerate() { + match c { + b'#' => obstructions.push(Obstruction { + pos: Vec2::new(x as i64, y as i64), + }), + b'^' => { + guard = Some(Guard { + pos: Vec2::new(x as i64, y as i64), + vel: Vec2::new(0, -1), + }) + } + _ => {} + } + } + } + Ok(Lab { + size, + guard: guard.unwrap(), + obstructions, + }) + } +} + +impl Lab { + fn step(&mut self) { + // move guard + let next_pos = self.guard.pos.add(&self.guard.vel); + self.guard.pos = next_pos; + + // after moving, check if we need to turn + // TODO: obstructions should lookup by position instead of this + // broad-phase "check each one each time" slow business + while self.has_obstacle_at(&self.guard.pos.add(&self.guard.vel)) { + self.guard.turn(); + } + } + + fn contains(&self, pos: &Vec2) -> bool { + (0..self.size.x).contains(&pos.x) && (0..self.size.y).contains(&pos.y) + } + + fn num_steps_until_guard_leaves_lab(&mut self) -> usize { + let mut visited = HashSet::new(); + visited.insert(self.guard.pos.clone()); + while self.contains(&self.guard.pos) { + self.step(); + visited.insert(self.guard.pos.clone()); + } + visited.len() - 1 + } + + fn has_obstacle_at(&self, pos: &Vec2) -> bool { + self.obstructions.iter().any(|o| o.pos == *pos) + } + + fn num_positions_of_new_obstructions_that_cause_guard_loop(&mut self) -> usize { + let mut positions_with_cycles_detected = 0; + let mut visited = HashSet::new(); + visited.insert(self.guard.clone()); + // TODO: avoid super brute force + for x in 0..self.size.x { + for y in 0..self.size.y { + println!("Simulating at {x} {y}..."); + let mut alternate_visited = visited.clone(); + let mut alternate_lab = self.clone(); + alternate_lab.obstructions.push(Obstruction { + pos: Vec2::new(x, y), + }); + while alternate_lab.contains(&alternate_lab.guard.pos) { + alternate_lab.step(); + if alternate_visited.contains(&alternate_lab.guard) { + positions_with_cycles_detected += 1; + break; + } + alternate_visited.insert(alternate_lab.guard.clone()); + } + } + } + positions_with_cycles_detected + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let input = " +....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#..." + .trim(); + let mut lab: Lab = input.parse().unwrap(); + let mut lab2 = lab.clone(); + assert_eq!(lab.num_steps_until_guard_leaves_lab(), 41); + assert_eq!( + lab2.num_positions_of_new_obstructions_that_cause_guard_loop(), + 6 + ); + } +} diff --git a/2024/rust/src/prelude.rs b/2024/rust/src/prelude.rs index af3d37d..dd0080f 100644 --- a/2024/rust/src/prelude.rs +++ b/2024/rust/src/prelude.rs @@ -3,6 +3,8 @@ pub use std::{ cmp::Ordering, collections::HashMap, collections::HashSet, + convert::Infallible, + fmt::Display, fs::File, io::Read, iter::zip,