Skip to main content

year2019/
day17.rs

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/// Interpreting machine code to find a path, then compressing it.
11#[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) []);