1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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)