Skip to content

Commit 00eb708

Browse files
committed
2019 day 17 part 2
1 parent aef8c7e commit 00eb708

1 file changed

Lines changed: 265 additions & 54 deletions

File tree

src/y2019/day17.rs

Lines changed: 265 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,295 @@
11
use super::intcode::Vm;
22
use crate::grid::{Compass, Compass::*, Grid, ORIGIN, Pos};
3+
use std::fmt;
4+
use std::ops::Range;
35

46
pub struct Solver {
5-
camera_vm: Vm,
7+
vm: Vm,
8+
map: Grid<Cell>,
9+
robot_start_pos: Pos,
10+
robot_start_dir: Compass,
11+
debug: bool, // if true, display all output from the program
612
}
713

814
impl crate::Puzzle for Solver {
915
fn new(input: &str) -> Self {
16+
let vm = Vm::from(input);
17+
18+
// initial camera output is used to construct the map and robot starting position
19+
let output = vm.clone().run(&[]);
20+
let width = output.iter().position(|&i| i == 10).unwrap() as u32;
21+
let height = (output.len() as u32 - 1) / (width + 1);
22+
let mut map = Grid::new(width, height, Cell::Unknown);
23+
let mut robot_start_pos = ORIGIN;
24+
let mut robot_start_dir: Option<Compass> = None;
25+
let mut p = ORIGIN;
26+
for i in output.iter().map(|i| *i as u8) {
27+
if i == b'\n' {
28+
p.x = 0;
29+
p.y += 1;
30+
} else {
31+
match i {
32+
b'#' => {
33+
map.set(p, Cell::Scaffold);
34+
}
35+
b'.' => {
36+
map.set(p, Cell::Space);
37+
}
38+
b'<' | b'>' | b'^' | b'v' => {
39+
map.set(p, Cell::Scaffold);
40+
robot_start_pos = p;
41+
robot_start_dir = Some(match i {
42+
b'<' => West,
43+
b'>' => East,
44+
b'^' => North,
45+
b'v' => South,
46+
_ => unreachable!(),
47+
});
48+
}
49+
_ => {
50+
panic!("Unhandled input: {}", i);
51+
}
52+
}
53+
p.x += 1;
54+
}
55+
}
56+
1057
Self {
11-
camera_vm: Vm::from(input),
58+
vm,
59+
map,
60+
robot_start_pos,
61+
robot_start_dir: robot_start_dir.unwrap(),
62+
debug: false,
1263
}
1364
}
1465

1566
fn part1(&self) -> String {
16-
let image = capture_image(&self.camera_vm);
17-
//println!("{}", print_image(&image));
18-
sum_intersections(&image).to_string()
67+
sum_intersections(&self.map).to_string()
1968
}
2069

2170
fn part2(&self) -> String {
22-
"unimplemented".to_string()
71+
let mut vm = self.vm.clone();
72+
vm.direct_write(0, 2); // instruct robot to wake up
73+
74+
// solve the actual problem
75+
let path = find_path(&self.map, self.robot_start_pos, self.robot_start_dir);
76+
let (a, b, c, main) = split_paths(&path);
77+
78+
// play the solution into the VM
79+
run_program(&mut vm, None, self.debug);
80+
// Main:
81+
run_program(
82+
&mut vm,
83+
Some(
84+
main.iter()
85+
.map(|c| c.to_string())
86+
.collect::<Vec<_>>()
87+
.join(","),
88+
),
89+
self.debug,
90+
);
91+
// Function A:
92+
run_program(&mut vm, Some(moves_to_string(&path[a])), self.debug);
93+
// Function B:
94+
run_program(&mut vm, Some(moves_to_string(&path[b])), self.debug);
95+
// Function C:
96+
run_program(&mut vm, Some(moves_to_string(&path[c])), self.debug);
97+
// Continuous video feed?
98+
let output = run_program(
99+
&mut vm,
100+
Some((if self.debug { "y" } else { "n" }).to_string()),
101+
self.debug,
102+
);
103+
104+
output.last().unwrap().to_string()
105+
}
106+
}
107+
108+
// Input is a string, if present is converted to intcode input with an ASCII newline added.
109+
// If debug is true, the ASCII output of the program will be printed to the console
110+
fn run_program(vm: &mut Vm, input: Option<String>, debug: bool) -> Vec<i64> {
111+
let input: Vec<i64> = match input {
112+
Some(string) => {
113+
let mut v: Vec<_> = string.chars().map(|c| c as i64).collect();
114+
v.push(b'\n' as i64);
115+
v
116+
}
117+
None => vec![],
118+
};
119+
if debug {
120+
println!("INPUT: {:?}", input);
121+
}
122+
let output = vm.run(&input);
123+
if debug {
124+
println!("{}", output_to_string(&output));
125+
}
126+
output
127+
}
128+
129+
// split a path of moves into 3 functions (a, b, c) and a main routine that
130+
// defines the order to use them
131+
// a, b and c are returned as ranges into the original path
132+
//
133+
// constraint:
134+
// each path must be rendered as a string in <= 20 characters so it can be entered
135+
// into the VM's input prompts
136+
//
137+
// Assumptions:
138+
// A will always start at 0
139+
// B will follow A (maybe not true in general? A could repeat)
140+
// C may start anywhere after where B starts (it could overlap with either A or B)
141+
fn split_paths(path: &[Move]) -> (Range<usize>, Range<usize>, Range<usize>, Vec<char>) {
142+
let a_from = 0;
143+
for a_to in (1..path.len()).rev() {
144+
let a = a_from..(a_to + 1);
145+
if moves_size(&path[a.clone()]) <= 20 {
146+
let b_from = a_to + 1;
147+
for b_to in ((b_from + 1)..path.len()).rev() {
148+
let b = b_from..(b_to + 1);
149+
if moves_size(&path[b.clone()]) <= 20 {
150+
for c_from in (b_from + 1)..path.len() {
151+
for c_to in ((c_from + 1)..path.len()).rev() {
152+
let c = c_from..(c_to + 1);
153+
if moves_size(&path[c.clone()]) <= 20
154+
&& let Some(mut main) = check_solution(path, 0, &a, &b, &c)
155+
{
156+
main.reverse();
157+
return (a, b, c, main);
158+
}
159+
}
160+
}
161+
}
162+
}
163+
}
164+
}
165+
panic!("No solution found");
166+
}
167+
168+
fn check_solution(
169+
path: &[Move],
170+
from: usize,
171+
a: &Range<usize>,
172+
b: &Range<usize>,
173+
c: &Range<usize>,
174+
) -> Option<Vec<char>> {
175+
if from == path.len() {
176+
return Some(vec![]);
177+
}
178+
if range_match(path, from, a)
179+
&& let Some(mut solution) = check_solution(path, from + a.len(), a, b, c)
180+
{
181+
solution.push('A');
182+
return Some(solution);
183+
}
184+
if range_match(path, from, b)
185+
&& let Some(mut solution) = check_solution(path, from + b.len(), a, b, c)
186+
{
187+
solution.push('B');
188+
return Some(solution);
23189
}
190+
if range_match(path, from, c)
191+
&& let Some(mut solution) = check_solution(path, from + c.len(), a, b, c)
192+
{
193+
solution.push('C');
194+
return Some(solution);
195+
}
196+
197+
None
24198
}
25199

26-
fn capture_image(camera_vm: &Vm) -> Grid<Cell> {
27-
let output = camera_vm.clone().run(&[]);
28-
let width = output.iter().position(|&i| i == 10).unwrap() as u32;
29-
let height = (output.len() as u32 - 1) / (width + 1);
30-
let mut image = Grid::new(width, height, Cell::Unknown);
31-
let mut p = ORIGIN;
32-
for i in output {
33-
if i == 10 {
34-
p.x = 0;
35-
p.y += 1;
200+
fn range_match(path: &[Move], from: usize, range: &Range<usize>) -> bool {
201+
if from + range.len() > path.len() {
202+
return false;
203+
}
204+
for (i, r) in range.clone().enumerate() {
205+
if path[r] != path[from + i] {
206+
return false;
207+
}
208+
}
209+
true
210+
}
211+
212+
// Assumptions about the input:
213+
// - we can traverse the scaffold by a series of moves, where a move is a turn
214+
// left or right then steps as far as possible
215+
// - reaching the end of the straight line is always followed by a left or
216+
// right turn (never a choice)
217+
// - When reaching a dead end, we've seen every part of the map.
218+
fn find_path(map: &Grid<Cell>, robot_start_pos: Pos, robot_start_dir: Compass) -> Vec<Move> {
219+
let mut path = vec![];
220+
let mut pos = robot_start_pos;
221+
let mut dir = robot_start_dir;
222+
223+
loop {
224+
let turn = if is_scaffold(map.look(pos, dir.left90())) {
225+
Turn::Left
226+
} else if is_scaffold(map.look(pos, dir.right90())) {
227+
Turn::Right
36228
} else {
37-
image.set(
38-
p,
39-
match i {
40-
35 => Cell::Scaffold, // #
41-
46 => Cell::Space, // .
42-
94 => Cell::Robot(North), // ^
43-
_ => panic!("unhandled: {}", i),
44-
},
45-
);
46-
p.x += 1;
229+
return path;
230+
};
231+
dir = match turn {
232+
Turn::Left => dir.left90(),
233+
Turn::Right => dir.right90(),
234+
};
235+
let mut steps = 0;
236+
while is_scaffold(map.look(pos, dir)) {
237+
steps += 1;
238+
pos = pos.step(dir);
47239
}
240+
path.push(Move { turn, steps });
48241
}
242+
}
49243

50-
image
244+
#[derive(PartialEq)]
245+
enum Turn {
246+
Left,
247+
Right,
51248
}
52249

53-
#[allow(dead_code)]
54-
fn print_image(image: &Grid<Cell>) -> String {
55-
let mut s = "".to_string();
250+
#[derive(PartialEq)]
251+
struct Move {
252+
turn: Turn,
253+
steps: u32,
254+
}
56255

57-
for (p, cell) in image.iter() {
58-
s.push(match cell {
59-
Cell::Unknown => panic!("grid is not filled {}", p),
60-
Cell::Space => '.',
61-
Cell::Scaffold => '#',
62-
Cell::Robot(dir) => match dir {
63-
North => '^',
64-
East => '>',
65-
South => 'v',
66-
West => '<',
67-
_ => unreachable!(),
256+
impl fmt::Debug for Move {
257+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258+
fmt::Display::fmt(self, f)
259+
}
260+
}
261+
262+
impl fmt::Display for Move {
263+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264+
write!(
265+
f,
266+
"{},{}",
267+
match self.turn {
268+
Turn::Left => "L",
269+
Turn::Right => "R",
68270
},
69-
});
70-
if p.x == image.maxx() {
71-
s += "\n";
72-
}
271+
self.steps
272+
)
73273
}
274+
}
275+
276+
fn moves_size(moves: &[Move]) -> usize {
277+
moves_to_string(moves).len()
278+
}
279+
280+
fn moves_to_string(moves: &[Move]) -> String {
281+
moves
282+
.iter()
283+
.map(|m| m.to_string())
284+
.collect::<Vec<_>>()
285+
.join(",")
286+
}
74287

75-
s
288+
fn output_to_string(output: &[i64]) -> String {
289+
output
290+
.iter()
291+
.filter_map(|&i| char::from_u32(i as u32))
292+
.collect()
76293
}
77294

78295
fn sum_intersections(image: &Grid<Cell>) -> i32 {
@@ -95,20 +312,14 @@ fn sum_intersections(image: &Grid<Cell>) -> i32 {
95312
}
96313

97314
fn is_scaffold(cell: &Cell) -> bool {
98-
matches!(cell, Cell::Scaffold | Cell::Robot(_))
99-
//match cell {
100-
// Cell::Scaffold => true,
101-
// Cell::Robot(_) => true,
102-
// _ => false,
103-
//}
315+
matches!(cell, Cell::Scaffold)
104316
}
105317

106-
#[derive(Clone, PartialEq)]
318+
#[derive(Clone, Debug, PartialEq)]
107319
enum Cell {
108320
Unknown,
109321
Space,
110322
Scaffold,
111-
Robot(Compass),
112323
}
113324

114325
impl Pos {

0 commit comments

Comments
 (0)