Skip to content

Commit 4669aef

Browse files
committed
Day 16 - Puzzle 2
1 parent 928d334 commit 4669aef

File tree

4 files changed

+209
-9
lines changed

4 files changed

+209
-9
lines changed

.rspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
--color
2-
--format documentation
2+
--format progress

lib/solutions/day_12.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ def part_two(input)
1919

2020
price_perimeter, price_sides = @grid.calculate_price_area_perimeter
2121

22-
puts "Price perimeter: #{price_perimeter}"
23-
puts "Price sides: #{price_sides}"
24-
2522
price_sides
2623
end
2724
end

lib/solutions/day_16.rb

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,167 @@
11
require 'set'
22

33
class 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
116277
end
117278

spec/solutions/day_16_spec.rb

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,51 @@
5353
end
5454

5555
describe '#part_two' do
56-
it 'calculates the correct solutions for part two' do
57-
expect(subject.part_two(input)).to eq(0)
58-
end
56+
it 'calculates example 1' do
57+
expect(subject.part_two(
58+
<<-INPUT
59+
###############
60+
#.......#....E#
61+
#.#.###.#.###.#
62+
#.....#.#...#.#
63+
#.###.#####.#.#
64+
#.#.#.......#.#
65+
#.#.#####.###.#
66+
#...........#.#
67+
###.#.#####.#.#
68+
#...#.....#.#.#
69+
#.#.#.###.#.#.#
70+
#.....#...#.#.#
71+
#.###.#.#.#.#.#
72+
#S..#.....#...#
73+
###############
74+
INPUT
75+
)).to eq(45)
76+
end
77+
78+
it 'calculates example 2' do
79+
expect(subject.part_two(
80+
<<-INPUT
81+
#################
82+
#...#...#...#..E#
83+
#.#.#.#.#.#.#.#.#
84+
#.#.#.#...#...#.#
85+
#.#.#.#.###.#.#.#
86+
#...#.#.#.....#.#
87+
#.#.#.#.#.#####.#
88+
#.#...#.#.#.....#
89+
#.#.#####.#.###.#
90+
#.#.#.......#...#
91+
#.#.###.#####.###
92+
#.#.#...#.....#.#
93+
#.#.#.#####.###.#
94+
#.#.#.........#.#
95+
#.#.#.#########.#
96+
#S#.............#
97+
#################
98+
INPUT
99+
)).to eq(64)
100+
end
59101
end
60102
end
61103

0 commit comments

Comments
 (0)