Skip to content

Commit 922f692

Browse files
committed
Day 23 part 1 Ruby solution
1 parent e14cdb8 commit 922f692

File tree

1 file changed

+262
-0
lines changed

1 file changed

+262
-0
lines changed

23-1.rb

+262
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'algorithms' # https://github.com/kanwei/algorithms v1.0.0
4+
5+
class State
6+
attr_accessor :energy
7+
attr_accessor :hallway
8+
attr_accessor :rooms
9+
attr_accessor :solution
10+
11+
def to_s
12+
s = "Energy: #{@energy}\n"
13+
s += "Moves: #{@solution}\n"
14+
s += '#############' + "\n"
15+
s += '#'
16+
@hallway.each_with_index do |h, pos|
17+
s += '.' if pos.between?(2, 5)
18+
s += (h or '.')
19+
end
20+
s += '#' + "\n"
21+
s += '##'
22+
@rooms.each { |r| s += '#' + (r[0] or '.') }
23+
s += '###' + "\n"
24+
s += ' '
25+
@rooms.each { |r| s += '#' + (r[1] or '.') }
26+
s += '#' + "\n"
27+
s += ' #########' + "\n"
28+
s
29+
end
30+
31+
alias inspect to_s
32+
33+
@@cost = {
34+
'A' => 1,
35+
'B' => 10,
36+
'C' => 100,
37+
'D' => 1000
38+
}
39+
40+
@@destination = {
41+
'A' => 0,
42+
'B' => 1,
43+
'C' => 2,
44+
'D' => 3
45+
}
46+
47+
@@hallway_coords = {
48+
0 => 0,
49+
1 => 1,
50+
2 => 3,
51+
3 => 5,
52+
4 => 7,
53+
5 => 9,
54+
6 => 10
55+
}
56+
57+
@@room_coords = {
58+
0 => 2,
59+
1 => 4,
60+
2 => 6,
61+
3 => 8
62+
}
63+
64+
def hallway_out_distance(room, hallway)
65+
d = 3 - @rooms[room].size
66+
d += (@@hallway_coords[hallway] - @@room_coords[room]).abs
67+
d
68+
end
69+
70+
def hallway_in_distance(room, hallway)
71+
d = 2 - @rooms[room].size
72+
d += (@@hallway_coords[hallway] - @@room_coords[room]).abs
73+
d
74+
end
75+
76+
def room_distance(source, dest)
77+
d = 3 - @rooms[source].size
78+
d += (@@room_coords[source] - @@room_coords[dest]).abs
79+
d += 2 - @rooms[dest].size
80+
end
81+
82+
def initialize(rooms)
83+
@energy = 0
84+
@hallway = [nil] * 7
85+
@rooms = rooms.map(&:dup)
86+
@solution = []
87+
end
88+
89+
def initialize_copy(other)
90+
@energy = other.energy
91+
@hallway = other.hallway.dup
92+
@rooms = other.rooms.map(&:dup)
93+
@solution = other.solution.dup
94+
end
95+
96+
def hash
97+
s = @hallway.map { |p| p == nil ? '0' : p }
98+
s += @rooms.map do |room|
99+
case room.size
100+
when 0
101+
'00'
102+
when 1
103+
'0' + room[0]
104+
when 2
105+
room[0] + room[1]
106+
end
107+
end
108+
s.join('').to_i(16)
109+
end
110+
111+
def finished?
112+
finished = hallway.all? nil
113+
rooms.each_index do |position|
114+
finished &&= @rooms[position].all? do |amphipod|
115+
position == @@destination[amphipod]
116+
end
117+
end
118+
finished
119+
end
120+
121+
def ==(other)
122+
@hallway == other.hallway and @rooms == other.rooms and @energy == other.energy
123+
end
124+
125+
alias eql? ==
126+
127+
def moves
128+
possible_moves = []
129+
130+
# Is there space in the destination rooms?
131+
valid_target_room = [true] * 4
132+
@rooms.each_with_index do |occupants, room_no|
133+
valid_target_room[room_no] &&= occupants.size < 2
134+
valid_target_room[room_no] &&= occupants.all? { |amphipod| room_no == @@destination[amphipod] }
135+
end
136+
137+
# Is the hallway clear?
138+
clear_hallway = @hallway.map(&:nil?)
139+
clear_hallway[0] &&= @hallway[1].nil?
140+
clear_hallway[6] &&= @hallway[5].nil?
141+
142+
# Move amphipods out of the hallway into destination rooms
143+
@hallway.each_with_index do |amphipod, hallway_position|
144+
next unless amphipod
145+
146+
destination_room = @@destination[amphipod]
147+
# Hallway positions that need to be clear for this move to be possible
148+
diff = hallway_position - destination_room
149+
clear_range = if diff > 2 # Crossing one or more hallways to the left
150+
destination_room + 2..hallway_position - 1
151+
elsif diff <= 0 # Crossing one or more hallways to the right
152+
hallway_position + 1..destination_room + 1
153+
elsif diff == 1 and hallway_position == 0
154+
1..2
155+
else
156+
1..0 # Room is next to hallway position, no extra clear hallway required.
157+
end
158+
159+
next unless amphipod and valid_target_room[destination_room] and clear_hallway[clear_range].all? true
160+
161+
# We have an amphipod in the hallway, the destination has
162+
# space, and the hallway is clear
163+
moved = self.dup
164+
moved.hallway[hallway_position] = nil
165+
moved.rooms[destination_room].unshift amphipod
166+
moved.energy += hallway_in_distance(destination_room, hallway_position) * @@cost[amphipod]
167+
moved.solution.append "#{amphipod}: H#{hallway_position}->R#{destination_room}"
168+
possible_moves.append moved
169+
end
170+
171+
# Move amphipods between rooms
172+
@rooms.each_with_index do |amphipods, source_room|
173+
next if amphipods.empty?
174+
# No amphipod will move out of this room
175+
next if amphipods.all? { |a| source_room == @@destination[a] }
176+
177+
amphipod = amphipods[0]
178+
destination_room = @@destination[amphipod]
179+
# Hallway positions that need to be clear for this move to be possible
180+
clear_range = if destination_room > source_room
181+
source_room + 2..destination_room + 1
182+
elsif destination_room < source_room
183+
destination_room + 2..source_room + 1
184+
end
185+
186+
next unless valid_target_room[destination_room] and clear_hallway[clear_range].all? true
187+
188+
# We have an amphipod in the wrong room, the destination has
189+
# space, and the hallway is clear
190+
moved = self.dup
191+
moved.rooms[source_room].shift
192+
moved.rooms[destination_room].unshift amphipod
193+
moved.energy += room_distance(source_room, destination_room) * @@cost[amphipod]
194+
moved.solution.append "#{amphipod}: R#{source_room}->R#{destination_room}"
195+
possible_moves.append moved
196+
end
197+
198+
# Move amphipods out of rooms into the hallway
199+
@rooms.each_with_index do |amphipods, source_room|
200+
next if amphipods.empty?
201+
202+
# No amphipod in this room can move out
203+
next if amphipods.all? { |a| source_room == @@destination[a] }
204+
205+
amphipod = amphipods[0]
206+
0.upto(6) do |hallway_position|
207+
# Hallway positions that need to be clear for this move to be possible
208+
diff = hallway_position - source_room
209+
clear_range = if diff > 2 # Crossing one or more hallways to the left
210+
source_room + 2..hallway_position - 1
211+
elsif diff <= 0 # Crossing one or more hallways to the right
212+
hallway_position..source_room + 1
213+
elsif diff == 1 and hallway_position == 0
214+
1..2
215+
else
216+
1..0 # Room is next to hallway position, no extra clear hallway required.
217+
end
218+
219+
next unless clear_hallway[hallway_position] and clear_hallway[clear_range].all? true
220+
221+
# We have an amphipod in the wrong room, and the hallway is clear
222+
moved = self.dup
223+
moved.rooms[source_room].shift
224+
moved.hallway[hallway_position] = amphipod
225+
moved.energy += hallway_out_distance(source_room, hallway_position) * @@cost[amphipod]
226+
moved.solution.append "#{amphipod}: R#{source_room}->H#{hallway_position}"
227+
possible_moves.append moved
228+
end
229+
end
230+
231+
possible_moves
232+
end
233+
end
234+
235+
map = File.read('23.input').lines.map(&:strip)
236+
237+
rooms = []
238+
0.upto(3) do |i|
239+
rooms.append [map[2].delete('#')[i], map[3].delete('#')[i]]
240+
end
241+
242+
start = State.new rooms
243+
visited = Hash.new false
244+
245+
queue = Containers::MinHeap.new
246+
queue.push(start.energy, start)
247+
248+
until queue.empty?
249+
state = queue.pop
250+
251+
if state.finished?
252+
print state.energy, "\n"
253+
exit
254+
end
255+
256+
state.moves.each do |move|
257+
unless visited.has_key? move
258+
queue.push(move.energy, move)
259+
visited[move] = true
260+
end
261+
end
262+
end

0 commit comments

Comments
 (0)