@@ -17,11 +17,11 @@ impl crate::Puzzle for Solver {
1717 }
1818
1919 fn part1 ( & self ) -> String {
20- solve ( & self . maze ) . to_string ( )
20+ solve ( & self . maze , false ) . unwrap ( ) . to_string ( )
2121 }
2222
2323 fn part2 ( & self ) -> String {
24- "unimplemented" . to_string ( )
24+ solve ( & self . maze , true ) . unwrap ( ) . to_string ( )
2525 }
2626}
2727
@@ -33,6 +33,9 @@ fn parse_input(input: &str) -> Maze {
3333 // Map of labels to a vec of (entrance, local_exit) pair
3434 let mut warp_markers: HashMap < ( char , char ) , Vec < ( Pos , Pos ) > > = HashMap :: new ( ) ;
3535
36+ let mut top_left_corner: Option < Pos > = None ;
37+ let mut bottom_right_corner: Option < Pos > = None ;
38+
3639 // iterate over each marker. Record entrance, exit and each warp with its local exit
3740 let each_pos: Vec < Pos > = grid. iter ( ) . map ( |( p, _cell) | p) . collect ( ) ;
3841 for p in each_pos {
@@ -58,15 +61,40 @@ fn parse_input(input: &str) -> Maze {
5861 grid. set ( p2, Empty ) ;
5962 }
6063 }
64+ Wall => {
65+ // track these corners to determine if warps are outer/inner
66+ if top_left_corner. is_none ( ) {
67+ top_left_corner = Some ( p) ;
68+ }
69+ bottom_right_corner = Some ( p) ;
70+ }
6171 _ => { }
6272 }
6373 }
6474
75+ // lines containing outer portals
76+ let outer_left_x = top_left_corner. unwrap ( ) . x - 1 ;
77+ let outer_right_x = bottom_right_corner. unwrap ( ) . x + 1 ;
78+ let outer_top_y = top_left_corner. unwrap ( ) . y - 1 ;
79+ let outer_bottom_y = bottom_right_corner. unwrap ( ) . y + 1 ;
80+
6581 // Each label should have exactly 2 (entrace,local_exits) pairs.
6682 // Insert them into the maze, swapping local exits so that they become warp exits.
6783 for ( _label, v) in warp_markers. iter ( ) {
68- grid. set ( v[ 0 ] . 0 , Warp ( v[ 1 ] . 1 ) ) ;
69- grid. set ( v[ 1 ] . 0 , Warp ( v[ 0 ] . 1 ) ) ;
84+ let p = v[ 0 ] . 0 ;
85+ if p. x == outer_left_x
86+ || p. x == outer_right_x
87+ || p. y == outer_top_y
88+ || p. y == outer_bottom_y
89+ {
90+ // the first pair is outer
91+ grid. set ( v[ 0 ] . 0 , OuterPortal ( v[ 1 ] . 1 ) ) ;
92+ grid. set ( v[ 1 ] . 0 , InnerPortal ( v[ 0 ] . 1 ) ) ;
93+ } else {
94+ // the first pair is inner
95+ grid. set ( v[ 0 ] . 0 , InnerPortal ( v[ 1 ] . 1 ) ) ;
96+ grid. set ( v[ 1 ] . 0 , OuterPortal ( v[ 0 ] . 1 ) ) ;
97+ }
7098 }
7199
72100 Maze {
@@ -112,8 +140,9 @@ enum Cell {
112140 Passage ,
113141 Entrance ,
114142 Exit ,
115- Label ( char ) ,
116- Warp ( Pos ) ,
143+ Label ( char ) , // temporary during parsing
144+ InnerPortal ( Pos ) ,
145+ OuterPortal ( Pos ) ,
117146}
118147
119148impl From < char > for Cell {
@@ -136,28 +165,75 @@ struct Maze {
136165
137166const NEIGHBOURS : [ Compass ; 4 ] = [ North , East , South , West ] ;
138167
139- fn solve ( maze : & Maze ) -> usize {
140- bfs:: search (
141- & maze. entrance ,
142- |p| {
143- NEIGHBOURS
144- . iter ( )
145- . filter_map ( |dir| {
146- let p2 = p. step ( * dir) ;
147- match maze. grid . get ( p2) {
148- Wall => None ,
149- Passage => Some ( ( p2, dir) ) ,
150- Exit => Some ( ( p2, dir) ) ,
151- Warp ( to) => Some ( ( * to, dir) ) ,
152- _ => None ,
153- }
154- } )
155- . collect :: < Vec < _ > > ( )
168+ fn solve ( maze : & Maze , recursive : bool ) -> Option < usize > {
169+ let path = bfs:: search (
170+ & ( maze. entrance , 0 ) ,
171+ |( p, level) | {
172+ if recursive {
173+ recursive_neighbours ( maze, p, level)
174+ } else {
175+ non_recursive_neighbours ( maze, p)
176+ }
156177 } ,
157- |p| * p == maze. exit ,
158- )
159- . unwrap ( )
160- . len ( )
178+ |( p, level) | * level == 0 && * p == maze. exit ,
179+ ) ;
180+ if let Some ( v) = path {
181+ Some ( v. len ( ) )
182+ } else {
183+ None
184+ }
185+ }
186+
187+ // original version, any portal warps to its companion with the same name
188+ fn non_recursive_neighbours ( maze : & Maze , p : & Pos ) -> Vec < ( ( Pos , u32 ) , Compass ) > {
189+ NEIGHBOURS
190+ . iter ( )
191+ . filter_map ( |dir| {
192+ let p2 = p. step ( * dir) ;
193+ match maze. grid . get ( p2) {
194+ Wall => None ,
195+ Passage => Some ( ( ( p2, 0 ) , * dir) ) ,
196+ Exit => Some ( ( ( p2, 0 ) , * dir) ) ,
197+ InnerPortal ( to) | OuterPortal ( to) => Some ( ( ( * to, 0 ) , * dir) ) ,
198+ _ => None ,
199+ }
200+ } )
201+ . collect ( )
202+ }
203+
204+ // inner warps are now to a deeper copy of the maze,
205+ // outer warps are back to the previous copy
206+ // initial maze is outermost (level=0); warps do not lead anywhere
207+ // entrance and exit only exist on the outermost maze.
208+ fn recursive_neighbours ( maze : & Maze , p : & Pos , level : & u32 ) -> Vec < ( ( Pos , u32 ) , Compass ) > {
209+ NEIGHBOURS
210+ . iter ( )
211+ . filter_map ( |dir| {
212+ let p2 = p. step ( * dir) ;
213+ match maze. grid . get ( p2) {
214+ Wall => None ,
215+ Passage => Some ( ( ( p2, * level) , * dir) ) ,
216+ Exit => Some ( ( ( p2, * level) , * dir) ) ,
217+ InnerPortal ( to) => {
218+ // A level limit is needed to avoid infinite recursion when there's
219+ // no path (like test example 2)
220+ if * level < 25 {
221+ Some ( ( ( * to, level + 1 ) , * dir) )
222+ } else {
223+ None
224+ }
225+ }
226+ OuterPortal ( to) => {
227+ if * level > 0 {
228+ Some ( ( ( * to, level - 1 ) , * dir) )
229+ } else {
230+ None
231+ }
232+ }
233+ _ => None ,
234+ }
235+ } )
236+ . collect ( )
161237}
162238
163239#[ test]
@@ -185,7 +261,8 @@ fn test() {
185261 ]
186262 . join ( "" ) ;
187263 let maze = parse_input ( & example1) ;
188- assert_eq ! ( 23 , solve( & maze) ) ;
264+ assert_eq ! ( Some ( 23 ) , solve( & maze, false ) ) ;
265+ assert_eq ! ( Some ( 26 ) , solve( & maze, true ) ) ;
189266
190267 let example2 = [
191268 " A \n " ,
@@ -228,5 +305,50 @@ fn test() {
228305 ]
229306 . join ( "" ) ;
230307 let maze = parse_input ( & example2) ;
231- assert_eq ! ( 58 , solve( & maze) ) ;
308+ assert_eq ! ( Some ( 58 ) , solve( & maze, false ) ) ;
309+ assert_eq ! ( None , solve( & maze, true ) ) ;
310+
311+ let example3 = [
312+ " Z L X W C \n " ,
313+ " Z P Q B K \n " ,
314+ " ###########.#.#.#.#######.############### \n " ,
315+ " #...#.......#.#.......#.#.......#.#.#...# \n " ,
316+ " ###.#.#.#.#.#.#.#.###.#.#.#######.#.#.### \n " ,
317+ " #.#...#.#.#...#.#.#...#...#...#.#.......# \n " ,
318+ " #.###.#######.###.###.#.###.###.#.####### \n " ,
319+ " #...#.......#.#...#...#.............#...# \n " ,
320+ " #.#########.#######.#.#######.#######.### \n " ,
321+ " #...#.# F R I Z #.#.#.# \n " ,
322+ " #.###.# D E C H #.#.#.# \n " ,
323+ " #.#...# #...#.# \n " ,
324+ " #.###.# #.###.# \n " ,
325+ " #.#....OA WB..#.#..ZH\n " ,
326+ " #.###.# #.#.#.# \n " ,
327+ "CJ......# #.....# \n " ,
328+ " ####### ####### \n " ,
329+ " #.#....CK #......IC\n " ,
330+ " #.###.# #.###.# \n " ,
331+ " #.....# #...#.# \n " ,
332+ " ###.### #.#.#.# \n " ,
333+ "XF....#.# RF..#.#.# \n " ,
334+ " #####.# ####### \n " ,
335+ " #......CJ NM..#...# \n " ,
336+ " ###.#.# #.###.# \n " ,
337+ "RE....#.# #......RF\n " ,
338+ " ###.### X X L #.#.#.# \n " ,
339+ " #.....# F Q P #.#.#.# \n " ,
340+ " ###.###########.###.#######.#########.### \n " ,
341+ " #.....#...#.....#.......#...#.....#.#...# \n " ,
342+ " #####.#.###.#######.#######.###.###.#.#.# \n " ,
343+ " #.......#.......#.#.#.#.#...#...#...#.#.# \n " ,
344+ " #####.###.#####.#.#.#.#.###.###.#.###.### \n " ,
345+ " #.......#.....#.#...#...............#...# \n " ,
346+ " #############.#.#.###.################### \n " ,
347+ " A O F N \n " ,
348+ " A A D M \n " ,
349+ ]
350+ . join ( "" ) ;
351+ let maze = parse_input ( & example3) ;
352+ assert_eq ! ( Some ( 77 ) , solve( & maze, false ) ) ;
353+ assert_eq ! ( Some ( 396 ) , solve( & maze, true ) ) ;
232354}
0 commit comments