2022-12-13 14:11:55 -05:00
|
|
|
use std::collections::HashSet;
|
2022-12-09 17:43:10 -05:00
|
|
|
|
2022-12-13 14:11:55 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum Direction {
|
|
|
|
Up,
|
|
|
|
Down,
|
|
|
|
Left,
|
|
|
|
Right,
|
|
|
|
}
|
|
|
|
use Direction::*;
|
|
|
|
|
|
|
|
struct Move {
|
|
|
|
dir: Direction,
|
2022-12-13 16:19:26 -05:00
|
|
|
amount: isize,
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Move {
|
|
|
|
fn from_line(line: &str) -> Self {
|
|
|
|
let parts: Vec<&str> = line.split_ascii_whitespace().collect();
|
|
|
|
|
|
|
|
let dir = match parts[0] {
|
|
|
|
"U" => Up,
|
|
|
|
"D" => Down,
|
|
|
|
"L" => Left,
|
|
|
|
"R" => Right,
|
|
|
|
_ => panic!("Invalid direction!"),
|
|
|
|
};
|
2022-12-13 16:19:26 -05:00
|
|
|
let amount = parts[1].parse::<isize>().unwrap();
|
2022-12-13 14:11:55 -05:00
|
|
|
|
|
|
|
Move { dir, amount }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq)]
|
|
|
|
struct Location {
|
2022-12-13 16:19:26 -05:00
|
|
|
x: isize,
|
|
|
|
y: isize,
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Location {
|
2022-12-13 16:19:26 -05:00
|
|
|
fn new(x: isize, y: isize) -> Self {
|
2022-12-13 14:11:55 -05:00
|
|
|
Location { x, y }
|
|
|
|
}
|
2022-12-13 16:19:26 -05:00
|
|
|
|
|
|
|
fn get_distance(self, other: Self) -> f64 {
|
|
|
|
let squares = (other.x - self.x).pow(2) + (other.y - self.y).pow(2);
|
|
|
|
|
|
|
|
(squares as f64).sqrt()
|
|
|
|
}
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|
|
|
|
|
2022-12-13 16:19:26 -05:00
|
|
|
// ---------------------------------------------------------------------------
|
2022-12-13 14:11:55 -05:00
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
struct Rope {
|
2022-12-13 17:11:45 -05:00
|
|
|
knots: Vec<Location>,
|
|
|
|
knot_count: usize,
|
2022-12-13 14:11:55 -05:00
|
|
|
head_visited: HashSet<Location>,
|
|
|
|
tail_visited: HashSet<Location>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Rope {
|
2022-12-13 17:11:45 -05:00
|
|
|
pub fn new(knot_count: usize) -> Self {
|
2022-12-13 16:19:26 -05:00
|
|
|
let mut rope = Self::default();
|
2022-12-13 17:11:45 -05:00
|
|
|
rope.knot_count = knot_count;
|
2022-12-13 16:19:26 -05:00
|
|
|
rope.head_visited.insert(Location::default());
|
|
|
|
rope.tail_visited.insert(Location::default());
|
2022-12-13 17:11:45 -05:00
|
|
|
rope.knots.resize_with(knot_count, Location::default);
|
2022-12-13 16:19:26 -05:00
|
|
|
|
|
|
|
rope
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
pub fn get_knot(&self, idx: usize) -> Location {
|
|
|
|
self.knots[idx]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_tail(&self, idx: usize) -> bool {
|
|
|
|
idx == (self.knot_count - 1)
|
|
|
|
}
|
|
|
|
|
2022-12-13 14:11:55 -05:00
|
|
|
pub fn move_head(&mut self, moves: Move) {
|
|
|
|
for _ in 0..moves.amount {
|
2022-12-13 17:11:45 -05:00
|
|
|
let mut x = self.knots[0].x;
|
|
|
|
let mut y = self.knots[0].y;
|
2022-12-13 14:11:55 -05:00
|
|
|
|
|
|
|
match moves.dir {
|
2022-12-13 16:19:26 -05:00
|
|
|
Up => {
|
|
|
|
y += 1;
|
|
|
|
}
|
|
|
|
Down => {
|
|
|
|
y -= 1;
|
|
|
|
}
|
|
|
|
Left => {
|
|
|
|
x -= 1;
|
|
|
|
}
|
|
|
|
Right => {
|
|
|
|
x += 1;
|
|
|
|
}
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
let to = Location::new(x, y);
|
2022-12-13 17:11:45 -05:00
|
|
|
self.knots[0] = to;
|
2022-12-13 14:11:55 -05:00
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
for i in 1..self.knot_count {
|
|
|
|
self.move_knot(i, i - 1);
|
|
|
|
}
|
|
|
|
// self.head = to;
|
|
|
|
// self.head_visited.insert(to);
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
fn must_move(&mut self, current: usize, prev: usize) -> bool {
|
|
|
|
let distance = self.get_knot(current).get_distance(self.get_knot(prev));
|
2022-12-13 16:19:26 -05:00
|
|
|
|
|
|
|
distance >= 2.0
|
|
|
|
}
|
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
fn move_knot(&mut self, c: usize, p: usize) {
|
|
|
|
if !self.must_move(c, p) {
|
2022-12-13 16:19:26 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
let mut current = self.get_knot(c);
|
|
|
|
let prev = self.get_knot(p);
|
2022-12-13 16:19:26 -05:00
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
if current.y != prev.y {
|
|
|
|
if prev.y - current.y < 0 {
|
|
|
|
current.y -= 1;
|
2022-12-13 16:19:26 -05:00
|
|
|
} else {
|
2022-12-13 17:11:45 -05:00
|
|
|
current.y += 1;
|
2022-12-13 16:19:26 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
if current.x != prev.x {
|
|
|
|
if prev.x - current.x < 0 {
|
|
|
|
current.x -= 1;
|
2022-12-13 16:19:26 -05:00
|
|
|
} else {
|
2022-12-13 17:11:45 -05:00
|
|
|
current.x += 1;
|
2022-12-13 16:19:26 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
self.knots[c] = current;
|
|
|
|
if self.is_tail(c) {
|
|
|
|
self.tail_visited.insert(current);
|
|
|
|
}
|
2022-12-13 16:19:26 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_tail_pos_count(&self) -> usize {
|
|
|
|
self.tail_visited.len()
|
|
|
|
}
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|
2022-12-09 17:43:10 -05:00
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let file_str = include_str!("input.txt");
|
2022-12-13 17:11:45 -05:00
|
|
|
|
|
|
|
let mut rope = Rope::new(2);
|
2022-12-13 14:11:55 -05:00
|
|
|
|
|
|
|
file_str
|
|
|
|
.lines()
|
|
|
|
.map(Move::from_line)
|
|
|
|
.for_each(|m| rope.move_head(m));
|
2022-12-13 16:19:26 -05:00
|
|
|
|
|
|
|
let tail_positions = rope.get_tail_pos_count();
|
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
println!(
|
|
|
|
"Part 1: Number of tail movements with 2 knots: {}",
|
|
|
|
tail_positions
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut rope = Rope::new(10);
|
|
|
|
|
|
|
|
file_str
|
|
|
|
.lines()
|
|
|
|
.map(Move::from_line)
|
|
|
|
.for_each(|m| rope.move_head(m));
|
|
|
|
|
|
|
|
let tail_positions = rope.get_tail_pos_count();
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"Part 2: Number of tail movements with 10 knots: {}",
|
|
|
|
tail_positions
|
|
|
|
);
|
2022-12-09 17:43:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
2022-12-13 16:19:26 -05:00
|
|
|
#[test]
|
|
|
|
fn test_location_get_distance() {
|
|
|
|
let a = Location::new(0, 0);
|
|
|
|
|
|
|
|
assert_eq!(a.get_distance(Location::new(0, 0)), 0.0);
|
|
|
|
assert_eq!(a.get_distance(Location::new(1, 0)), 1.0);
|
|
|
|
assert_eq!(a.get_distance(Location::new(1, 1)), 2.0f64.sqrt());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_tail_position_count() {
|
2022-12-13 17:11:45 -05:00
|
|
|
let file_str = include_str!("test-input.txt");
|
|
|
|
let mut rope = Rope::new(2);
|
2022-12-13 16:19:26 -05:00
|
|
|
|
2022-12-13 17:11:45 -05:00
|
|
|
file_str
|
2022-12-13 16:19:26 -05:00
|
|
|
.lines()
|
|
|
|
.map(Move::from_line)
|
|
|
|
.for_each(|m| rope.move_head(m));
|
|
|
|
|
|
|
|
assert_eq!(rope.get_tail_pos_count(), 13);
|
|
|
|
}
|
2022-12-13 17:11:45 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_tail_position_count_10_knots() {
|
|
|
|
let file_str = include_str!("test-input2.txt");
|
|
|
|
let mut rope = Rope::new(10);
|
|
|
|
|
|
|
|
file_str
|
|
|
|
.lines()
|
|
|
|
.map(Move::from_line)
|
|
|
|
.for_each(|m| rope.move_head(m));
|
|
|
|
|
|
|
|
assert_eq!(rope.get_tail_pos_count(), 36);
|
|
|
|
}
|
2022-12-13 14:11:55 -05:00
|
|
|
}
|