1use crate::intcode::features::Day09Features;
2use crate::intcode::{Event, Interpreter};
3use std::io::Write;
4use std::ops::ControlFlow;
5use utils::array::ArrayVec;
6use utils::geometry::Direction;
7use utils::grid;
8use utils::prelude::*;
9
10#[derive(Clone, Debug)]
12pub struct Day17 {
13 interpreter: Interpreter,
14 scaffold: Vec<bool>,
15 rows: usize,
16 cols: usize,
17 robot_index: usize,
18 robot_dir: Direction,
19}
20
21impl Day17 {
22 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
23 let interpreter = Interpreter::parse(input, 1)?;
24
25 let mut camera = interpreter.clone();
26 let mut output = Vec::with_capacity(4096);
27 loop {
28 match camera.run::<Day09Features>() {
29 Event::Halt => break,
30 Event::Input => {
31 return Err(InputError::new(
32 input,
33 0,
34 "expected camera program to halt without requesting input",
35 ));
36 }
37 Event::Output(value)
38 if let Ok(byte) = u8::try_from(value)
39 && matches!(byte, b'\n' | b'.' | b'#' | b'^' | b'>' | b'v' | b'<') =>
40 {
41 output.push(byte);
42 }
43 Event::Output(_) => {
44 return Err(InputError::new(input, 0, "expected valid camera output"));
45 }
46 }
47 }
48 while output.pop_if(|x| *x == b'\n').is_some() {}
49 if output.is_empty() {
50 return Err(InputError::new(
51 input,
52 0,
53 "expected non-empty camera output",
54 ));
55 }
56
57 let mut robot = None;
58 let (rows, cols, scaffold) = grid::parse(
59 std::str::from_utf8(output.as_slice()).unwrap(),
60 1,
61 false,
62 |b| b == b'#',
63 |b| matches!(b, b'.' | b'#'),
64 |i, b| {
65 let dir = match b {
66 b'^' => Direction::Up,
67 b'>' => Direction::Right,
68 b'v' => Direction::Down,
69 b'<' => Direction::Left,
70 _ => unreachable!("output already validated"),
71 };
72 if robot.replace((i, dir)).is_some() {
73 return Err("duplicate robot");
74 }
75 Ok(true)
76 },
77 )?;
78 let Some((robot_index, robot_dir)) = robot else {
79 return Err(InputError::new(input, 0, "expected one robot"));
80 };
81
82 Ok(Self {
83 interpreter,
84 scaffold,
85 rows,
86 cols,
87 robot_index,
88 robot_dir,
89 })
90 }
91
92 #[must_use]
93 pub fn part1(&self) -> u32 {
94 let mut part1 = 0;
95 for y in 0..self.rows - 2 {
96 for x in 0..self.cols - 2 {
97 let index = (y + 1) * self.cols + (x + 1);
98 if self.scaffold[index - self.cols]
99 && self.scaffold[index - 1]
100 && self.scaffold[index]
101 && self.scaffold[index + 1]
102 && self.scaffold[index + self.cols]
103 {
104 part1 += (x * y) as u32;
105 }
106 }
107 }
108 part1
109 }
110
111 #[must_use]
112 pub fn part2(&self) -> i64 {
113 let mut path = Vec::with_capacity(256);
114 let offsets = [-(self.cols as isize), 1, self.cols as isize, -1];
115 let (mut index, mut dir) = (self.robot_index, self.robot_dir);
116 loop {
117 let left = dir.turn_left();
118 let right = dir.turn_right();
119 let (turn, next_dir) = match (
120 self.scaffold[index.wrapping_add_signed(offsets[left as usize])],
121 self.scaffold[index.wrapping_add_signed(offsets[right as usize])],
122 ) {
123 (true, false) => ('L', left),
124 (false, true) => ('R', right),
125 (false, false) => break,
126 (true, true) => panic!("no solution found: unexpected t-junction"),
127 };
128 dir = next_dir;
129
130 let offset = offsets[dir as usize];
131 index = index.wrapping_add_signed(offset);
132
133 let mut distance = 1;
134 while let next = index.wrapping_add_signed(offset)
135 && self.scaffold[next]
136 {
137 index = next;
138 distance += 1;
139 }
140
141 write!(&mut path, "{turn},{distance},").unwrap();
142 }
143
144 let mut main = ArrayVec::new();
145 let mut routines = [None; 3];
146 if Self::compress(&path, &mut main, &mut routines).is_continue() {
147 panic!("no solution found: failed to compress path into three routines");
148 }
149
150 let mut interpreter = self.interpreter.clone();
151 interpreter.mem[0] = 2;
152
153 for line in std::iter::once(main.as_slice())
154 .chain(routines.into_iter().flatten())
155 .map(|line| line.strip_suffix(b",").unwrap_or(line))
156 .chain(std::iter::once(b"n".as_slice()))
157 {
158 for &b in line {
159 interpreter.push_input(i64::from(b));
160 }
161 interpreter.push_input(i64::from(b'\n'));
162 }
163
164 loop {
165 let output = interpreter.expect_output::<Day09Features>();
166 if output > 255 {
167 return output;
168 }
169 }
170 }
171
172 #[inline]
173 fn compress<'a>(
174 path: &'a [u8],
175 main: &mut ArrayVec<u8, 20>,
176 routines: &mut [Option<&'a [u8]>; 3],
177 ) -> ControlFlow<()> {
178 if path.is_empty() {
179 return ControlFlow::Break(());
180 }
181
182 for r in 0..3 {
183 if main.push(b'A' + r as u8).is_err() {
184 return ControlFlow::Continue(());
185 };
186 main.push(b',')
187 .expect("pushing 2nd byte into even length array should never fail");
188
189 if let Some(routine) = routines[r] {
190 if path.starts_with(routine) {
191 Self::compress(&path[routine.len()..], main, routines)?;
192 }
193 } else {
194 for (i, _) in path
195 .iter()
196 .enumerate()
197 .filter(|&(_, &b)| b == b',')
198 .skip(1)
199 .step_by(2)
200 .take_while(|&(i, _)| i < 21)
201 {
202 let (routine, remaining) = path.split_at(i + 1);
203 routines[r] = Some(routine);
204 Self::compress(remaining, main, routines)?;
205 routines[r] = None;
206 }
207 }
208
209 let (_, _) = (main.pop(), main.pop());
210 }
211
212 ControlFlow::Continue(())
213 }
214}
215
216examples!(Day17 -> (u32, i64) []);