diff --git a/2022/day9/rust/src/main.rs b/2022/day9/rust/src/main.rs index 151dc7a..e7f6551 100644 --- a/2022/day9/rust/src/main.rs +++ b/2022/day9/rust/src/main.rs @@ -3,113 +3,63 @@ use std::{ collections::BTreeSet, io::{stdin, Read}, - ops::{Add, AddAssign, Sub}, }; -use aoc::*; +use aoc::vec2::{Direction, Vec2}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Vec2 { - x: i32, // increases toward right - y: i32, // increases toward top +fn tail_movement(following: Vec2, tail: Vec2) -> Vec2 { + let (dx, dy) = (following - tail).into(); + + let movement = if dx.abs() <= 1 && dy.abs() <= 1 { + (0, 0) + } else { + (dx.signum(), dy.signum()) + }; + movement.into() } -impl Vec2 { - #[must_use] - pub fn new(x: i32, y: i32) -> Self { - Self { x, y } - } +fn simulate_knots(num_knots: usize, instructions: &[(Direction, usize)]) -> usize { + const START: Vec2 = Vec2::new(0, 0); - #[must_use] - pub fn map(self, mut f: impl FnMut(i32) -> i32) -> Self { - Self { - x: f(self.x), - y: f(self.y), + assert!(num_knots >= 2); + + let mut knots = vec![START; num_knots]; + let mut tail_positions = BTreeSet::new(); + tail_positions.insert(START); + + for (direction, distance) in instructions { + for _i in 0..*distance { + knots[0] += Vec2::from(*direction); + + let tail = knots + .iter_mut() + .reduce(|following, knot| { + *knot += tail_movement(*following, *knot); + knot + }) + .unwrap(); + + tail_positions.insert(*tail); } } -} -impl Add for Vec2 { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self { - x: self.x + rhs.x, - y: self.y + rhs.y, - } - } -} - -impl AddAssign for Vec2 { - fn add_assign(&mut self, rhs: Self) { - self.x += rhs.x; - self.y += rhs.y; - } -} - -impl Sub for Vec2 { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self { - x: self.x - rhs.x, - y: self.y - rhs.y, - } - } -} - -impl From<(i32, i32)> for Vec2 { - fn from((x, y): (i32, i32)) -> Self { - Self { x, y } - } -} - -impl From for (i32, i32) { - fn from(v: Vec2) -> Self { - (v.x, v.y) - } -} - -fn get_direction_vec(direction: &str) -> Vec2 { - match direction { - "U" => (0, 1), - "R" => (1, 0), - "D" => (0, -1), - "L" => (-1, 0), - _ => panic!(), - } - .into() + tail_positions.len() } fn main() { let mut data = String::new(); stdin().read_to_string(&mut data).unwrap(); - let mut knots = vec![Vec2::new(0, 0); 10]; + let instructions: Vec<_> = data + .lines() + .map(|line| { + let (direction, distance) = line.split_once(' ').unwrap(); + let direction = direction.parse().unwrap(); + let distance = distance.parse().unwrap(); + (direction, distance) + }) + .collect(); - let mut tail_positions = BTreeSet::new(); - tail_positions.insert(*knots.last().unwrap()); - - for line in data.lines() { - let (direction, distance) = line.split_once(' ').unwrap(); - let distance: usize = distance.parse().unwrap(); - - for _i in 0..distance { - knots[0] += get_direction_vec(direction); - let mut following = knots[0]; - - for knot in &mut knots[1..] { - let delta = match (following - *knot).into() { - (x, y) if x.abs() <= 1 && y.abs() <= 1 => (0, 0), - (x, y) => (x.signum(), y.signum()), - } - .into(); - *knot += delta; - following = *knot; - } - tail_positions.insert(*knots.last().unwrap()); - } - } - - println!("{:?}", tail_positions.len()); + println!("{:?}", simulate_knots(2, &instructions)); + println!("{:?}", simulate_knots(10, &instructions)); } diff --git a/common/rust/src/lib.rs b/common/rust/src/lib.rs index 8be2e20..c79c26f 100644 --- a/common/rust/src/lib.rs +++ b/common/rust/src/lib.rs @@ -1,6 +1,7 @@ #![warn(clippy::pedantic)] mod section_range; +pub mod vec2; use std::{mem, path::Path}; diff --git a/common/rust/src/vec2.rs b/common/rust/src/vec2.rs new file mode 100644 index 0000000..d05e710 --- /dev/null +++ b/common/rust/src/vec2.rs @@ -0,0 +1,108 @@ +use std::{ + ops::{Add, AddAssign, Sub, SubAssign}, + str::FromStr, +}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Vec2 { + x: i32, // increases toward right + y: i32, // increases toward top +} + +impl Vec2 { + #[must_use] + pub const fn new(x: i32, y: i32) -> Self { + Self { x, y } + } + + #[must_use] + pub fn map(self, mut f: impl FnMut(i32) -> i32) -> Self { + Self { + x: f(self.x), + y: f(self.y), + } + } +} + +impl Add for Vec2 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl AddAssign for Vec2 { + fn add_assign(&mut self, rhs: Self) { + self.x += rhs.x; + self.y += rhs.y; + } +} + +impl Sub for Vec2 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl SubAssign for Vec2 { + fn sub_assign(&mut self, rhs: Self) { + self.x -= rhs.x; + self.y -= rhs.y; + } +} + +impl From<(i32, i32)> for Vec2 { + fn from((x, y): (i32, i32)) -> Self { + Self { x, y } + } +} + +impl From for (i32, i32) { + fn from(v: Vec2) -> Self { + (v.x, v.y) + } +} + +impl From for Vec2 { + /// Creates a unit vector in the given direction. + fn from(dir: Direction) -> Self { + match dir { + Direction::Up => (0, 1), + Direction::Down => (0, -1), + Direction::Left => (-1, 0), + Direction::Right => (1, 0), + } + .into() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + +impl FromStr for Direction { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "U" => Ok(Direction::Up), + "D" => Ok(Direction::Down), + "L" => Ok(Direction::Left), + "R" => Ok(Direction::Right), + _ => Err(()), + } + } +}