1use utils::grid;
2use utils::point::Point2D;
3use utils::prelude::*;
4
5#[derive(Clone, Debug)]
7pub struct Day06 {
8 pub rows: usize,
9 pub cols: usize,
10 pub grid: Vec<u8>,
11 pub start: Point2D<usize>,
12}
13
14const DIRECTIONS: [Point2D<isize>; 4] = [Point2D::DOWN, Point2D::RIGHT, Point2D::UP, Point2D::LEFT];
15
16impl Day06 {
17 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
18 let (rows, cols, mut grid) = grid::from_str(input, |b| match b {
19 b'.' | b'#' | b'^' => Some(b),
20 _ => None,
21 })?;
22
23 let start_index = grid.iter().position(|&c| c == b'^').unwrap();
24 let start = Point2D::new(start_index % cols, start_index / cols);
25 grid[start_index] = b'.';
26
27 Ok(Self {
28 rows,
29 cols,
30 grid,
31 start,
32 })
33 }
34
35 #[must_use]
36 pub fn part1(&self) -> usize {
37 let mut pos = self.start;
38 let mut dir = 0;
39 let mut visited = vec![false; self.grid.len()];
40 loop {
41 visited[pos.y * self.cols + pos.x] = true;
42
43 let next = pos.wrapping_add_signed(DIRECTIONS[dir]);
44 if next.x >= self.cols || next.y >= self.rows {
45 break;
46 }
47 if self.grid[next.y * self.cols + next.x] == b'#' {
48 dir = (dir + 1) % 4;
49 } else {
50 pos = next;
51 }
52 }
53 visited.iter().filter(|&&c| c).count()
54 }
55
56 #[must_use]
57 pub fn part2(&self) -> usize {
58 let mut pos = self.start;
59 let mut dir = 0;
60 let mut visited = vec![0u8; self.grid.len()];
61 let mut obstructions = vec![false; self.grid.len()];
62 let mut cached_step_counts = vec![[0; 4]; self.grid.len()];
63 loop {
64 visited[pos.y * self.cols + pos.x] |= 1 << dir;
65
66 let next = pos.wrapping_add_signed(DIRECTIONS[dir]);
67 if next.x >= self.cols || next.y >= self.rows {
68 break;
69 }
70
71 if self.grid[next.y * self.cols + next.x] == b'#' {
72 dir = (dir + 1) % 4;
73 } else {
74 if !obstructions[next.y * self.cols + next.x]
75 && visited[next.y * self.cols + next.x] == 0
76 && self.check_cycle(next, pos, dir, &visited, &mut cached_step_counts)
77 {
78 obstructions[next.y * self.cols + next.x] = true;
79 }
80
81 pos = next;
82 }
83 }
84 obstructions.iter().filter(|&&c| c).count()
85 }
86
87 fn check_cycle(
92 &self,
93 obstruction: Point2D<usize>,
94 pos: Point2D<usize>,
95 dir: usize,
96 visited: &[u8],
97 cache: &mut [[isize; 4]],
98 ) -> bool {
99 let (mut power, mut lambda) = (1, 1);
100 let (mut tortoise_pos, mut tortoise_dir) = (pos, dir);
101 let (mut hare_pos, mut hare_dir) = (pos, dir);
102
103 loop {
104 if power == lambda {
105 tortoise_pos = hare_pos;
106 tortoise_dir = hare_dir;
107 power *= 2;
108 lambda = 0;
109 }
110 lambda += 1;
111
112 if hare_pos.x == obstruction.x || hare_pos.y == obstruction.y {
114 loop {
116 let next = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir]);
117 if next.x >= self.cols || next.y >= self.rows {
118 return false;
120 }
121 if self.grid[next.y * self.cols + next.x] == b'#' || next == obstruction {
122 break;
123 }
124 hare_pos = next;
125 }
126 } else {
127 let cached_count = &mut cache[hare_pos.y * self.cols + hare_pos.x][hare_dir];
129 if *cached_count > 0 {
130 hare_pos = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir] * *cached_count);
132 if hare_pos.x >= self.cols || hare_pos.y >= self.rows {
133 return false;
135 }
136 } else {
137 loop {
139 let next = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir]);
140 if next.x >= self.cols || next.y >= self.rows {
141 *cached_count += 1;
143 return false;
144 }
145 if self.grid[next.y * self.cols + next.x] == b'#' {
146 break;
147 }
148 hare_pos = next;
149 *cached_count += 1;
150 }
151 }
152 }
153
154 hare_dir = (hare_dir + 1) % 4;
155
156 if visited[hare_pos.y * self.cols + hare_pos.x] & (1 << hare_dir) != 0 {
157 return true;
159 }
160 if hare_pos == tortoise_pos && hare_dir == tortoise_dir {
161 return true;
163 }
164 }
165 }
166}
167
168examples!(Day06 -> (usize, usize) [
169 {file: "day06_example0.txt", part1: 41, part2: 6},
170]);