@@ -56,10 +56,14 @@ impl Tile {
56
56
57
57
// Return whether the current tile connects to the other tile in the given direction.
58
58
fn connects_with ( & self , other : & Tile , direction : & Direction ) -> bool {
59
- let self_connects_to_other = self . connections [ * direction as usize ] ;
60
- let other_connects_to_self = other. connections [ direction. opposite ( ) as usize ] ;
59
+ let self_connects_to_other = self . connects_direction ( direction) ;
60
+ let other_connects_to_self = other. connects_direction ( & direction. opposite ( ) ) ;
61
61
self_connects_to_other && other_connects_to_self
62
62
}
63
+
64
+ fn connects_direction ( & self , direction : & Direction ) -> bool {
65
+ self . connections [ * direction as usize ]
66
+ }
63
67
}
64
68
65
69
impl std:: str:: FromStr for Tile {
@@ -88,15 +92,19 @@ struct Maze {
88
92
89
93
impl Maze {
90
94
pub fn find_loop_length ( & self ) -> usize {
91
- let mut result = 0 ;
95
+ self . find_loop ( ) . count ( )
96
+ }
97
+
98
+ fn find_loop ( & self ) -> impl Iterator < Item = ( usize , usize ) > {
99
+ let mut result = vec ! [ ] ;
92
100
let mut current = self . start ;
93
101
let mut previous = None ;
94
102
loop {
95
103
//self.print_maze(Some(current));
96
104
97
105
// Find a connected tile that is not going back
98
106
let mut next = None ;
99
- for ( x, y, d ) in self . connected_tiles ( current) {
107
+ for ( x, y, _ ) in self . connected_tiles ( current) {
100
108
match previous {
101
109
Some ( ( px, py) ) => {
102
110
if ( x, y) != ( px, py) {
@@ -113,13 +121,114 @@ impl Maze {
113
121
// Walk it
114
122
previous = Some ( current) ;
115
123
current = next. unwrap ( ) ;
116
- result += 1 ;
124
+ result. push ( current ) ;
117
125
118
126
if current == self . start {
119
127
break ;
120
128
}
121
129
}
122
- result
130
+ result. into_iter ( )
131
+ }
132
+
133
+ pub fn find_num_enclosed_tiles ( & self ) -> usize {
134
+ // Step 1: Mark the loop: Sort the loop tiles by y and x coordinate, so we can quickly look up whether a tile is in the loop.
135
+ // The start tile is a bit annoying here, replace it with what it represents by finding the two tiles that connect to it in the loop.
136
+ let start_connected_tiles = self . connected_tiles ( self . start ) . collect :: < Vec < _ > > ( ) ;
137
+ assert ! ( start_connected_tiles. len( ) == 2 ) ;
138
+ let mut start_real_tile = None ;
139
+ for t in Tile :: all ( ) {
140
+ if * t == Tile :: START {
141
+ continue
142
+ }
143
+ let ( x1, y1, d1) = start_connected_tiles[ 0 ] ;
144
+ let ( x2, y2, d2) = start_connected_tiles[ 1 ] ;
145
+ if t. connects_with ( & self . tiles [ y1] [ x1] , & d1) && t. connects_with ( & self . tiles [ y2] [ x2] , & d2) {
146
+ start_real_tile = Some ( t) ;
147
+ break ;
148
+ }
149
+ }
150
+ assert ! ( start_real_tile. is_some( ) ) ;
151
+
152
+ let mut marked_tiles = self . find_loop ( ) . collect :: < Vec < _ > > ( ) ;
153
+ marked_tiles. sort_by ( |( x1, y1) , ( x2, y2) | {
154
+ if y1 < y2 {
155
+ return std:: cmp:: Ordering :: Less
156
+ }
157
+ if y1 > y2 {
158
+ return std:: cmp:: Ordering :: Greater
159
+ }
160
+ if x1 < x2 {
161
+ return std:: cmp:: Ordering :: Less
162
+ }
163
+ if x1 > x2 {
164
+ return std:: cmp:: Ordering :: Greater
165
+ }
166
+ std:: cmp:: Ordering :: Equal
167
+ } ) ;
168
+ let mut marked_tiles_iter = marked_tiles. iter ( ) . peekable ( ) ;
169
+
170
+ // Step 2: Scan over the whole maze, and track which tile is "outside" and "inside" the loop.
171
+ // We assume the tile at -1,y is "outside", and then by scanning line per line change our understanding
172
+ // of "outside": When crossing a `|` tile we are now "inside", and when crossing the next one we're back "outside."
173
+ let mut inside_tiles = vec ! [ ] ;
174
+
175
+ for ( y, row) in self . tiles . iter ( ) . enumerate ( ) {
176
+ // NB: We could probably keep the previous value: The loop is fully enclosed in the maze, so at the end of a line
177
+ // we must hit "outside == true".
178
+ let mut outside = true ;
179
+ let mut first_on_loop_tile: Option < & Tile > = None ;
180
+ for ( x, mut tile) in row. iter ( ) . enumerate ( ) {
181
+ if * tile == Tile :: START {
182
+ println ! ( "replacing start tile with {:?}" , start_real_tile) ;
183
+ tile = start_real_tile. unwrap ( ) ;
184
+ }
185
+ if let Some ( ( lx, ly) ) = marked_tiles_iter. peek ( ) {
186
+ if * ly == y && * lx == x {
187
+ // Tile is part of the loop, consume this tile.
188
+ marked_tiles_iter. next ( ) ;
189
+ // See whether we cross the loop boundary:
190
+ // - | is trivially crossing
191
+ // - L-*7 is crossing (L has north, 7 has south)
192
+ // - F-*J is crossing (F has south, J has north)
193
+ // We don't need to check for horizontal tiles, because we're scanning line per line,
194
+ // and we do need to look at 7/J tiles to start at pattern as we're going west to east.
195
+ if * tile == Tile :: VERTICAL {
196
+ // println!("crossed the loop");
197
+ outside = !outside;
198
+ } else if let Some ( folt) = first_on_loop_tile {
199
+ // We had a previous loop tile, check whether the current tile completes or cancels the crossing.
200
+ // The next tile is expected to be _not_ on the loop anymore, or will start another crossing.
201
+ if ( folt. connects_direction ( & Direction :: North ) && tile. connects_direction ( & Direction :: South ) ) || ( folt. connects_direction ( & Direction :: South ) && tile. connects_direction ( & Direction :: North ) ) {
202
+ // println!("finished crossing the loop");
203
+ outside = !outside;
204
+ first_on_loop_tile = None ;
205
+ } else if ( folt. connects_direction ( & Direction :: North ) && tile. connects_direction ( & Direction :: North ) ) || ( folt. connects_direction ( & Direction :: South ) && tile. connects_direction ( & Direction :: South ) ) {
206
+ // println!("canceled crossing the loop");
207
+ first_on_loop_tile = None ;
208
+ } else {
209
+ // println!("still on the loop");
210
+ }
211
+ } else if tile. connections [ Direction :: East as usize ] {
212
+ // This cannot be a `-`, as we would have already had another first_on_loop_tile.
213
+ // println!("started crossing the loop");
214
+ first_on_loop_tile = Some ( tile) ;
215
+ }
216
+ } else {
217
+ // Tile is not part of the loop, count it if we're "inside" right now.
218
+ assert ! ( first_on_loop_tile. is_none( ) , "Must not have a pending crossing when leaving the loop" ) ;
219
+ if !outside {
220
+ inside_tiles. push ( ( x, y) ) ;
221
+ }
222
+ }
223
+ }
224
+ // self.print_maze(Some((x, y)), Some(&inside_tiles));
225
+ }
226
+ assert ! ( outside) ;
227
+ }
228
+ println ! ( "Final maze:" ) ;
229
+ self . print_maze ( None , Some ( & inside_tiles) ) ;
230
+
231
+ inside_tiles. len ( )
123
232
}
124
233
125
234
fn connected_tiles ( & self , node : ( usize , usize ) ) -> impl Iterator < Item = ( usize , usize , Direction ) > {
@@ -135,7 +244,7 @@ impl Maze {
135
244
}
136
245
let other = & self . tiles [ ny as usize ] [ nx as usize ] ;
137
246
if tile. connects_with ( other, d) {
138
- println ! ( "tile {:?} connects to {:?} in {:?} direction" , tile. symbol, other. symbol, d) ;
247
+ // println!("tile {:?} connects to {:?} in {:?} direction", tile.symbol, other.symbol, d);
139
248
result. push ( ( nx as usize , ny as usize , * d) ) ;
140
249
}
141
250
}
@@ -144,12 +253,14 @@ impl Maze {
144
253
}
145
254
146
255
#[ allow( dead_code) ]
147
- fn print_maze ( & self , current : Option < ( usize , usize ) > ) {
256
+ fn print_maze ( & self , current : Option < ( usize , usize ) > , inside_tiles : Option < & Vec < ( usize , usize ) > > ) {
148
257
for ( y, row) in self . tiles . iter ( ) . enumerate ( ) {
149
258
print ! ( "{:04} " , y) ;
150
259
for ( x, tile) in row. iter ( ) . enumerate ( ) {
151
260
if Some ( ( x, y) ) == current {
152
261
print ! ( "*" ) ;
262
+ } else if inside_tiles. is_some ( ) && inside_tiles. unwrap ( ) . contains ( & ( x, y) ) {
263
+ print ! ( "I" ) ;
153
264
} else {
154
265
print ! ( "{}" , tile. symbol) ;
155
266
}
@@ -186,6 +297,7 @@ pub fn main() {
186
297
let maze = input. parse :: < Maze > ( ) . unwrap ( ) ;
187
298
let loop_length = maze. find_loop_length ( ) ;
188
299
println ! ( "loop length = {:?}, farthest distance = {:?}" , loop_length, loop_length / 2 ) ;
300
+ println ! ( "enclosed tiles = {:?}" , maze. find_num_enclosed_tiles( ) ) ;
189
301
} ,
190
302
Err ( reason) => println ! ( "error = {}" , reason)
191
303
}
@@ -255,4 +367,63 @@ SJLL7
255
367
LJ.LJ" ;
256
368
assert_eq ! ( MAZE . parse:: <Maze >( ) . ok( ) . unwrap( ) . find_loop_length( ) , 16 ) ;
257
369
}
370
+
371
+
372
+ #[ test]
373
+ fn part2_example1 ( ) {
374
+ static MAZE : & str = "...........
375
+ .S-------7.
376
+ .|F-----7|.
377
+ .||.....||.
378
+ .||.....||.
379
+ .|L-7.F-J|.
380
+ .|..|.|..|.
381
+ .L--J.L--J.
382
+ ..........." ;
383
+ assert_eq ! ( MAZE . parse:: <Maze >( ) . ok( ) . unwrap( ) . find_num_enclosed_tiles( ) , 4 ) ;
384
+ }
385
+
386
+ #[ test]
387
+ fn part2_example2 ( ) {
388
+ static MAZE : & str = "..........
389
+ .S------7.
390
+ .|F----7|.
391
+ .||....||.
392
+ .||....||.
393
+ .|L-7F-J|.
394
+ .|..||..|.
395
+ .L--JL--J.
396
+ .........." ;
397
+ assert_eq ! ( MAZE . parse:: <Maze >( ) . ok( ) . unwrap( ) . find_num_enclosed_tiles( ) , 4 ) ;
398
+ }
399
+
400
+ #[ test]
401
+ fn part2_example3 ( ) {
402
+ static MAZE : & str = ".F----7F7F7F7F-7....
403
+ .|F--7||||||||FJ....
404
+ .||.FJ||||||||L7....
405
+ FJL7L7LJLJ||LJ.L-7..
406
+ L--J.L7...LJS7F-7L7.
407
+ ....F-J..F7FJ|L7L7L7
408
+ ....L7.F7||L7|.L7L7|
409
+ .....|FJLJ|FJ|F7|.LJ
410
+ ....FJL-7.||.||||...
411
+ ....L---J.LJ.LJLJ..." ;
412
+ assert_eq ! ( MAZE . parse:: <Maze >( ) . ok( ) . unwrap( ) . find_num_enclosed_tiles( ) , 8 ) ;
413
+ }
414
+
415
+ #[ test]
416
+ fn part2_example4 ( ) {
417
+ static MAZE : & str = "FF7FSF7F7F7F7F7F---7
418
+ L|LJ||||||||||||F--J
419
+ FL-7LJLJ||||||LJL-77
420
+ F--JF--7||LJLJ7F7FJ-
421
+ L---JF-JLJ.||-FJLJJ7
422
+ |F|F-JF---7F7-L7L|7|
423
+ |FFJF7L7F-JF7|JL---7
424
+ 7-L-JL7||F7|L7F-7F7|
425
+ L.L7LFJ|||||FJL7||LJ
426
+ L7JLJL-JLJLJL--JLJ.L" ;
427
+ assert_eq ! ( MAZE . parse:: <Maze >( ) . ok( ) . unwrap( ) . find_num_enclosed_tiles( ) , 10 ) ;
428
+ }
258
429
}
0 commit comments