class Layout
  def self.from(raw, &logic)
    seats = Hash.new
    raw.split("\n").each.with_index do |row,y|
      row.chars.each.with_index do |pos,x|
        seats[[y,x]] = false if pos == ?L
      end
    end
    self.new(seats, &logic)
  end

  attr_reader :seats, :y_max, :x_max

  def initialize(seats, &logic)
    @seats, @logic = seats, logic
    @y_max = @seats.keys.map(&:first).max
    @x_max = @seats.keys.map(&:last).max
  end

  def [](yx)
    @seats[yx]
  end

  def each
    return enum_for(__method__) unless block_given?

    loop do
      yield self
      seats = tick(&@logic)
      break if seats == @seats
      @seats = seats
    end
  end

  def tick
    @seats.keys.map {|yx|
      [yx, @logic[self, yx]]
    }.to_h
  end

  def to_s
    (0..@y_max).map {|y|
      (0..@x_max).map {|x|
        case @seats[[y,x]]
        when true then ?#
        when false then ?L
        when nil then ?.
        else fail
        end
      }.join
    }.join("\n")
  end
end

NEIGHBORS = (-1..1).flat_map {|y| (-1..1).map {|x| [y, x] }} - [[0, 0]]
day1 = ->(layout, yx) {
  y, x = yx
  occupied_neighbors = NEIGHBORS.filter_map {|dy,dx| layout[[y+dy, x+dx]] }
  if !layout[yx] && occupied_neighbors.empty?
    true
  elsif layout[yx] && occupied_neighbors.size >= 4
    false
  else
    layout[yx]
  end
}

day2 = ->(layout, yx) {
  y, x = yx
  occupied_neighbors = NEIGHBORS.filter_map {|dy,dx|
    (1..).lazy
      .map {|i| [y+dy*i, x+dx*i] }
      .find {|y,x|
        !(0..layout.y_max).cover?(y) ||
          !(0..layout.x_max).cover?(x) ||
          layout.seats.has_key?([y, x])
    }
  }.filter_map {|yx| layout[yx] }
  if !layout[yx] && occupied_neighbors.empty?
    true
  elsif layout[yx] && occupied_neighbors.size >= 5
    false
  else
    layout[yx]
  end
}

layout = Layout.from(ARGF.read, &day2)
layout.each.take_while(&:itself).last
puts layout.seats.count(&:last)