You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
advent-of-code/2023/ruby/day_10.rb

102 lines
2.5 KiB

require "forwardable"
DIR_DELTAS = {
N: [-1, 0],
W: [ 0, -1],
E: [ 0, 1],
S: [ 1, 0],
}
TILE_NEIGHBOR_DELTAS = {
?| => "NS",
?- => "EW",
?L => "NE",
?J => "NW",
?7 => "SW",
?F => "SE",
?S => "NEWS",
}.transform_values {|dirs| dirs.chars.map { DIR_DELTAS.fetch(_1.to_sym) }}
Tiles = Data.define(:tiles) do
extend Forwardable
def_delegator :tiles, :key
def_delegator :tiles, :fetch
def_delegator :tiles, :size
def neighbors(coord)
TILE_NEIGHBOR_DELTAS.fetch(self.fetch(coord))
.map {|delta| coord.zip(delta).map { _1 + _2 }}
.select { tiles.has_key?(_1) }
end
def to_s
coords = tiles.keys
y_range, x_range = *extents
y_range.map {|y|
x_range.map {|x| tiles.fetch([y,x], ?.) }.join
}.join("\n")
end
def extents
y_range = Range.new(*tiles.keys.map(&:first).minmax)
x_range = Range.new(*tiles.keys.map(&:last).minmax)
[y_range, x_range]
end
end
tiles = ARGF.readlines(chomp: true).each.with_index.inject({}) {|tiles, (row, y)|
tiles.merge(
row.chars.each.with_index
.reject {|tile,_| tile == ?. }
.to_h {|tile,x| [[y,x], tile] }
)
}
tiles = Tiles.new(tiles)
start = tiles.key(?S)
seed = tiles.neighbors(start)
.find {|candidate|
tiles.neighbors(candidate).any? { tiles.fetch(_1) == ?S }
}
loop_ = [seed]
until loop_.last == start
cur = loop_.last
loop_ << tiles.neighbors(cur)
.reject { _1 == loop_[-2] }
.first
end
# fix the start tile
start_dirs = [seed, loop_[-2]].map {|coord| start.zip(coord).map { _2 - _1 }}
tile = TILE_NEIGHBOR_DELTAS.find { _2.sort == start_dirs.sort }[0]
tiles.tiles[start] = tile
loop_ = Tiles.new(loop_.to_h { [_1, tiles.fetch(_1)] })
# puts loop_
# part one
p loop_.size / 2
# part two
y_range, x_range = *loop_.extents
p y_range.sum {|y|
x_range.chunk_while {|x_a, x_b|
a = loop_.fetch([y, x_a], nil)
b = loop_.fetch([y, x_b], nil)
# it's a chunk if it's multiple empty tiles or a pipe that goes east
(a.nil? && b.nil?) || (a && TILE_NEIGHBOR_DELTAS.fetch(a).include?([0,1]))
}.select {|chunk|
# keep empty tiles
end_tiles = chunk.values_at(0, -1).map {|x| loop_.fetch([y,x], nil) }
next true if end_tiles.all?(&:nil?)
# only keep pipes that cross the horizontal axis
deltas = end_tiles.flat_map {|tile| TILE_NEIGHBOR_DELTAS.fetch(tile) }
[[-1,0], [1,0]].all? {|dy| deltas.include?(dy) }
}.sum {|chunk|
tiles = chunk.map {|x| loop_.fetch([y,x], nil) }
(tiles[0]...tiles[0]) ? tiles.count(nil) : 0 # lol
}
}