@@ -7,28 +7,93 @@ def move(pos: tuple, dir_: Dir, grid: Grid) -> tuple[tuple, Dir]:
7
7
next_pos = pos + Vec2 (dir_ )
8
8
9
9
# out of bounds or open, return next position
10
- if not grid .in_bounds (next_pos ) or not grid [next_pos ] == '#' :
10
+ if not grid .in_bounds (next_pos ) or not grid [next_pos ] in '#O ' :
11
11
return next_pos .as_tuple (), dir_
12
12
13
13
# obstacle, rotate right 90 degrees
14
14
else :
15
15
return pos , dir_ .turn_right ()
16
16
17
17
18
- def main ():
19
- grid = Grid (aoc .read_lines ())
20
-
21
- pos = grid .find ('^' )
22
- dir_ = Dir .UP
23
-
18
+ def simulate (pos : tuple , dir_ : Dir , grid : Grid ) -> tuple [set , list , bool ]:
24
19
visited = set ()
25
20
visited .add (pos )
21
+ path = []
22
+ path .append ((pos , dir_ ))
23
+ loops = False
24
+
26
25
while grid .in_bounds (pos ):
27
26
pos , dir_ = move (pos , dir_ , grid )
28
27
visited .add (pos )
28
+ if path [- 1 ][0 ] == pos :
29
+ path .append ((pos , dir_ ))
30
+ path .append ((pos , None ))
31
+ elif (pos , dir_ ) in path :
32
+ loops = True
33
+ break
34
+ else :
35
+ path .append ((pos , dir_ ))
36
+
37
+ return visited , path , loops
38
+
39
+
40
+ def print_path (grid : Grid , path : list , start_pos : tuple ):
41
+ dir_chars = {
42
+ Dir .UP : '|' ,
43
+ Dir .DOWN : '|' ,
44
+ Dir .LEFT : '-' ,
45
+ Dir .RIGHT : '-' ,
46
+ None : '+' ,
47
+ }
48
+ print (grid .render_with_overlays ([{pos : dir_chars [dir_ ] for pos , dir_ in path },
49
+ {start_pos : '^' }]))
50
+
51
+
52
+ def main ():
53
+ grid = Grid (aoc .read_lines ())
54
+
55
+ start_pos = grid .find ('^' )
56
+ dir_ = Dir .UP
57
+
58
+ visited , path , _ = simulate (start_pos , dir_ , grid )
59
+
60
+ # print(grid.render_with_overlays([{pos: 'X' for pos in visited}]))
61
+ # print("p1:", len(visited) - 1)
62
+
63
+ # print_path(grid, path, start_pos)
64
+
65
+ loop_obstacles = set ()
66
+ tried_obstacles = set ()
67
+ path = [p for p in path if p [1 ] is not None ]
68
+
69
+ # try placing an obstacle at every position along our initial path
70
+ for i , pair in enumerate (path ):
71
+ pos = pair [0 ]
72
+
73
+ # skip the start position, out-of-bounds, and previously tried obstacles
74
+ if pos == start_pos or not grid .in_bounds (pos ) or pos in tried_obstacles :
75
+ continue
76
+
77
+ # place the obstacle
78
+ grid [pos ] = 'O'
79
+ tried_obstacles .add (pos )
80
+
81
+ # start pathing from just before the newly placed obstacle
82
+ new_start_pos , new_dir = path [i - 1 ] if path [i - 1 ][0 ] != pos else path [i - 2 ]
83
+
84
+ new_visited , new_path , loops = simulate (new_start_pos , new_dir , grid )
85
+
86
+ # track obstacles that cause loops
87
+ if loops :
88
+ loop_obstacles .add (pos )
89
+ # print_path(grid, new_path, new_start_pos)
90
+ print (f"{ i } /{ len (path )} { len (loop_obstacles )} " )
91
+
92
+ # remove the current obstacle so we can try the next one
93
+ grid [pos ] = '.'
29
94
30
- print (grid .render_with_overlays ([{pos : 'X' for pos in visited }]))
31
95
print ("p1:" , len (visited ) - 1 )
96
+ print ("p2:" , len (loop_obstacles ))
32
97
33
98
34
99
if __name__ == "__main__" :
0 commit comments