11use super :: intcode:: Vm ;
22use crate :: grid:: { Compass , Compass :: * , Grid , ORIGIN , Pos } ;
3+ use std:: fmt;
4+ use std:: ops:: Range ;
35
46pub 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
814impl 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
78295fn sum_intersections ( image : & Grid < Cell > ) -> i32 {
@@ -95,20 +312,14 @@ fn sum_intersections(image: &Grid<Cell>) -> i32 {
95312}
96313
97314fn 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 ) ]
107319enum Cell {
108320 Unknown ,
109321 Space ,
110322 Scaffold ,
111- Robot ( Compass ) ,
112323}
113324
114325impl Pos {
0 commit comments