1use crate::util::grid::*;
15use crate::util::point::*;
16use crate::util::thread::*;
17use std::collections::VecDeque;
18use std::sync::Mutex;
19use std::sync::atomic::{AtomicUsize, Ordering};
20
21type Pair = (Point, u32);
22
23const UP: u32 = 0;
24const DOWN: u32 = 1;
25const LEFT: u32 = 2;
26const RIGHT: u32 = 3;
27
28pub struct Input {
29 grid: Grid<u8>,
30 up: Grid<i32>,
31 down: Grid<i32>,
32 left: Grid<i32>,
33 right: Grid<i32>,
34}
35
36struct Shared<'a> {
38 input: &'a Input,
39 tiles: AtomicUsize,
40 mutex: Mutex<Vec<Pair>>,
41}
42
43pub fn parse(input: &str) -> Input {
45 let grid = Grid::parse(input);
46
47 let mut up: Grid<i32> = grid.same_size_with(0);
48 let mut down: Grid<i32> = grid.same_size_with(0);
49 let mut left: Grid<i32> = grid.same_size_with(0);
50 let mut right: Grid<i32> = grid.same_size_with(0);
51
52 for x in 0..grid.width {
53 let mut last = -1;
54
55 for y in 0..grid.height {
56 let point = Point::new(x, y);
57 up[point] = last;
58
59 if matches!(grid[point], b'/' | b'\\' | b'-') {
60 last = y;
61 }
62 }
63 }
64
65 for x in 0..grid.width {
66 let mut last = grid.height;
67
68 for y in (0..grid.height).rev() {
69 let point = Point::new(x, y);
70 down[point] = last;
71
72 if matches!(grid[point], b'/' | b'\\' | b'-') {
73 last = y;
74 }
75 }
76 }
77
78 for y in 0..grid.height {
79 let mut last = -1;
80
81 for x in 0..grid.width {
82 let point = Point::new(x, y);
83 left[point] = last;
84
85 if matches!(grid[point], b'/' | b'\\' | b'|') {
86 last = x;
87 }
88 }
89 }
90
91 for y in 0..grid.height {
92 let mut last = grid.width;
93
94 for x in (0..grid.width).rev() {
95 let point = Point::new(x, y);
96 right[point] = last;
97
98 if matches!(grid[point], b'/' | b'\\' | b'|') {
99 last = x;
100 }
101 }
102 }
103
104 Input { grid, up, down, left, right }
105}
106
107pub fn part1(input: &Input) -> usize {
108 count(input, (ORIGIN, RIGHT))
109}
110
111pub fn part2(input: &Input) -> usize {
112 let Input { grid, .. } = input;
114 let mut todo = Vec::new();
115
116 for x in 0..grid.width {
117 todo.push((Point::new(x, 0), DOWN));
118 todo.push((Point::new(x, grid.height - 1), UP));
119 }
120
121 for y in 0..grid.height {
122 todo.push((Point::new(0, y), RIGHT));
123 todo.push((Point::new(grid.width - 1, y), LEFT));
124 }
125
126 let shared = Shared { input, tiles: AtomicUsize::new(0), mutex: Mutex::new(todo) };
128
129 spawn(|| worker(&shared));
131
132 shared.tiles.load(Ordering::Relaxed)
133}
134
135fn worker(shared: &Shared<'_>) {
137 loop {
138 let start = {
139 let mut exclusive = shared.mutex.lock().unwrap();
140 let Some(start) = exclusive.pop() else {
141 break;
142 };
143 start
144 };
145 shared.tiles.fetch_max(count(shared.input, start), Ordering::Relaxed);
146 }
147}
148
149fn count(input: &Input, start: Pair) -> usize {
151 let Input { grid, up, down, left, right } = input;
152
153 let mut todo = VecDeque::with_capacity(1_000);
154 let mut seen: Grid<u8> = grid.same_size_with(0);
155 let mut energized: Grid<bool> = grid.same_size_with(false);
156
157 todo.push_back(start);
158
159 while let Some((position, direction)) = todo.pop_front() {
160 let mut next = |direction: u32| {
161 let mask = 1 << direction;
163 if seen[position] & mask != 0 {
164 return;
165 }
166 seen[position] |= mask;
167
168 match direction {
169 UP => {
170 let x = position.x;
171 let last = up[position];
172
173 for y in last..position.y {
174 energized[Point::new(x, y + 1)] = true;
175 }
176 if last >= 0 {
177 todo.push_back((Point::new(x, last), UP));
178 }
179 }
180 DOWN => {
181 let x = position.x;
182 let last = down[position];
183
184 for y in position.y..last {
185 energized[Point::new(x, y)] = true;
186 }
187 if last < grid.height {
188 todo.push_back((Point::new(x, last), DOWN));
189 }
190 }
191 LEFT => {
192 let y = position.y;
193 let last = left[position];
194
195 for x in last..position.x {
196 energized[Point::new(x + 1, y)] = true;
197 }
198 if last >= 0 {
199 todo.push_back((Point::new(last, y), LEFT));
200 }
201 }
202 RIGHT => {
203 let y = position.y;
204 let last = right[position];
205
206 for x in position.x..last {
207 energized[Point::new(x, y)] = true;
208 }
209 if last < grid.width {
210 todo.push_back((Point::new(last, y), RIGHT));
211 }
212 }
213 _ => unreachable!(),
214 }
215 };
216
217 match grid[position] {
218 b'.' => next(direction),
219 b'/' => match direction {
220 UP => next(RIGHT),
221 DOWN => next(LEFT),
222 LEFT => next(DOWN),
223 RIGHT => next(UP),
224 _ => unreachable!(),
225 },
226 b'\\' => match direction {
227 UP => next(LEFT),
228 DOWN => next(RIGHT),
229 LEFT => next(UP),
230 RIGHT => next(DOWN),
231 _ => unreachable!(),
232 },
233 b'|' => match direction {
234 UP | DOWN => next(direction),
235 LEFT | RIGHT => {
236 next(UP);
237 next(DOWN);
238 }
239 _ => unreachable!(),
240 },
241 b'-' => match direction {
242 LEFT | RIGHT => next(direction),
243 UP | DOWN => {
244 next(LEFT);
245 next(RIGHT);
246 }
247 _ => unreachable!(),
248 },
249 _ => unreachable!(),
250 }
251 }
252
253 energized.bytes.iter().filter(|&&b| b).count()
254}