|
@@ -0,0 +1,122 @@
|
|
|
+use std::{collections::HashSet};
|
|
|
+
|
|
|
+#[derive(Clone, Hash, Eq, PartialEq)]
|
|
|
+struct Point {
|
|
|
+ pub x: i32,
|
|
|
+ pub y: i32
|
|
|
+}
|
|
|
+
|
|
|
+struct RopeGrid {
|
|
|
+ head: Point,
|
|
|
+ tail: Point,
|
|
|
+ tail_positions: HashSet<(i32, i32)>
|
|
|
+}
|
|
|
+
|
|
|
+impl RopeGrid {
|
|
|
+ pub fn new() -> RopeGrid {
|
|
|
+ return RopeGrid { head: Point{x: 0, y: 0}, tail: Point{ x: 0, y: 0}, tail_positions: HashSet::from_iter([(0, 0)]) }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn update_tail(&mut self) {
|
|
|
+ if self.head.x == self.tail.x && i32::abs(self.head.y - self.tail.y) == 2 {
|
|
|
+ // head 10, tail 8, head is above tail, dir is +
|
|
|
+ if self.head.y > self.tail.y {
|
|
|
+ self.tail.y += 1;
|
|
|
+ } else {
|
|
|
+ self.tail.y -= 1;
|
|
|
+ }
|
|
|
+ } else if self.head.y == self.tail.y && i32::abs(self.head.x - self.tail.x) == 2 {
|
|
|
+ if self.head.x > self.tail.x {
|
|
|
+ self.tail.x += 1;
|
|
|
+ } else {
|
|
|
+ self.tail.x -= 1;
|
|
|
+ }
|
|
|
+ } else if i32::abs(self.head.x - self.tail.x) + i32::abs(self.head.y - self.tail.y) > 2 {
|
|
|
+ let x_dir = if self.head.x > self.tail.x { 1 } else { -1 };
|
|
|
+ let y_dir = if self.head.y > self.tail.y { 1 } else { -1 };
|
|
|
+ self.tail.x += x_dir;
|
|
|
+ self.tail.y += y_dir;
|
|
|
+ }
|
|
|
+
|
|
|
+ // println!("Tail now at ({}, {})", self.tail.x, self.tail.y);
|
|
|
+ self.tail_positions.insert((self.tail.x, self.tail.y));
|
|
|
+ }
|
|
|
+
|
|
|
+ fn do_steps(&mut self, x_sign: i32, y_sign: i32, dist: i32) {
|
|
|
+ // println!("Moving x {} y {} steps {}", x_sign, y_sign, dist);
|
|
|
+ for _ in 0..dist {
|
|
|
+ self.head.x += x_sign;
|
|
|
+ self.head.y += y_sign;
|
|
|
+ self.update_tail();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn move_head(&mut self, moov: Move) {
|
|
|
+ match moov {
|
|
|
+ Move::Up(distance) => { self.do_steps(0, 1, distance)},
|
|
|
+ Move::Down(distance) => { self.do_steps(0, -1, distance)},
|
|
|
+ Move::Left(distance) => { self.do_steps(-1, 0, distance)},
|
|
|
+ Move::Right(distance) => { self.do_steps(1, 0, distance)},
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn num_tail_positions(&self) -> usize {
|
|
|
+ self.tail_positions.len()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+enum Move {
|
|
|
+ Right(i32),
|
|
|
+ Left(i32),
|
|
|
+ Up(i32),
|
|
|
+ Down(i32)
|
|
|
+}
|
|
|
+
|
|
|
+impl Move {
|
|
|
+ pub fn from_stirng(s: &str) -> Move {
|
|
|
+ let (direction, steps) = s.split_once(" ").unwrap();
|
|
|
+ match direction {
|
|
|
+ "U" => { Move::Up(steps.parse().unwrap())},
|
|
|
+ "D" => { Move::Down(steps.parse().unwrap())},
|
|
|
+ "L" => { Move::Left(steps.parse().unwrap())},
|
|
|
+ "R" => { Move::Right(steps.parse().unwrap())},
|
|
|
+ _ => panic!("Unknown direction {}", direction)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub fn part_one(input: &str) -> Option<usize> {
|
|
|
+ let moves: Vec<Move> = input.lines().map(|line| Move::from_stirng(line)).collect();
|
|
|
+ let mut ropegrid = RopeGrid::new();
|
|
|
+ for moov in moves {
|
|
|
+ ropegrid.move_head(moov)
|
|
|
+ }
|
|
|
+ Some(ropegrid.num_tail_positions())
|
|
|
+}
|
|
|
+
|
|
|
+pub fn part_two(input: &str) -> Option<usize> {
|
|
|
+ None
|
|
|
+}
|
|
|
+
|
|
|
+fn main() {
|
|
|
+ let input = &advent_of_code::read_file("inputs", 9);
|
|
|
+ advent_of_code::solve!(1, part_one, input);
|
|
|
+ advent_of_code::solve!(2, part_two, input);
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_part_one() {
|
|
|
+ let input = advent_of_code::read_file("examples", 9);
|
|
|
+ assert_eq!(part_one(&input), Some(13));
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_part_two() {
|
|
|
+ let input = advent_of_code::read_file("examples", 9);
|
|
|
+ assert_eq!(part_two(&input), None);
|
|
|
+ }
|
|
|
+}
|