#!/usr/bin/env ruby # frozen_string_literal: true require "pathname" require "set" class Grid NEIGHBOR = { n: [0, -1], s: [0, 1], e: [1, 0], w: [-1, 0], ne: [1, -1], se: [1, 1], nw: [-1, -1], sw: [-1, 1], }.map(&:freeze).to_h.freeze def add_row(row) @area ||= [] row = row.map { |x| x == "#" ? 1 : 0 } @width ||= row.size @area.push(row[0..@height]) end def to_s = @area.map(&:to_s).join("\n") def row_count = @area.size def col_count = @width def dup grid = self.class.new grid.instance_variable_set(:@area, @area.map(&:dup).dup) grid.instance_variable_set(:@width, @width.dup) grid.instance_variable_set(:@sticky, @sticky&.dup) grid end def []=(x, y, val) return if @sticky&.include?([x, y]) @area[y][x] = val end def [](x, y) @area[y][x] end def make_sticky(x, y, val = 1) @sticky ||= Set.new self[x, y] = val @sticky.add([x, y]) end def count_on @area.sum { |r| r.sum } end def advance(steps) steps.times do |step| change_count = advance_step() return step if change_count.zero? end end private def advance_step changes = [] @area.each_with_index do |row, y| row.each_with_index do |_, x| nb_on_count = neighbors_that_are_on(x, y) if self[x, y] == 1 && (nb_on_count < 2 || nb_on_count > 3) changes.push([x, y, 0]) elsif self[x, y] == 0 && nb_on_count == 3 changes.push([x, y, 1]) end end end changes.each do |(x, y, val)| self[x, y] = val end changes.size end def neighbors_that_are_on(x, y) NEIGHBOR.keys.sum { |d| neighbor_on?(x, y, d) ? 1 : 0 } end def neighbor_on?(x, y, direction) nb_x, nb_y = NEIGHBOR[direction].then { |(nx, ny)| [x + nx, y + ny] } return false if nb_x < 0 || nb_y < 0 neighbor = @area.dig(nb_y, nb_x) return false unless neighbor neighbor == 1 end end def parse_file(file) file.each_line.each_with_object(Grid.new) do |line, grid| grid.add_row(line.chomp.chars) end end def main(file_paths) file_paths.each do |file_path| puts "File: #{file_path}" grid1 = parse_file(file_path) grid2 = grid1.dup grid1.advance(100) solution1 = grid1.count_on puts " Solution 1: #{solution1}" grid2.make_sticky(0, 0) grid2.make_sticky(0, grid2.col_count - 1) grid2.make_sticky(grid2.row_count - 1, 0) grid2.make_sticky(grid2.row_count - 1, grid2.col_count - 1) grid2.advance(100) solution2 = grid2.count_on puts " Solution 2: #{solution2}" end end main(ARGV.map { |x| Pathname(x) })