130 lines
3.7 KiB
Rust
130 lines
3.7 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use crate::util::Dir;
|
|
|
|
pub type Motion = (Dir, usize);
|
|
|
|
pub fn parse_motions(input: &String) -> Vec<Motion> {
|
|
let mut result = Vec::new();
|
|
|
|
for line in input.lines() {
|
|
let (d, n) = line.split_once(' ').unwrap();
|
|
let dir = match d {
|
|
"L" => Dir::Left,
|
|
"R" => Dir::Right,
|
|
"U" => Dir::Up,
|
|
"D" => Dir::Down,
|
|
_ => panic!("Unknown direction {}", d),
|
|
};
|
|
let n = n.parse().unwrap();
|
|
result.push((dir, n));
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub type Coord = (i64, i64);
|
|
|
|
pub struct State {
|
|
pub head: Coord,
|
|
pub knots: Vec<Coord>,
|
|
pub visited: HashSet<Coord>,
|
|
}
|
|
|
|
impl State {
|
|
pub fn new(n_knots: usize) -> State {
|
|
let mut s = State {
|
|
head: (0, 0),
|
|
knots: vec![(0, 0); n_knots],
|
|
visited: HashSet::new(),
|
|
};
|
|
s.visited.insert((0, 0));
|
|
s
|
|
}
|
|
|
|
pub fn print(&self) {
|
|
let knots_x: Vec<i64> = self.knots.iter().map(|v| v.0).collect();
|
|
let visited_x: Vec<i64> = self.visited.iter().map(|v| v.0).collect();
|
|
let knots_y: Vec<i64> = self.knots.iter().map(|v| v.1).collect();
|
|
let visited_y: Vec<i64> = self.visited.iter().map(|v| v.1).collect();
|
|
let min_x = 0
|
|
.min(self.head.0)
|
|
.min(*knots_x.iter().min().unwrap())
|
|
.min(*visited_x.iter().min().unwrap());
|
|
let max_x = 0
|
|
.max(self.head.0)
|
|
.max(*knots_x.iter().max().unwrap())
|
|
.max(*visited_x.iter().max().unwrap());
|
|
let min_y = 0
|
|
.min(self.head.1)
|
|
.min(*knots_y.iter().min().unwrap())
|
|
.min(*visited_y.iter().min().unwrap());
|
|
let max_y = 0
|
|
.max(self.head.1)
|
|
.max(*knots_y.iter().max().unwrap())
|
|
.max(*visited_y.iter().max().unwrap());
|
|
|
|
for y in min_y..max_y + 1 {
|
|
'x: for x in min_x..max_x + 1 {
|
|
let c = (x, y);
|
|
if self.head == c {
|
|
print!("H");
|
|
} else {
|
|
for (i, knot) in self.knots.iter().enumerate() {
|
|
if c == *knot {
|
|
print!("{}", i + 1);
|
|
continue 'x;
|
|
}
|
|
}
|
|
if self.visited.contains(&c) {
|
|
print!("#");
|
|
} else if c == (0, 0) {
|
|
print!("s");
|
|
} else {
|
|
print!(".");
|
|
}
|
|
}
|
|
}
|
|
print!("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn execute_motion(state: &mut State, motion: &Motion) {
|
|
for _ in 0..motion.1 {
|
|
let head = &mut state.head;
|
|
*head = match motion.0 {
|
|
Dir::Left => (head.0 - 1, head.1),
|
|
Dir::Right => (head.0 + 1, head.1),
|
|
Dir::Up => (head.0, head.1 - 1),
|
|
Dir::Down => (head.0, head.1 + 1),
|
|
};
|
|
tail_catchup(state);
|
|
state.visited.insert(*state.knots.last().unwrap());
|
|
}
|
|
}
|
|
|
|
pub fn tail_catchup(state: &mut State) {
|
|
let mut prev = state.head.clone();
|
|
let knots = &mut state.knots;
|
|
for i in 0..knots.len() {
|
|
let knot = knots.get_mut(i).unwrap();
|
|
let dx = prev.0 - knot.0;
|
|
let dy = prev.1 - knot.1;
|
|
if dx > 1 {
|
|
knot.0 += 1;
|
|
knot.1 += dy / dy.abs().max(1);
|
|
} else if dx < -1 {
|
|
knot.0 -= 1;
|
|
knot.1 += dy / dy.abs().max(1);
|
|
} else if dy > 1 {
|
|
knot.1 += 1;
|
|
knot.0 += dx / dx.abs().max(1);
|
|
} else if dy < -1 {
|
|
knot.1 -= 1;
|
|
knot.0 += dx / dx.abs().max(1);
|
|
}
|
|
prev = knot.clone();
|
|
}
|
|
}
|