diff --git a/day17/Cargo.lock b/day17/Cargo.lock new file mode 100644 index 0000000..5165014 --- /dev/null +++ b/day17/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "day17" +version = "0.1.0" +dependencies = [ + "md5", +] + +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" diff --git a/day17/Cargo.toml b/day17/Cargo.toml new file mode 100644 index 0000000..8543f1f --- /dev/null +++ b/day17/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "day17" +version = "0.1.0" +edition = "2024" + +[dependencies] +md5 = "*" diff --git a/day17/src/main.rs b/day17/src/main.rs new file mode 100644 index 0000000..59ad258 --- /dev/null +++ b/day17/src/main.rs @@ -0,0 +1,154 @@ +use std::{ + collections::{HashSet, VecDeque}, + fmt::Display, +}; + +#[cfg(debug_assertions)] +// const PASSCODE: &str = "ihgpwlah"; +const PASSCODE: &str = "ulqzkmiv"; + +#[cfg(not(debug_assertions))] +const PASSCODE: &str = "hhhxzeay"; + +fn main() { + let state = State::new(); + + let result = bfs(state); + println!("{result}"); +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct State { + route: Route, + position: (usize, usize), +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct Route(Vec); + +impl Display for Route { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for dir in &self.0 { + let the_char = match dir { + Direction::Up => 'U', + Direction::Down => 'D', + Direction::Left => 'L', + Direction::Right => 'R', + }; + write!(f, "{the_char}")?; + } + + Ok(()) + } +} + +impl Route { + fn new() -> Self { + Self(vec![]) + } + + fn push(&mut self, dir: Direction) { + self.0.push(dir); + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum Direction { + Up, + Down, + Left, + Right, +} + +impl State { + fn new() -> Self { + Self { + route: Route::new(), + position: (0, 0), + } + } + + fn is_goal_state(&self) -> bool { + self.position == (3, 3) + } + + fn neighbor_states(&self, passcode: &str) -> impl Iterator { + let input = format!("{}{}", passcode, self.route); + + let mut hash = format!("{:x}", md5::compute(input)); + hash.truncate(4); + + let possible_directions = self.generate_directions_from_hash(&hash); + + let mut result = vec![]; + + for direction in possible_directions { + let mut new_state = self.clone(); + new_state.update_position(&direction); + + new_state.route.push(direction); + result.push(new_state) + } + result.into_iter() + } + + fn generate_directions_from_hash(&self, hash: &str) -> impl Iterator { + hash.chars() + .zip([ + Direction::Up, + Direction::Down, + Direction::Left, + Direction::Right, + ]) + .filter(|(c, _)| ('b'..='f').contains(c)) + .map(|(_, dir)| dir) + .filter(|dir| match dir { + Direction::Up => self.position.1 > 0, + Direction::Down => self.position.1 < 3, + Direction::Left => self.position.0 > 0, + Direction::Right => self.position.0 < 3, + }) + } + + fn update_position(&mut self, dir: &Direction) { + match *dir { + Direction::Up => { + self.position.1 -= 1; + } + Direction::Down => { + self.position.1 += 1; + } + Direction::Left => { + self.position.0 -= 1; + } + Direction::Right => { + self.position.0 += 1; + } + } + } +} + +fn bfs(start: State) -> Route { + let mut queue = VecDeque::new(); + let mut visited = HashSet::new(); + visited.insert(start.clone()); + + queue.push_back(start); + + while let Some(current_state) = queue.pop_front() { + if current_state.is_goal_state() { + // return current_state.route; + continue; + } + + for neighbor in current_state.neighbor_states(PASSCODE) { + println!("{} {}", neighbor.route.0.len(), neighbor.is_goal_state()); + if !visited.contains(&neighbor) { + visited.insert(neighbor.clone()); + queue.push_back(neighbor); + } + } + } + + panic!("goal state unreachable"); +}