use utils::grid;
use utils::point::Point2D;
use utils::prelude::*;
#[derive(Clone, Debug)]
pub struct Day06 {
pub rows: usize,
pub cols: usize,
pub grid: Vec<u8>,
pub start: Point2D<usize>,
}
const DIRECTIONS: [Point2D<isize>; 4] = [Point2D::DOWN, Point2D::RIGHT, Point2D::UP, Point2D::LEFT];
impl Day06 {
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
let (rows, cols, mut grid) = grid::from_str(input, |b| match b {
b'.' | b'#' | b'^' => Some(b),
_ => None,
})?;
let start_index = grid.iter().position(|&c| c == b'^').unwrap();
let start = Point2D::new(start_index % cols, start_index / cols);
grid[start_index] = b'.';
Ok(Self {
rows,
cols,
grid,
start,
})
}
#[must_use]
pub fn part1(&self) -> usize {
let mut pos = self.start;
let mut dir = 0;
let mut visited = vec![false; self.grid.len()];
loop {
visited[pos.y * self.cols + pos.x] = true;
let next = pos.wrapping_add_signed(DIRECTIONS[dir]);
if next.x >= self.cols || next.y >= self.rows {
break;
}
if self.grid[next.y * self.cols + next.x] == b'#' {
dir = (dir + 1) % 4;
} else {
pos = next;
}
}
visited.iter().filter(|&&c| c).count()
}
#[must_use]
pub fn part2(&self) -> usize {
let mut pos = self.start;
let mut dir = 0;
let mut visited = vec![0u8; self.grid.len()];
let mut obstructions = vec![false; self.grid.len()];
let mut cached_step_counts = vec![[0; 4]; self.grid.len()];
loop {
visited[pos.y * self.cols + pos.x] |= 1 << dir;
let next = pos.wrapping_add_signed(DIRECTIONS[dir]);
if next.x >= self.cols || next.y >= self.rows {
break;
}
if self.grid[next.y * self.cols + next.x] == b'#' {
dir = (dir + 1) % 4;
} else {
if !obstructions[next.y * self.cols + next.x]
&& visited[next.y * self.cols + next.x] == 0
&& self.check_cycle(next, pos, dir, &visited, &mut cached_step_counts)
{
obstructions[next.y * self.cols + next.x] = true;
}
pos = next;
}
}
obstructions.iter().filter(|&&c| c).count()
}
fn check_cycle(
&self,
obstruction: Point2D<usize>,
pos: Point2D<usize>,
dir: usize,
visited: &[u8],
cache: &mut [[isize; 4]],
) -> bool {
let (mut power, mut lambda) = (1, 1);
let (mut tortoise_pos, mut tortoise_dir) = (pos, dir);
let (mut hare_pos, mut hare_dir) = (pos, dir);
loop {
if power == lambda {
tortoise_pos = hare_pos;
tortoise_dir = hare_dir;
power *= 2;
lambda = 0;
}
lambda += 1;
if hare_pos.x == obstruction.x || hare_pos.y == obstruction.y {
loop {
let next = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir]);
if next.x >= self.cols || next.y >= self.rows {
return false;
}
if self.grid[next.y * self.cols + next.x] == b'#' || next == obstruction {
break;
}
hare_pos = next;
}
} else {
let cached_count = &mut cache[hare_pos.y * self.cols + hare_pos.x][hare_dir];
if *cached_count > 0 {
hare_pos = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir] * *cached_count);
if hare_pos.x >= self.cols || hare_pos.y >= self.rows {
return false;
}
} else {
loop {
let next = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir]);
if next.x >= self.cols || next.y >= self.rows {
*cached_count += 1;
return false;
}
if self.grid[next.y * self.cols + next.x] == b'#' {
break;
}
hare_pos = next;
*cached_count += 1;
}
}
}
hare_dir = (hare_dir + 1) % 4;
if visited[hare_pos.y * self.cols + hare_pos.x] & (1 << hare_dir) != 0 {
return true;
}
if hare_pos == tortoise_pos && hare_dir == tortoise_dir {
return true;
}
}
}
}
examples!(Day06 -> (usize, usize) [
{file: "day06_example0.txt", part1: 41, part2: 6},
]);