aoc2020/day_20/both.rb

196 lines
3.7 KiB
Ruby

# frozen_string_literal: true
MONSTER = [
/..................#./,
/#....##....##....###/,
/.#..#..#..#..#..#.../
].freeze
class Tile
attr_accessor :id, :grid
def initialize(id, grid)
@id = id
@grid = grid
end
def top
@grid.first.join
end
def btm
@grid.last.join
end
def left
@grid.map(&:first).join
end
def right
@grid.map(&:last).join
end
def grid_without_borders
out = []
@grid.each_with_index do |line, i|
next if i == 0
next if i == 9
out << line[1..-2]
end
out
end
def edges
[top, right, btm, left]
end
def edge_orientations
out = [*edges]
out.push(*edges.map(&:reverse))
out
end
def flip_vertical
self.class.new @id, @grid.map(&:reverse)
end
def flip_horizontal
self.class.new @id, @grid.reverse
end
def rotate
self.class.new @id, @grid.transpose.map(&:reverse)
end
def all_rotations
out = [self]
3.times do
out << out.last.rotate
end
out
end
def all_orientations
out = [self, flip_horizontal, flip_vertical, flip_horizontal.flip_vertical]
out.dup.each do |tile|
out.push(*tile.all_rotations)
end
out.uniq
end
def to_s
@grid.map(&:join).join "\n"
end
def find_matching_right_from(tiles)
tiles.map(&:all_orientations).flatten.uniq.each do |ori|
return ori if right == ori.left
end
end
def find_matching_bottom_from(tiles)
tiles.map(&:all_orientations).flatten.uniq.each do |ori|
return ori if btm == ori.top
end
end
def find_monsters
count = 0
image = to_s.lines.map(&:chomp)
image[0..-3].each_with_index do |row, y|
row[0..-20].chars.each_index do |x|
count += 1 if monster_at?(x, y, image)
end
end
count
end
def monster_at?(x, y, image)
MONSTER.each_with_index do |regex, i|
return false unless image[y + i][x, 20][regex]
end
true
end
def corner?
t = $input.map do |other|
next if self == other
(edge_orientations & other.edge_orientations).count
end
t.count(2) == 2
end
end
def rotate_top_left(tl)
all_edges = $tiles.map(&:edge_orientations).flatten.uniq
tl.all_orientations.each do |ori|
left_side = ori.left
top_side = ori.top
unless all_edges.include?(left_side) || all_edges.include?(top_side)
return ori
end
end
nil
end
$input = File.read('./input').split("\n\n").map do |tile|
tile = tile.split("\n")
id = tile.shift.scan(/\d+/).first.to_i
Tile.new id, tile.map(&:chars)
end
corner = $input.find(&:corner?)
$tiles = $input.dup
$tiles.delete(corner)
$map = [[]]
$map[0] << rotate_top_left(corner)
11.times do
$map[0] << $map[0].last.find_matching_right_from($tiles)
$tiles.delete($tiles.find { |t| t.id == $map[0].last.id })
$map << [$map.last.first.find_matching_bottom_from($tiles)]
$tiles.delete($tiles.find { |t| t.id == $map.last.last.id })
end
(1..11).each do |y|
11.times do
$map[y] << $map[y].last.find_matching_right_from($tiles)
$tiles.delete($tiles.find { |t| t.id == $map[y].last.id })
end
end
corners = [$map[0][0].id, $map[0][11].id, $map[11][0].id, $map[11][11].id]
puts "Corners: #{corners.inspect}"
puts "Product of corner ids: #{corners.inject(:*)}"
image = ''
$map.each do |row|
out = Array.new(8) { '' }
row.each do |tile|
tile.grid_without_borders.each_with_index do |line, i|
out[i] += line.join
end
end
image += out.join "\n"
image += "\n"
end
image = image.lines.map(&:chomp)
image = Tile.new 1, image.map(&:chars)
monsters = 0
image.all_orientations.each do |ori|
monsters = ori.find_monsters
break if monsters > 0
end
puts "Monsters: #{monsters}"
puts "Num of '#' not belonging to monster: #{image.to_s.count('#') - 15 * monsters}"