11require 'set'
22
33class Day16
4+
5+ class DayTwo
6+ DIRECTIONS = [
7+ [ 0 , -1 ] , # North
8+ [ 1 , 0 ] , # East
9+ [ 0 , 1 ] , # South
10+ [ -1 , 0 ] # West
11+ ]
12+
13+ TURN_COST = 1000
14+ MOVE_COST = 1
15+
16+ def initialize ( input )
17+ @grid = input . split ( "\n " )
18+ @rows = @grid . size
19+ @cols = @grid [ 0 ] . size
20+ end
21+
22+ def run!
23+ # Run, Dijkstra, Run!
24+ start_xy = find_cell ( 'S' )
25+ end_xy = find_cell ( 'E' )
26+
27+ direction = 1 # East
28+
29+ # 3D array to keep track of distances from each cell
30+ distances = Array . new ( @cols ) { Array . new ( @rows ) { Array . new ( 4 , Float ::INFINITY ) } }
31+
32+ # Start cell
33+ distances [ start_xy [ 0 ] ] [ start_xy [ 1 ] ] [ direction ] = 0
34+
35+ # Priority queue for [cost, x, y, direction]
36+ p_queue = [ ]
37+ p_queue << [ 0 , start_xy [ 0 ] , start_xy [ 1 ] , direction ]
38+
39+ while !p_queue . empty?
40+ # Sort priority queueu by cost
41+ p_queue . sort_by! { |a | a [ 0 ] }
42+
43+ # Get next items from queue
44+ cost , x , y , direction = p_queue . shift
45+
46+ # Reached end
47+ # return cost if x == end_xy[0] && y == end_xy[1]
48+
49+ # Skip if we already have a lower cost to this cell
50+ next if distances [ x ] [ y ] [ direction ] < cost
51+
52+ # Try moving forward
53+ new_x = x + DIRECTIONS [ direction ] [ 0 ]
54+ new_y = y + DIRECTIONS [ direction ] [ 1 ]
55+
56+ if can_move? ( new_x , new_y )
57+ new_cost = cost + MOVE_COST
58+ if new_cost < distances [ new_x ] [ new_y ] [ direction ]
59+ distances [ new_x ] [ new_y ] [ direction ] = new_cost
60+ p_queue << [ new_cost , new_x , new_y , direction ]
61+ end
62+ end
63+
64+ # Try turning
65+ # Left
66+ left_dir = ( direction - 1 ) % 4
67+ new_cost = cost + TURN_COST
68+ # Turning costs, even if we do not move. But only do it if moving
69+ # afterwards makes sense
70+ if new_cost < distances [ x ] [ y ] [ left_dir ]
71+ distances [ x ] [ y ] [ left_dir ] = new_cost
72+ p_queue << [ new_cost , x , y , left_dir ]
73+ end
74+
75+ # Right
76+ right_dir = ( direction + 1 ) % 4
77+ new_cost = cost + TURN_COST
78+
79+ if new_cost < distances [ x ] [ y ] [ right_dir ]
80+ distances [ x ] [ y ] [ right_dir ] = new_cost
81+ p_queue << [ new_cost , x , y , right_dir ]
82+ end
83+ end
84+
85+ # Moar work to do, backtracking steps for best cost
86+ best_cost = 4 . times . map { |d | distances [ end_xy [ 0 ] ] [ end_xy [ 1 ] ] [ d ] } . min
87+ return Float ::INFINITY if best_cost == Float ::INFINITY
88+
89+ on_shortest_path = Array . new ( @cols ) { Array . new ( @rows ) { Array . new ( 4 , false ) } }
90+ queue = [ ]
91+
92+ 4 . times do |d |
93+ if distances [ end_xy [ 0 ] ] [ end_xy [ 1 ] ] [ d ] == best_cost
94+ on_shortest_path [ end_xy [ 0 ] ] [ end_xy [ 1 ] ] [ d ] = true
95+ queue << [ end_xy [ 0 ] , end_xy [ 1 ] , d ]
96+ end
97+ end
98+
99+ shortest_path_cells = Set . new
100+ shortest_path_cells << end_xy
101+
102+
103+
104+ while !queue . empty?
105+ x , y , d = queue . shift
106+ cost_here = distances [ x ] [ y ] [ d ]
107+
108+ # Check moves
109+ new_x = x - DIRECTIONS [ d ] [ 0 ]
110+ new_y = y - DIRECTIONS [ d ] [ 1 ]
111+ if distances [ new_x ] [ new_y ] [ d ] + MOVE_COST == cost_here && can_move? ( x , y )
112+ if !on_shortest_path [ new_x ] [ new_y ] [ d ]
113+ on_shortest_path [ new_x ] [ new_y ] [ d ] = true
114+ shortest_path_cells << [ new_x , new_y ]
115+ queue << [ new_x , new_y , d ]
116+ end
117+ end
118+
119+ # Check turns
120+ left_dir = ( d - 1 ) % 4
121+ if distances [ x ] [ y ] [ left_dir ] + TURN_COST == cost_here
122+ unless on_shortest_path [ x ] [ y ] [ left_dir ]
123+ on_shortest_path [ x ] [ y ] [ left_dir ] = true
124+ shortest_path_cells << [ x , y ]
125+ queue << [ x , y , left_dir ]
126+ end
127+ end
128+
129+ right_dir = ( d + 1 ) % 4
130+ if distances [ x ] [ y ] [ right_dir ] + TURN_COST == cost_here
131+ unless on_shortest_path [ x ] [ y ] [ right_dir ]
132+ on_shortest_path [ x ] [ y ] [ right_dir ] = true
133+ shortest_path_cells << [ x , y ]
134+ queue << [ x , y , right_dir ]
135+ end
136+ end
137+ end
138+
139+ shortest_path_cells . size
140+ end
141+
142+ def can_move? ( x , y )
143+ return false if x < 0 || x >= @cols
144+ return false if y < 0 || y >= @rows
145+ return false if @grid [ y ] [ x ] == '#'
146+ true
147+ end
148+
149+ def inside? ( x , y )
150+ x >= 0 && x < @cols && y >= 0 && y < @rows
151+ end
152+
153+
154+ def find_cell ( ch )
155+ @grid . each_with_index do |row , y |
156+ x = row . index ( ch )
157+ if x
158+ return [ x , y ]
159+ end
160+ end
161+ nil
162+ end
163+ end
164+
4165 class DayOne
5166 DIRECTIONS = [
6167 [ 0 , -1 ] , # North
@@ -97,7 +258,6 @@ def find_cell(ch)
97258 @grid . each_with_index do |row , y |
98259 x = row . index ( ch )
99260 if x
100- puts "Found #{ ch } at #{ x } , #{ y } "
101261 return [ x , y ]
102262 end
103263 end
@@ -111,7 +271,8 @@ def part_one(input)
111271 end
112272
113273 def part_two ( input )
114- 0
274+ sim = DayTwo . new ( input )
275+ sim . run!
115276 end
116277end
117278
0 commit comments