1use utils::geometry::Vec2;
2use utils::grid;
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: Vec2<usize>,
12}
13
14const DIRECTIONS: [Vec2<isize>; 4] = [Vec2::DOWN, Vec2::RIGHT, Vec2::UP, Vec2::LEFT];
15
16impl Day06 {
17 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
18 let mut start = None;
19 let (rows, cols, grid) = grid::parse(
20 input,
21 0,
22 0,
23 |b| b,
24 |b| matches!(b, b'.' | b'#'),
25 |i, b| {
26 if b == b'^' && start.is_none() {
27 start = Some(i);
28 Ok(b'.')
29 } else if b == b'^' {
30 Err("expected only one '^'")
31 } else {
32 Err("expected '.', '#' or '^'")
33 }
34 },
35 )?;
36
37 let Some(start) = start else {
38 return Err(InputError::new(input, 0, "expected one '^'"));
39 };
40
41 Ok(Self {
42 rows,
43 cols,
44 grid,
45 start: Vec2::new(start % cols, start / cols),
46 })
47 }
48
49 #[must_use]
50 pub fn part1(&self) -> usize {
51 let mut pos = self.start;
52 let mut dir = 0;
53 let mut visited = vec![false; self.grid.len()];
54 loop {
55 visited[pos.y * self.cols + pos.x] = true;
56
57 let next = pos.wrapping_add_signed(DIRECTIONS[dir]);
58 if next.x >= self.cols || next.y >= self.rows {
59 break;
60 }
61 if self.grid[next.y * self.cols + next.x] == b'#' {
62 dir = (dir + 1) % 4;
63 } else {
64 pos = next;
65 }
66 }
67 visited.iter().filter(|&&c| c).count()
68 }
69
70 #[must_use]
71 pub fn part2(&self) -> usize {
72 let mut pos = self.start;
73 let mut dir = 0;
74 let mut visited = vec![0u8; self.grid.len()];
75 let mut obstructions = vec![false; self.grid.len()];
76 let mut cached_step_counts = vec![[0; 4]; self.grid.len()];
77 loop {
78 visited[pos.y * self.cols + pos.x] |= 1 << dir;
79
80 let next = pos.wrapping_add_signed(DIRECTIONS[dir]);
81 if next.x >= self.cols || next.y >= self.rows {
82 break;
83 }
84
85 if self.grid[next.y * self.cols + next.x] == b'#' {
86 dir = (dir + 1) % 4;
87 } else {
88 if !obstructions[next.y * self.cols + next.x]
89 && visited[next.y * self.cols + next.x] == 0
90 && self.check_cycle(next, pos, dir, &visited, &mut cached_step_counts)
91 {
92 obstructions[next.y * self.cols + next.x] = true;
93 }
94
95 pos = next;
96 }
97 }
98 obstructions.iter().filter(|&&c| c).count()
99 }
100
101 fn check_cycle(
106 &self,
107 obstruction: Vec2<usize>,
108 pos: Vec2<usize>,
109 dir: usize,
110 visited: &[u8],
111 cache: &mut [[isize; 4]],
112 ) -> bool {
113 let (mut power, mut lambda) = (1, 1);
114 let (mut tortoise_pos, mut tortoise_dir) = (pos, dir);
115 let (mut hare_pos, mut hare_dir) = (pos, dir);
116
117 loop {
118 if power == lambda {
119 tortoise_pos = hare_pos;
120 tortoise_dir = hare_dir;
121 power *= 2;
122 lambda = 0;
123 }
124 lambda += 1;
125
126 if hare_pos.x == obstruction.x || hare_pos.y == obstruction.y {
128 loop {
130 let next = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir]);
131 if next.x >= self.cols || next.y >= self.rows {
132 return false;
134 }
135 if self.grid[next.y * self.cols + next.x] == b'#' || next == obstruction {
136 break;
137 }
138 hare_pos = next;
139 }
140 } else {
141 let cached_count = &mut cache[hare_pos.y * self.cols + hare_pos.x][hare_dir];
143 if *cached_count > 0 {
144 hare_pos = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir] * *cached_count);
146 if hare_pos.x >= self.cols || hare_pos.y >= self.rows {
147 return false;
149 }
150 } else {
151 loop {
153 let next = hare_pos.wrapping_add_signed(DIRECTIONS[hare_dir]);
154 if next.x >= self.cols || next.y >= self.rows {
155 *cached_count += 1;
157 return false;
158 }
159 if self.grid[next.y * self.cols + next.x] == b'#' {
160 break;
161 }
162 hare_pos = next;
163 *cached_count += 1;
164 }
165 }
166 }
167
168 hare_dir = (hare_dir + 1) % 4;
169
170 if visited[hare_pos.y * self.cols + hare_pos.x] & (1 << hare_dir) != 0 {
171 return true;
173 }
174 if hare_pos == tortoise_pos && hare_dir == tortoise_dir {
175 return true;
177 }
178 }
179 }
180}
181
182examples!(Day06 -> (usize, usize) [
183 {file: "day06_example0.txt", part1: 41, part2: 6},
184]);