summary refs log tree commit diff homepage
path: root/src/bin
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2016-12-18 14:47:19 -0500
committerJune McEnroe <june@causal.agency>2020-11-22 00:14:25 -0500
commitda115b01ba0749fb8745c1360554bbb18dd56a29 (patch)
tree2800a3e2667b62172cc301cf222c4ec45e7db102 /src/bin
parentDay 18 part 2 (diff)
downloadaoc-da115b01ba0749fb8745c1360554bbb18dd56a29.tar.gz
aoc-da115b01ba0749fb8745c1360554bbb18dd56a29.zip
Day 17
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/day17.rs122
1 files changed, 122 insertions, 0 deletions
diff --git a/src/bin/day17.rs b/src/bin/day17.rs
new file mode 100644
index 0000000..a80cbb8
--- /dev/null
+++ b/src/bin/day17.rs
@@ -0,0 +1,122 @@
+extern crate crypto;
+
+use std::collections::VecDeque;
+use std::io::{self, Read};
+
+use crypto::digest::Digest;
+use crypto::md5::Md5;
+
+#[derive(Clone, Copy)]
+enum Direction {
+    Up,
+    Down,
+    Left,
+    Right,
+}
+
+const DIRECTIONS: &'static [Direction] = &[
+    Direction::Up,
+    Direction::Down,
+    Direction::Left,
+    Direction::Right,
+];
+
+impl From<Direction> for char {
+    fn from(direction: Direction) -> Self {
+        match direction {
+            Direction::Up => 'U',
+            Direction::Down => 'D',
+            Direction::Left => 'L',
+            Direction::Right => 'R',
+        }
+    }
+}
+
+#[derive(Default, Clone, Copy)]
+struct Room {
+    x: u8,
+    y: u8,
+}
+
+impl Room {
+    fn neighbor(self, direction: Direction) -> Option<Room> {
+        match direction {
+            Direction::Up    if self.y != 0 => Some(Room { x: self.x, y: self.y - 1 }),
+            Direction::Down  if self.y != 3 => Some(Room { x: self.x, y: self.y + 1 }),
+            Direction::Left  if self.x != 0 => Some(Room { x: self.x - 1, y: self.y }),
+            Direction::Right if self.x != 3 => Some(Room { x: self.x + 1, y: self.y }),
+            _ => None,
+        }
+    }
+
+    fn is_vault(self) -> bool {
+        self.x == 3 && self.y == 3
+    }
+}
+
+#[derive(Default, Clone)]
+struct State {
+    room: Room,
+    path: Vec<Direction>,
+}
+
+impl<'a> From<&'a State> for String {
+    fn from(state: &'a State) -> String {
+        state.path.iter().cloned().map(char::from).collect()
+    }
+}
+
+impl State {
+    fn is_vault(&self) -> bool {
+        self.room.is_vault()
+    }
+
+    fn generate_states(&self, states: &mut VecDeque<State>, passcode: &str) {
+        let mut md5 = Md5::new();
+        md5.input_str(passcode);
+        md5.input_str(&String::from(self));
+        let hash = md5.result_str();
+
+        for (direction, ch) in DIRECTIONS.iter().cloned().zip(hash.chars()) {
+            if ch < 'b' || ch > 'f' { continue }
+
+            let room = match self.room.neighbor(direction) {
+                Some(r) => r,
+                None => continue,
+            };
+
+            let mut path = self.path.clone();
+            path.push(direction);
+
+            states.push_back(State { room: room, path: path });
+        }
+    }
+}
+
+fn solve(passcode: &str) -> String {
+    let mut states = VecDeque::new();
+    states.push_back(State::default());
+
+    while let Some(state) = states.pop_front() {
+        if state.is_vault() {
+            return String::from(&state);
+        }
+        state.generate_states(&mut states, passcode);
+    }
+
+    panic!("no path to vault")
+}
+
+fn main() {
+    let mut input = String::new();
+    io::stdin().read_to_string(&mut input).unwrap();
+
+    println!("Part 1: {}", solve(input.trim()));
+}
+
+#[test]
+fn part1() {
+    assert_eq!("DDRRRD", solve("ihgpwlah"));
+    assert_eq!("DDUDRLRRUDRD", solve("kglvqrro"));
+    assert_eq!("DRURDRUDDLLDLUURRDULRLDUUDDDRR", solve("ulqzkmiv"));
+}