Skip to content

Commit 2523ba1

Browse files
committed
Day 15 - Puzzle 2 - Attempt 1
1 parent a9b5094 commit 2523ba1

File tree

2 files changed

+441
-3
lines changed

2 files changed

+441
-3
lines changed

lib/solutions/day_15.rb

Lines changed: 206 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,207 @@
11
class Day15
2+
class PartTwo
3+
# Anything on the map
4+
class Entity < Struct.new(:x, :y, :w, :h, :type)
5+
def move!(dir)
6+
self.x += dir[0]
7+
self.y += dir[1]
8+
end
9+
10+
def at?(pos)
11+
pos[0] >= x && pos[0] < x + w && pos[1] == y
12+
end
13+
14+
def targets(dir)
15+
# PLayers are simple, they move one square
16+
return [[x + dir[0], y + dir[1]]] if type == :player
17+
18+
# Crates are more comlicated when they move.
19+
# Moving on x-axis
20+
# Target 1 square to the left or right of current position. Not that
21+
# crates have x, x+1 coordinates
22+
if dir[0] == -1
23+
# Moving left, check square next to us.
24+
[[x - 1, y]]
25+
elsif dir[0] == 1
26+
# Moving right, check two squares to the right
27+
[[x + 2, y]]
28+
elsif dir[1] != 0
29+
# Check two square above or below
30+
[[x, y + dir[1]], [x + 1, y + dir[1]]]
31+
end
32+
end
33+
end
34+
35+
R_WALL = '##'.freeze
36+
R_CRATE = '[]'.freeze
37+
R_PLAYER = '@.'.freeze
38+
R_FREE = '..'.freeze
39+
40+
MAP_REPLACEMENTS = {
41+
'#' => R_WALL,
42+
'O' => R_CRATE,
43+
'@' => R_PLAYER,
44+
'.' => R_FREE
45+
}
46+
47+
def initialize(input)
48+
map_input, moves_input = input.split(/(?:^\r?\n)+/)
49+
50+
# Moves might be nil during tests
51+
@moves = moves_input.nil? ? nil : moves_input.gsub(/\n/, '').chars
52+
53+
# Load map extra wide
54+
@map = wide_map(map_input)
55+
@width = @map[0].length
56+
@height = @map.length
57+
58+
load_entities
59+
60+
# Not using the map after this,
61+
@map = nil
62+
end
63+
64+
def load_entities
65+
@entities = []
66+
@map.each_with_index do |row, y|
67+
x = 0
68+
while x < row.length
69+
case row[x, 2]
70+
when R_WALL
71+
width = 2
72+
while x + width < row.length && row[x + width, 2] == R_WALL
73+
width += 2
74+
end
75+
@entities << Entity.new(x, y, width, 1, :wall)
76+
x += width
77+
when R_CRATE
78+
@entities << Entity.new(x, y, 2, 1, :crate)
79+
x += 2
80+
when R_PLAYER
81+
# Scan '@.', but ignore the free space
82+
@entities << Entity.new(x, y, 1, 1, :player)
83+
x += 2
84+
else
85+
x += 2
86+
end
87+
end
88+
end
89+
end
90+
91+
def wide_map(map_input)
92+
@map = map_input.split("\n")
93+
@map.map do |row|
94+
row.gsub(/[#O.@]/, MAP_REPLACEMENTS)
95+
end
96+
end
97+
98+
def run!
99+
@moves.each_with_index do |move, i|
100+
step!(move)
101+
end
102+
end
103+
104+
def score
105+
@entities.select { |e| e.type == :crate }
106+
.map { |e| e.y * 100 + e.x }
107+
.sum
108+
end
109+
110+
def step!(direction)
111+
dir = map_direction(direction)
112+
player = @entities.find { |e| e.type == :player }
113+
move!(player, dir)
114+
end
115+
116+
# Move,
117+
def move!(entity, dir)
118+
# puts "Moving #{entity.type} #{entity.x},#{entity.y} #{dir.inspect}"
119+
120+
# Get list of target coords to check
121+
targets = entity.targets(dir)
122+
123+
# puts targets.inspect
124+
125+
entities = find_entities_at(targets)
126+
127+
# puts entities.inspect
128+
129+
# Can't move if we're hitting a wall
130+
return false if entities.any? { |e| e.type == :wall }
131+
132+
# Free space
133+
# REMOVE: entities.all? { |e| e.move!(dir)} will return true for an
134+
# empty array as well.
135+
# if entities.none?
136+
# entity.move!(dir)
137+
# return true
138+
# end
139+
140+
# Now free space, not a wall, so it must be one or two crates
141+
if entities.all? { |e| move!(e, dir)}
142+
entity.move!(dir)
143+
return true
144+
end
145+
146+
false
147+
end
148+
149+
def find_entities_at(coords)
150+
coords.map do |coord|
151+
@entities.find { |entity| entity.at?(coord) }
152+
end.compact.uniq
153+
end
154+
155+
def move_cell(from, to)
156+
from_cell = get_cell(from)
157+
to_cell = get_cell(to)
158+
159+
set_cell(from, to_cell)
160+
set_cell(to, from_cell)
161+
end
162+
163+
def set_cell(pos, cell)
164+
@map[pos[1]][pos[0]] = cell
165+
end
166+
167+
def get_cell(pos)
168+
return nil if pos[0] < 0 || pos[1] < 0 || pos[0] >= @map[0].length || pos[1] >= @map.length
169+
170+
@map[pos[1]][pos[0]]
171+
end
172+
173+
def map_direction(direction)
174+
case direction
175+
when '^'
176+
[0, -1]
177+
when 'v'
178+
[0, 1]
179+
when '<'
180+
[-1, 0]
181+
when '>'
182+
[1, 0]
183+
end
184+
end
185+
186+
def to_map
187+
map = Array.new(@height) { Array.new(@width, '.') }
188+
@entities.each do |entity|
189+
case entity.type
190+
when :player
191+
map[entity.y][entity.x] = '@'
192+
when :crate
193+
map[entity.y][entity.x] = '['
194+
map[entity.y][entity.x + 1] = ']'
195+
when :wall
196+
entity.w.times do |x|
197+
map[entity.y][entity.x + x] = '#'
198+
end
199+
end
200+
end
201+
map.map(&:join).join("\n") + "\n"
202+
end
203+
end
204+
2205
class PartOne
3206
WALL = '#'.freeze
4207
CRATE = 'O'.freeze
@@ -115,6 +318,8 @@ def part_one(input)
115318
end
116319

117320
def part_two(input)
118-
0
321+
sim = PartTwo.new(input)
322+
sim.run!
323+
sim.score
119324
end
120325
end

0 commit comments

Comments
 (0)