|
|
|
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
|
|
|
|
}
|
|
|
|
}
|