Now it gets ugly
This commit is contained in:
parent
3d78aaee63
commit
e5669fac3b
193
2024/rust/src/day6.rs
Normal file
193
2024/rust/src/day6.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
mod prelude;
|
||||
pub use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let input = day_input(6);
|
||||
let mut lab: Lab = input.parse().unwrap();
|
||||
let mut lab2: Lab = input.parse().unwrap();
|
||||
show_answers(
|
||||
lab.num_steps_until_guard_leaves_lab(),
|
||||
lab2.num_positions_of_new_obstructions_that_cause_guard_loop(),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)]
|
||||
struct Vec2 {
|
||||
x: i64,
|
||||
y: i64,
|
||||
}
|
||||
|
||||
impl Vec2 {
|
||||
fn new(x: i64, y: i64) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
fn add(&self, v: &Vec2) -> Self {
|
||||
Self::new(self.x + v.x, self.y + v.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Vec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Vec2({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Obstruction {
|
||||
pos: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
struct Guard {
|
||||
pos: Vec2,
|
||||
vel: Vec2,
|
||||
}
|
||||
|
||||
impl Guard {
|
||||
fn turn(&mut self) {
|
||||
// too lazy for trig today I guess
|
||||
if self.vel.x > 0 && self.vel.y > 0 {
|
||||
self.vel.x *= -1;
|
||||
} else if self.vel.x < 0 && self.vel.y > 0 {
|
||||
self.vel.y *= -1;
|
||||
} else if self.vel.x < 0 && self.vel.y < 0 {
|
||||
self.vel.x *= -1;
|
||||
} else if self.vel.x > 0 && self.vel.y < 0 {
|
||||
self.vel.y *= -1;
|
||||
} else if self.vel.y != 0 {
|
||||
self.vel.x = self.vel.y * -1;
|
||||
self.vel.y = 0;
|
||||
} else if self.vel.x != 0 {
|
||||
self.vel.y = self.vel.x;
|
||||
self.vel.x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Lab {
|
||||
obstructions: Vec<Obstruction>,
|
||||
guard: Guard,
|
||||
size: Vec2,
|
||||
}
|
||||
|
||||
impl FromStr for Lab {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut size = Vec2::default();
|
||||
let mut obstructions = vec![];
|
||||
let mut guard: Option<Guard> = None;
|
||||
for (y, line) in s.lines().enumerate() {
|
||||
size.y += 1;
|
||||
size.x = line.len() as i64; // whatever - still linear ;)
|
||||
for (x, c) in line.bytes().enumerate() {
|
||||
match c {
|
||||
b'#' => obstructions.push(Obstruction {
|
||||
pos: Vec2::new(x as i64, y as i64),
|
||||
}),
|
||||
b'^' => {
|
||||
guard = Some(Guard {
|
||||
pos: Vec2::new(x as i64, y as i64),
|
||||
vel: Vec2::new(0, -1),
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Lab {
|
||||
size,
|
||||
guard: guard.unwrap(),
|
||||
obstructions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Lab {
|
||||
fn step(&mut self) {
|
||||
// move guard
|
||||
let next_pos = self.guard.pos.add(&self.guard.vel);
|
||||
self.guard.pos = next_pos;
|
||||
|
||||
// after moving, check if we need to turn
|
||||
// TODO: obstructions should lookup by position instead of this
|
||||
// broad-phase "check each one each time" slow business
|
||||
while self.has_obstacle_at(&self.guard.pos.add(&self.guard.vel)) {
|
||||
self.guard.turn();
|
||||
}
|
||||
}
|
||||
|
||||
fn contains(&self, pos: &Vec2) -> bool {
|
||||
(0..self.size.x).contains(&pos.x) && (0..self.size.y).contains(&pos.y)
|
||||
}
|
||||
|
||||
fn num_steps_until_guard_leaves_lab(&mut self) -> usize {
|
||||
let mut visited = HashSet::new();
|
||||
visited.insert(self.guard.pos.clone());
|
||||
while self.contains(&self.guard.pos) {
|
||||
self.step();
|
||||
visited.insert(self.guard.pos.clone());
|
||||
}
|
||||
visited.len() - 1
|
||||
}
|
||||
|
||||
fn has_obstacle_at(&self, pos: &Vec2) -> bool {
|
||||
self.obstructions.iter().any(|o| o.pos == *pos)
|
||||
}
|
||||
|
||||
fn num_positions_of_new_obstructions_that_cause_guard_loop(&mut self) -> usize {
|
||||
let mut positions_with_cycles_detected = 0;
|
||||
let mut visited = HashSet::new();
|
||||
visited.insert(self.guard.clone());
|
||||
// TODO: avoid super brute force
|
||||
for x in 0..self.size.x {
|
||||
for y in 0..self.size.y {
|
||||
println!("Simulating at {x} {y}...");
|
||||
let mut alternate_visited = visited.clone();
|
||||
let mut alternate_lab = self.clone();
|
||||
alternate_lab.obstructions.push(Obstruction {
|
||||
pos: Vec2::new(x, y),
|
||||
});
|
||||
while alternate_lab.contains(&alternate_lab.guard.pos) {
|
||||
alternate_lab.step();
|
||||
if alternate_visited.contains(&alternate_lab.guard) {
|
||||
positions_with_cycles_detected += 1;
|
||||
break;
|
||||
}
|
||||
alternate_visited.insert(alternate_lab.guard.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
positions_with_cycles_detected
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let input = "
|
||||
....#.....
|
||||
.........#
|
||||
..........
|
||||
..#.......
|
||||
.......#..
|
||||
..........
|
||||
.#..^.....
|
||||
........#.
|
||||
#.........
|
||||
......#..."
|
||||
.trim();
|
||||
let mut lab: Lab = input.parse().unwrap();
|
||||
let mut lab2 = lab.clone();
|
||||
assert_eq!(lab.num_steps_until_guard_leaves_lab(), 41);
|
||||
assert_eq!(
|
||||
lab2.num_positions_of_new_obstructions_that_cause_guard_loop(),
|
||||
6
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ pub use std::{
|
|||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
collections::HashSet,
|
||||
convert::Infallible,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::Read,
|
||||
iter::zip,
|
||||
|
|
Loading…
Reference in a new issue